comparison pytouhou/formats/anm0.py @ 429:40d5f3083ebc

Implement PCB’s ANM2 format and vm.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 03 Aug 2013 15:48:57 +0200
parents 88e2a2485b2b
children
comparison
equal deleted inserted replaced
428:f41a26971a19 429:40d5f3083ebc
22 22
23 from struct import pack, unpack 23 from struct import pack, unpack
24 from pytouhou.utils.helpers import read_string, get_logger 24 from pytouhou.utils.helpers import read_string, get_logger
25 25
26 from pytouhou.formats import WrongFormatError 26 from pytouhou.formats import WrongFormatError
27 from pytouhou.formats.thtx import Texture
27 28
28 29
29 logger = get_logger(__name__) 30 logger = get_logger(__name__)
30 31
31 #TODO: refactor/clean up 32 #TODO: refactor/clean up
37 self.interrupts = {} 38 self.interrupts = {}
38 39
39 40
40 41
41 class ANM0(object): 42 class ANM0(object):
42 _instructions = {0: ('', 'delete'), 43 _instructions = {0: {0: ('', 'delete'),
43 1: ('I', 'set_sprite'), 44 1: ('I', 'set_sprite'),
44 2: ('ff', 'set_scale'), 45 2: ('ff', 'set_scale'),
45 3: ('I', 'set_alpha'), 46 3: ('I', 'set_alpha'),
46 4: ('BBBx', 'set_color'), 47 4: ('BBBx', 'set_color'),
47 5: ('I', 'jump'), 48 5: ('I', 'jump'),
48 7: ('', 'toggle_mirrored'), 49 7: ('', 'toggle_mirrored'),
49 9: ('fff', 'set_3d_rotations'), 50 9: ('fff', 'set_3d_rotations'),
50 10: ('fff', 'set_3d_rotations_speed'), 51 10: ('fff', 'set_3d_rotations_speed'),
51 11: ('ff', 'set_scale_speed'), 52 11: ('ff', 'set_scale_speed'),
52 12: ('ii', 'fade'), 53 12: ('ii', 'fade'),
53 13: ('', 'set_blendmode_add'), 54 13: ('', 'set_blendmode_add'),
54 14: ('', 'set_blendmode_alphablend'), 55 14: ('', 'set_blendmode_alphablend'),
55 15: ('', 'keep_still'), 56 15: ('', 'keep_still'),
56 16: ('ii', 'set_random_sprite'), 57 16: ('ii', 'set_random_sprite'),
57 17: ('fff', 'set_3d_translation'), 58 17: ('fff', 'set_3d_translation'),
58 18: ('fffi', 'move_to_linear'), 59 18: ('fffi', 'move_to_linear'),
59 19: ('fffi', 'move_to_decel'), 60 19: ('fffi', 'move_to_decel'),
60 20: ('fffi', 'move_to_accel'), 61 20: ('fffi', 'move_to_accel'),
61 21: ('', 'wait'), 62 21: ('', 'wait'),
62 22: ('i', 'interrupt_label'), 63 22: ('i', 'interrupt_label'),
63 23: ('', 'set_corner_relative_placement'), 64 23: ('', 'set_corner_relative_placement'),
64 24: ('', 'wait_ex'), 65 24: ('', 'wait_ex'),
65 25: ('i', 'set_allow_offset'), #TODO: better name 66 25: ('i', 'set_allow_offset'), #TODO: better name
66 26: ('i', 'set_automatic_orientation'), 67 26: ('i', 'set_automatic_orientation'),
67 27: ('f', 'shift_texture_x'), 68 27: ('f', 'shift_texture_x'),
68 28: ('f', 'shift_texture_y'), 69 28: ('f', 'shift_texture_y'),
69 29: ('i', 'set_visible'), 70 29: ('i', 'set_visible'),
70 30: ('ffi', 'scale_in'), 71 30: ('ffi', 'scale_in'),
71 31: ('i', None)} 72 31: ('i', None)},
73
74 2: {0: ('', 'noop'),
75 1: ('', 'delete'),
76 2: ('', 'keep_still'),
77 3: ('I', 'set_sprite'),
78 4: ('II', 'jump_bis'),
79 5: ('III', 'jump_ex'),
80 6: ('fff', 'set_3d_translation'),
81 7: ('ff', 'set_scale'),
82 8: ('I', 'set_alpha'),
83 9: ('BBBx', 'set_color'),
84 10: ('', 'toggle_mirrored'),
85 12: ('fff', 'set_3d_rotations'),
86 13: ('fff', 'set_3d_rotations_speed'),
87 14: ('ff', 'set_scale_speed'),
88 15: ('ii', 'fade'),
89 16: ('I', 'set_blendmode'),
90 17: ('fffi', 'move_to_linear'),
91 18: ('fffi', 'move_to_decel'),
92 19: ('fffi', 'move_to_accel'),
93 20: ('', 'wait'),
94 21: ('i', 'interrupt_label'),
95 22: ('', 'set_corner_relative_placement'),
96 23: ('', 'wait_ex'),
97 24: ('i', 'set_allow_offset'), #TODO: better name
98 25: ('i', 'set_automatic_orientation'),
99 26: ('f', 'shift_texture_x'),
100 27: ('f', 'shift_texture_y'),
101 28: ('i', 'set_visible'),
102 29: ('ffi', 'scale_in'),
103 30: ('i', None),
104 31: ('I', None),
105 32: ('IIfff', 'move_in_linear_bis'),
106 33: ('IIBBBx', 'change_color_in'),
107 34: ('III', 'fade_bis'),
108 35: ('IIfff', 'rotate_in_bis'),
109 36: ('IIff', 'scale_in_bis'),
110 37: ('II', 'set_int'),
111 38: ('ff', 'set_float'),
112 42: ('ff', 'decrement_float'),
113 50: ('fff', 'add_float'),
114 52: ('fff', 'substract_float'),
115 55: ('III', 'divide_int'),
116 59: ('II', 'set_random_int'),
117 60: ('ff', 'set_random_float'),
118 69: ('IIII', 'branch_if_not_equal'),
119 79: ('I', 'wait_duration'),
120 80: ('I', None)}}
72 121
73 122
74 def __init__(self): 123 def __init__(self):
75 self.version = 0 124 self.version = 0
76 self.size = (0, 0) 125 self.size = (0, 0)
80 self.scripts = {} 129 self.scripts = {}
81 130
82 131
83 @classmethod 132 @classmethod
84 def read(cls, file): 133 def read(cls, file):
85 nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) 134 anm_list = []
86 width, height, format, unknown1 = unpack('<IIII', file.read(16)) 135 start_offset = 0
87 first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) 136 while True:
88 version, unknown2, thtxoffset, hasdata, nextoffset, zero2 = unpack('<IIIIII', file.read(24)) 137 file.seek(start_offset)
89 if version != 0: 138 nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12))
90 raise WrongFormatError(version) 139 width, height, fmt, unknown1 = unpack('<IIII', file.read(16))
91 assert (zero1, zero2) == (0, 0) 140 first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12))
92 141 version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24))
93 sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] 142
94 script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] 143 if version == 0:
95 144 assert zero1 == 0
96 self = cls() 145 assert unknown3 == 0
97 146 assert has_data == 0
98 self.size = (width, height) 147 elif version == 2:
99 148 assert zero1 == 0
100 # Names 149 assert secondary_name_offset == 0
101 if first_name_offset: 150 assert has_data == 1 # Can be false but we don’t support that yet.
102 file.seek(first_name_offset) 151 else:
103 self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? 152 raise WrongFormatError(version)
104 if secondary_name_offset: 153
105 file.seek(secondary_name_offset) 154 instructions = cls._instructions[version]
106 self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? 155
107 156 sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)]
108 157 script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)]
109 # Sprites 158
110 file.seek(64) 159 self = cls()
111 self.sprites = {} 160
112 for offset in sprite_offsets: 161 self.size = (width, height)
113 file.seek(offset) 162 self.version = version
114 idx, x, y, width, height = unpack('<Iffff', file.read(20)) 163
115 self.sprites[idx] = x, y, width, height 164 # Names
116 165 if first_name_offset:
117 166 file.seek(start_offset + first_name_offset)
118 # Scripts 167 self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really?
119 self.scripts = {} 168 if secondary_name_offset:
120 for i, offset in script_offsets: 169 file.seek(start_offset + secondary_name_offset)
121 self.scripts[i] = Script() 170 self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really?
122 instruction_offsets = [] 171
123 file.seek(offset) 172
124 while True: 173 # Sprites
125 instruction_offsets.append(file.tell() - offset) 174 for offset in sprite_offsets:
126 time, opcode, size = unpack('<HBB', file.read(4)) 175 file.seek(start_offset + offset)
176 idx, x, y, width, height = unpack('<Iffff', file.read(20))
177 self.sprites[idx] = x, y, width, height
178
179
180 # Scripts
181 for i, offset in script_offsets:
182 self.scripts[i] = Script()
183 instruction_offsets = []
184 file.seek(start_offset + offset)
185 while True:
186 instruction_offsets.append(file.tell() - (start_offset + offset))
187 if version == 0:
188 time, opcode, size = unpack('<HBB', file.read(4))
189 elif version == 2:
190 opcode, size, time, mask = unpack('<HHHH', file.read(8))
191 if opcode == 0xffff:
192 break
193 size -= 8
194 data = file.read(size)
195 if opcode in instructions:
196 args = unpack('<%s' % instructions[opcode][0], data)
197 else:
198 args = (data,)
199 logger.warn('unknown opcode %d', opcode)
200
201 self.scripts[i].append((time, opcode, args))
202 if version == 0 and opcode == 0:
203 break
204
205 # Translate offsets to instruction pointers and register interrupts
206 for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])):
207 time, opcode, args = instr
208 if version == 0:
209 if opcode == 5:
210 args = (instruction_offsets.index(args[0]),)
211 elif opcode == 22:
212 interrupt = args[0]
213 self.scripts[i].interrupts[interrupt] = j + 1
214 elif version == 2:
215 if opcode == 4:
216 args = (instruction_offsets.index(args[0]), args[1])
217 elif opcode == 5:
218 args = (args[0], instruction_offsets.index(args[1]), args[2])
219 elif opcode == 21:
220 interrupt = args[0]
221 self.scripts[i].interrupts[interrupt] = j + 1
222 elif opcode == 69:
223 args = (args[0], args[1], instruction_offsets.index(args[2]), args[3])
224 self.scripts[i][j] = time, opcode, args
225
226 # Texture
227 if has_data:
228 file.seek(start_offset + texture_offset)
229 magic = file.read(4)
230 assert magic == b'THTX'
231 zero, fmt, width, height, size = unpack('<HHHHI', file.read(12))
232 assert zero == 0
127 data = file.read(size) 233 data = file.read(size)
128 if opcode in cls._instructions: 234 self.texture = Texture(width, height, fmt, data)
129 args = unpack('<%s' % cls._instructions[opcode][0], data) 235
130 else: 236 anm_list.append(self)
131 args = (data,) 237
132 logger.warn('unknown opcode %d', opcode) 238 if next_offset:
133 239 start_offset += next_offset
134 self.scripts[i].append((time, opcode, args)) 240 else:
135 if opcode == 0: 241 break
136 break 242
137 243 return anm_list
138 # Translate offsets to instruction pointers and register interrupts
139 for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])):
140 time, opcode, args = instr
141 if opcode == 5:
142 args = (instruction_offsets.index(args[0]),)
143 elif opcode == 22:
144 interrupt = args[0]
145 self.scripts[i].interrupts[interrupt] = j + 1
146 self.scripts[i][j] = time, opcode, args
147
148 return self