changeset 188:008f90ebfdc0

Fix replay handling and add support for encrypted replays
author Thibaut Girka <thib@sitedethib.com>
date Thu, 27 Oct 2011 14:24:07 +0200
parents 46793ccfedca
children ba3297ab3bde
files eclviewer.py pytouhou/formats/t6rp.py pytouhou/game/game.py pytouhou/game/games.py pytouhou/opengl/gamerunner.py
diffstat 5 files changed, 64 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/eclviewer.py
+++ b/eclviewer.py
@@ -35,11 +35,16 @@ def main(path, stage_num, rank, characte
         character = replay.character
         if not replay.levels[stage_num-1]:
             raise Exception
+        from pytouhou.utils.random import Random
+        prng = Random(replay.levels[stage_num-1].random_seed)
+    else:
+        prng = None
 
     resource_loader = Loader()
     resource_loader.scan_archives(os.path.join(path, name)
                                     for name in ('CM.DAT', 'ST.DAT'))
-    game = EoSDGame(resource_loader, [PlayerState(character=character)], stage_num, rank, 16)
+    game = EoSDGame(resource_loader, [PlayerState(character=character)], stage_num, rank, 16,
+                    prng=prng)
 
     # Load stage data
     stage = resource_loader.get_stage('stage%d.std' % stage_num)
--- a/pytouhou/formats/t6rp.py
+++ b/pytouhou/formats/t6rp.py
@@ -13,6 +13,9 @@
 ##
 
 from struct import unpack
+from io import BytesIO
+
+from pytouhou.utils.random import Random
 from pytouhou.utils.helpers import read_string
 
 from pytouhou.utils.helpers import get_logger
@@ -22,43 +25,70 @@ logger = get_logger(__name__)
 
 class Level(object):
     def __init__(self):
+        self.score = 0
+        self.random_seed = 0
+
+        self.power = 0
+        self.lives = 2
+        self.bombs = 3
+        self.difficulty = 16
         self.keys = []
 
 
 class T6RP(object):
     def __init__(self):
+        self.version = 0x102
+        self.character = 0
+        self.rank = 0
+        self.key = 0
         self.levels = []
 
 
     @classmethod
-    def read(cls, file):
+    def read(cls, file, decrypt=True, verify=True):
         if file.read(4) != b'T6RP':
             raise Exception
-        if file.read(2) != b'\x02\x01':
-            raise Exception
 
         replay = cls()
 
-        replay.character, replay.rank, checksum, unknown, key, unknown = unpack('<BBHIBB', file.read(10))
+        replay.version, replay.character, replay.rank = unpack('<HBB', file.read(4))
+        checksum, replay.unknown1, replay.unknown2, replay.key = unpack('<IBBB', file.read(7))
+
+        # Decrypt data
+        if decrypt:
+            decrypted_file = BytesIO()
+            file.seek(0)
+            decrypted_file.write(file.read(15))
+            decrypted_file.write(b''.join(chr((ord(c) - replay.key - 7*i) & 0xff) for i, c in enumerate(file.read())))
+            file = decrypted_file
+            file.seek(15)
+
+        # Verify checksum
+        if verify:
+            data = file.read()
+            file.seek(15)
+            if checksum != (sum(ord(c) for c in data) + 0x3f000318 + replay.key) & 0xffffffff:
+                raise Exception #TODO
+
+        replay.unknown3 = unpack('<B', file.read(1))
         replay.date = read_string(file, 9, 'ascii')
         replay.name = read_string(file, 9, 'ascii').rstrip()
-        unknown, replay.score, unknown, replay.slowdown, unknown = unpack('<HIIfI', file.read(18))
+        replay.unknown4, replay.score, replay.unknown5, replay.slowdown, replay.unknown6 = unpack('<HIIfI', file.read(18))
 
         stages_offsets = unpack('<7I', file.read(28))
 
         replay.levels = []
 
         for offset in stages_offsets:
-            replay.levels.append(None)
-
             if offset == 0:
                 continue
 
             level = Level()
-            replay.levels[-1] = level
+            replay.levels.append(level)
 
             file.seek(offset)
-            level.score, level.random_seed, unknown, level.power, level.lives, level.bombs, level.difficulty, unknown = unpack('<IHHBbbBI', file.read(16))
+            (level.score, level.random_seed, level.unknown1, level.power,
+             level.lives, level.bombs, level.difficulty, level.unknown2) = unpack('<IHHBbbBI', file.read(16))
 
             while True:
                 time, keys, unknown = unpack('<IHH', file.read(8))
@@ -66,5 +96,6 @@ class T6RP(object):
                 if time == 9999999:
                     break
 
-                level.keys.append((time, keys))
+                level.keys.append((time, keys, unknown))
 
+        return replay
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.py
@@ -27,7 +27,7 @@ from pytouhou.game.effect import Particl
 
 class Game(object):
     def __init__(self, resource_loader, player_states, stage, rank, difficulty,
-                 bullet_types, item_types, characters, nb_bullets_max=None):
+                 bullet_types, item_types, characters, prng=None, nb_bullets_max=None):
         self.resource_loader = resource_loader
 
         self.nb_bullets_max = nb_bullets_max
