Mercurial > touhou
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 |