Mercurial > touhou
comparison pytouhou/formats/ecl.py @ 372:704bea2e4360
Use a future-proof ECL parser.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Mon, 06 Aug 2012 22:52:22 +0200 |
parents | 3be4c1078095 |
children | eef492100f4c |
comparison
equal
deleted
inserted
replaced
371:6702bc0215dc | 372:704bea2e4360 |
---|---|
35 instructions. | 35 instructions. |
36 The second section is a list of a different set of instructions describing | 36 The second section is a list of a different set of instructions describing |
37 enemy waves, triggering dialogs and level completion. | 37 enemy waves, triggering dialogs and level completion. |
38 | 38 |
39 Instance variables: | 39 Instance variables: |
40 main -- list of instructions describing waves and triggering dialogs | 40 mains -- list of lists of instructions describing waves and triggering dialogs |
41 subs -- list of subroutines | 41 subs -- list of subroutines |
42 """ | 42 """ |
43 | 43 |
44 _instructions = {0: ('', 'noop?'), | 44 _instructions = {0: ('', 'noop?'), |
45 1: ('I', 'delete?'), | 45 1: ('I', 'delete?'), |
160 8: ('', 'call_msg'), | 160 8: ('', 'call_msg'), |
161 9: ('', 'wait_msg'), | 161 9: ('', 'wait_msg'), |
162 10: ('II', 'resume_ecl'), | 162 10: ('II', 'resume_ecl'), |
163 12: ('', 'stop_time')} | 163 12: ('', 'stop_time')} |
164 | 164 |
165 _parameters = {6: {'main_count': 1, | |
166 'nb_main_offsets': 3, | |
167 'jumps_list': {2: 1, 3: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1}}} | |
168 | |
165 | 169 |
166 def __init__(self): | 170 def __init__(self): |
167 self.main = [] | 171 self.mains = [] |
168 self.subs = [[]] | 172 self.subs = [] |
169 | 173 |
170 | 174 |
171 @classmethod | 175 @classmethod |
172 def read(cls, file): | 176 def read(cls, file, version=6): |
173 """Read an ECL file. | 177 """Read an ECL file. |
174 | 178 |
175 Raise an exception if the file is invalid. | 179 Raise an exception if the file is invalid. |
176 Return a ECL instance otherwise. | 180 Return a ECL instance otherwise. |
177 """ | 181 """ |
178 | 182 |
179 sub_count, main_offset = unpack('<II', file.read(8)) | 183 parameters = cls._parameters[version] |
180 if file.read(8) != b'\x00\x00\x00\x00\x00\x00\x00\x00': | 184 |
181 raise Exception #TODO | 185 sub_count, main_count = unpack('<HH', file.read(4)) |
186 | |
187 main_offsets = unpack('<%dI' % parameters['nb_main_offsets'], file.read(4 * parameters['nb_main_offsets'])) | |
182 sub_offsets = unpack('<%dI' % sub_count, file.read(4 * sub_count)) | 188 sub_offsets = unpack('<%dI' % sub_count, file.read(4 * sub_count)) |
183 | 189 |
184 ecl = cls() | 190 ecl = cls() |
185 ecl.subs = [] | |
186 ecl.main = [] | |
187 | 191 |
188 # Read subs | 192 # Read subs |
189 for sub_offset in sub_offsets: | 193 for sub_offset in sub_offsets: |
190 file.seek(sub_offset) | 194 file.seek(sub_offset) |
191 ecl.subs.append([]) | 195 ecl.subs.append([]) |
220 # Indeed, jump instructions are relative and byte-based. | 224 # Indeed, jump instructions are relative and byte-based. |
221 # Since our representation doesn't conserve offsets, we have to | 225 # Since our representation doesn't conserve offsets, we have to |
222 # keep trace of where the jump is supposed to end up. | 226 # keep trace of where the jump is supposed to end up. |
223 for instr_offset, (i, instr) in zip(instruction_offsets, enumerate(ecl.subs[-1])): | 227 for instr_offset, (i, instr) in zip(instruction_offsets, enumerate(ecl.subs[-1])): |
224 time, opcode, rank_mask, param_mask, args = instr | 228 time, opcode, rank_mask, param_mask, args = instr |
225 if opcode in (2, 29, 30, 31, 32, 33, 34): # relative_jump | 229 if opcode in parameters['jumps_list']: |
226 frame, relative_offset = args | 230 num = parameters['jumps_list'][opcode] |
227 args = frame, instruction_offsets.index(instr_offset + relative_offset) | 231 args = list(args) |
228 elif opcode == 3: # relative_jump_ex | 232 args[num] = instruction_offsets.index(instr_offset + args[num]) |
229 frame, relative_offset, counter_id = args | 233 ecl.subs[-1][i] = time, opcode, rank_mask, param_mask, tuple(args) |
230 args = frame, instruction_offsets.index(instr_offset + relative_offset), counter_id | |
231 ecl.subs[-1][i] = time, opcode, rank_mask, param_mask, args | |
232 | 234 |
233 | 235 |
234 # Read main | 236 # Read main |
235 file.seek(main_offset) | 237 for main_offset in main_offsets: |
236 while True: | 238 if main_offset == 0: |
237 time, = unpack('<H', file.read(2)) | |
238 if time == 0xffff: | |
239 break | 239 break |
240 | 240 |
241 sub, opcode, size = unpack('<HHH', file.read(6)) | 241 file.seek(main_offset) |
242 data = file.read(size - 8) | 242 ecl.mains.append([]) |
243 | 243 while True: |
244 if opcode in cls._main_instructions: | 244 time, sub = unpack('<HH', file.read(4)) |
245 args = unpack('<%s' % cls._main_instructions[opcode][0], data) | 245 if time == 0xffff and sub == 4: |
246 else: | 246 break |
247 args = (data,) | 247 |
248 logger.warn('unknown main opcode %d', opcode) | 248 opcode, size = unpack('<HH', file.read(4)) |
249 | 249 data = file.read(size - 8) |
250 ecl.main.append((time, sub, opcode, args)) | 250 |
251 if opcode in cls._main_instructions: | |
252 args = unpack('<%s' % cls._main_instructions[opcode][0], data) | |
253 else: | |
254 args = (data,) | |
255 logger.warn('unknown main opcode %d', opcode) | |
256 | |
257 ecl.mains[-1].append((time, sub, opcode, args)) | |
251 | 258 |
252 return ecl | 259 return ecl |
253 | 260 |
254 | 261 |
255 def write(self, file): | 262 def write(self, file, version=6): |
256 """Write to an ECL file.""" | 263 """Write to an ECL file.""" |
264 | |
265 parameters = self._parameters[version] | |
257 | 266 |
258 sub_count = len(self.subs) | 267 sub_count = len(self.subs) |
259 sub_offsets = [] | 268 sub_offsets = [] |
260 main_offset = 0 | 269 main_offset = 0 |
261 | 270 |
284 raise | 293 raise |
285 | 294 |
286 #TODO: clean up this mess | 295 #TODO: clean up this mess |
287 for instruction, data, offset in zip(sub, instruction_datas, instruction_offsets): | 296 for instruction, data, offset in zip(sub, instruction_datas, instruction_offsets): |
288 time, opcode, rank_mask, param_mask, args = instruction | 297 time, opcode, rank_mask, param_mask, args = instruction |
289 if opcode in (2, 29, 30, 31, 32, 33, 34): # relative_jump | 298 if opcode in parameters['jumps_list']: |
290 frame, index = args | 299 num = parameters['jumps_list'][opcode] |
291 args = frame, instruction_offsets[index] - offset | 300 args = list(args) |
292 format = '<IHHHH%s' % self._instructions[opcode][0] | 301 args[num] = instruction_offsets[args[num]] - offset |
293 size = calcsize(format) | |
294 data = pack(format, time, opcode, size, rank_mask, param_mask, *args) | |
295 elif opcode == 3: # relative_jump_ex | |
296 frame, index, counter_id = args | |
297 args = frame, instruction_offsets[index] - offset, counter_id | |
298 format = '<IHHHH%s' % self._instructions[opcode][0] | 302 format = '<IHHHH%s' % self._instructions[opcode][0] |
299 size = calcsize(format) | 303 size = calcsize(format) |
300 data = pack(format, time, opcode, size, rank_mask, param_mask, *args) | 304 data = pack(format, time, opcode, size, rank_mask, param_mask, *args) |
301 file.write(data) | 305 file.write(data) |
302 file.write(b'\xff' * 6 + b'\x0c\x00\x00\xff\xff\x00') | 306 file.write(b'\xff' * 6 + b'\x0c\x00\x00\xff\xff\x00') |
303 | 307 |
304 # Write main | 308 # Write main |
305 main_offset = file.tell() | 309 main_offsets = [0] * parameters['nb_main_offsets'] |
306 for time, sub, opcode, args in self.main: | 310 for i, main in enumerate(self.mains): |
307 format = '<HHHH%s' % self._main_instructions[opcode][0] | 311 main_offsets[i] = file.tell() |
308 size = calcsize(format) | 312 for time, sub, opcode, args in main: |
309 | 313 format = '<HHHH%s' % self._main_instructions[opcode][0] |
310 file.write(pack(format, time, sub, opcode, size, *args)) | 314 size = calcsize(format) |
311 file.write(b'\xff\xff\x04\x00') | 315 |
316 file.write(pack(format, time, sub, opcode, size, *args)) | |
317 file.write(b'\xff\xff\x04\x00') | |
312 | 318 |
313 # Patch header | 319 # Patch header |
314 file.seek(0) | 320 file.seek(0) |
315 file.write(pack('<IIII%dI' % sub_count, sub_count, main_offset, 0, 0, *sub_offsets)) | 321 file.write(pack('<I%dI%dI' % (parameters['nb_main_offsets'], sub_count), sub_count, *(main_offsets + sub_offsets))) |
316 | 322 |