@@ -48,7 +48,7 @@ class Game(object):
         self.difficulty = difficulty
         self.boss = None
         self.spellcard = None
-        self.prng = Random()
+        self.prng = prng or Random()
         self.frame = 0
 
         self.enm_anm_wrapper = resource_loader.get_anm_wrapper2(('stg%denm.anm' % stage,
@@ -57,6 +57,11 @@ class Game(object):
         ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage)
         self.ecl_runner = ECLMainRunner(ecl, self)
 
+        #TODO: The game calls it two times. What for?
+        # See 102h.exe@0x413220 if you think you're brave enough.
+        self.prng.rand_uint16()
+        self.prng.rand_uint16()
+
 
     def drop_bonus(self, x, y, _type, end_pos=None):
         player = self.players[0] #TODO
--- a/pytouhou/game/games.py
+++ b/pytouhou/game/games.py
@@ -18,7 +18,7 @@ from pytouhou.game.bullettype import Bul
 from pytouhou.game.itemtype import ItemType
 
 class EoSDGame(Game):
-    def __init__(self, resource_loader, players, stage, rank, difficulty):
+    def __init__(self, resource_loader, players, stage, rank, difficulty, **kwargs):
         etama3 = resource_loader.get_anm_wrapper(('etama3.anm',))
         etama4 = resource_loader.get_anm_wrapper(('etama4.anm',))
         bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=4),
@@ -48,5 +48,5 @@ class EoSDGame(Game):
                       Character(player01, 5., 2.5, 2.5, 42.)]
 
         Game.__init__(self, resource_loader, players, stage, rank, difficulty,
-                      bullet_types, item_types, characters, nb_bullets_max=640)
+                      bullet_types, item_types, characters, nb_bullets_max=640, **kwargs)
 
--- a/pytouhou/opengl/gamerunner.py
+++ b/pytouhou/opengl/gamerunner.py
@@ -30,7 +30,6 @@ class GameRunner(pyglet.window.Window, G
             self.push_handlers(self.keys)
         else:
             self.keys = 0
-            self.instruction_pointer = 0
             self.replay_level = replay.levels[game.stage-1]
 
         self.fps_display = pyglet.clock.ClockDisplay()
@@ -113,20 +112,16 @@ class GameRunner(pyglet.window.Window, G
                     keystate |= 128
                 if self.keys[pyglet.window.key.LCTRL]:
                     keystate |= 256
-                self.game.run_iter(keystate) #TODO: self.keys...
+                self.game.run_iter(keystate)
             else:
-                frame, keys = self.replay_level.keys[self.instruction_pointer]
+                keystate = 0
+                for frame, _keystate, unknown in self.replay_level.keys:
+                    if self.game.frame < frame:
+                        break
+                    else:
+                        keystate = _keystate
 
-                if frame > self.game.frame:
-                    self.game.run_iter(self.keys)
-                    return
-
-                self.instruction_pointer += 1
-
-                if frame == self.game.frame:
-                    self.keys = keys
-
-                self.game.run_iter(self.keys)
+                self.game.run_iter(keystate)
 
 
     def on_draw(self):