diff 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
line wrap: on
line diff
--- a/pytouhou/formats/ecl.py
+++ b/pytouhou/formats/ecl.py
@@ -37,7 +37,7 @@ class ECL(object):
     enemy waves, triggering dialogs and level completion.
 
     Instance variables:
-    main -- list of instructions describing waves and triggering dialogs
+    mains -- list of lists of instructions describing waves and triggering dialogs
     subs -- list of subroutines
     """
 
@@ -162,28 +162,32 @@ class ECL(object):
                           10: ('II', 'resume_ecl'),
                           12: ('', 'stop_time')}
 
+    _parameters = {6: {'main_count': 1,
+                       'nb_main_offsets': 3,
+                       'jumps_list': {2: 1, 3: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1}}}
+
 
     def __init__(self):
-        self.main = []
-        self.subs = [[]]
+        self.mains = []
+        self.subs = []
 
 
     @classmethod
-    def read(cls, file):
+    def read(cls, file, version=6):
         """Read an ECL file.
 
         Raise an exception if the file is invalid.
         Return a ECL instance otherwise.
         """
 
-        sub_count, main_offset = unpack('<II', file.read(8))
-        if file.read(8) != b'\x00\x00\x00\x00\x00\x00\x00\x00':
-            raise Exception #TODO
+        parameters = cls._parameters[version]
+
+        sub_count, main_count = unpack('<HH', file.read(4))
+
+        main_offsets = unpack('<%dI' % parameters['nb_main_offsets'], file.read(4 * parameters['nb_main_offsets']))
         sub_offsets = unpack('<%dI' % sub_count, file.read(4 * sub_count))
 
         ecl = cls()
-        ecl.subs = []
-        ecl.main = []
 
         # Read subs
         for sub_offset in sub_offsets:
@@ -222,39 +226,44 @@ class ECL(object):
             # keep trace of where the jump is supposed to end up.
             for instr_offset, (i, instr) in zip(instruction_offsets, enumerate(ecl.subs[-1])):
                 time, opcode, rank_mask, param_mask, args = instr
-                if opcode in (2, 29, 30, 31, 32, 33, 34): # relative_jump
-                    frame, relative_offset = args
-                    args = frame, instruction_offsets.index(instr_offset + relative_offset)
-                elif opcode == 3: # relative_jump_ex
-                    frame, relative_offset, counter_id = args
-                    args = frame, instruction_offsets.index(instr_offset + relative_offset), counter_id
-                ecl.subs[-1][i] = time, opcode, rank_mask, param_mask, args
+                if opcode in parameters['jumps_list']:
+                    num = parameters['jumps_list'][opcode]
+                    args = list(args)
+                    args[num] = instruction_offsets.index(instr_offset + args[num])
+                    ecl.subs[-1][i] = time, opcode, rank_mask, param_mask, tuple(args)
 
 
         # Read main
-        file.seek(main_offset)
-        while True:
-            time, = unpack('<H', file.read(2))
-            if time == 0xffff:
+        for main_offset in main_offsets:
+            if main_offset == 0:
                 break
 
-            sub, opcode, size = unpack('<HHH', file.read(6))
-            data = file.read(size - 8)
+            file.seek(main_offset)
+            ecl.mains.append([])
+            while True:
+                time, sub = unpack('<HH', file.read(4))
+                if time == 0xffff and sub == 4:
+                    break
 
-            if opcode in cls._main_instructions:
-                args = unpack('<%s' % cls._main_instructions[opcode][0], data)
-            else:
-                args = (data,)
-                logger.warn('unknown main opcode %d', opcode)
+                opcode, size = unpack('<HH', file.read(4))
+                data = file.read(size - 8)
 
-            ecl.main.append((time, sub, opcode, args))
+                if opcode in cls._main_instructions:
+                    args = unpack('<%s' % cls._main_instructions[opcode][0], data)
+                else:
+                    args = (data,)
+                    logger.warn('unknown main opcode %d', opcode)
+
+                ecl.mains[-1].append((time, sub, opcode, args))
 
         return ecl
 
 
-    def write(self, file):
+    def write(self, file, version=6):
         """Write to an ECL file."""
 
+        parameters = self._parameters[version]
+
         sub_count = len(self.subs)
         sub_offsets = []
         main_offset = 0
@@ -286,15 +295,10 @@ class ECL(object):
             #TODO: clean up this mess
             for instruction, data, offset in zip(sub, instruction_datas, instruction_offsets):
                 time, opcode, rank_mask, param_mask, args = instruction
-                if opcode in (2, 29, 30, 31, 32, 33, 34): # relative_jump
-                    frame, index = args
-                    args = frame, instruction_offsets[index] - offset
-                    format = '<IHHHH%s' % self._instructions[opcode][0]
-                    size = calcsize(format)
-                    data = pack(format, time, opcode, size, rank_mask, param_mask, *args)
-                elif opcode == 3: # relative_jump_ex
-                    frame, index, counter_id = args
-                    args = frame, instruction_offsets[index] - offset, counter_id
+                if opcode in parameters['jumps_list']:
+                    num = parameters['jumps_list'][opcode]
+                    args = list(args)
+                    args[num] = instruction_offsets[args[num]] - offset
                     format = '<IHHHH%s' % self._instructions[opcode][0]
                     size = calcsize(format)
                     data = pack(format, time, opcode, size, rank_mask, param_mask, *args)
@@ -302,15 +306,17 @@ class ECL(object):
             file.write(b'\xff' * 6 + b'\x0c\x00\x00\xff\xff\x00')
 
         # Write main
-        main_offset = file.tell()
-        for time, sub, opcode, args in self.main:
-            format = '<HHHH%s' % self._main_instructions[opcode][0]
-            size = calcsize(format)
+        main_offsets = [0] * parameters['nb_main_offsets']
+        for i, main in enumerate(self.mains):
+            main_offsets[i] = file.tell()
+            for time, sub, opcode, args in main:
+                format = '<HHHH%s' % self._main_instructions[opcode][0]
+                size = calcsize(format)
 
-            file.write(pack(format, time, sub, opcode, size, *args))
-        file.write(b'\xff\xff\x04\x00')
+                file.write(pack(format, time, sub, opcode, size, *args))
+            file.write(b'\xff\xff\x04\x00')
 
         # Patch header
         file.seek(0)
-        file.write(pack('<IIII%dI' % sub_count, sub_count, main_offset, 0, 0, *sub_offsets))
+        file.write(pack('<I%dI%dI' % (parameters['nb_main_offsets'], sub_count), sub_count, *(main_offsets + sub_offsets)))