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