comparison pytouhou/formats/t6rp.py @ 373:6deab6ad8be8

Add the ability to save a replay.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sun, 05 Aug 2012 16:37:26 +0200
parents 56523a16db1d
children 6a63fd3deb76
comparison
equal deleted inserted replaced
372:704bea2e4360 373:6deab6ad8be8
18 The T6RP file format is an encrypted format describing different aspects of 18 The T6RP file format is an encrypted format describing different aspects of
19 a game of EoSD. Since the EoSD engine is entirely deterministic, a small 19 a game of EoSD. Since the EoSD engine is entirely deterministic, a small
20 replay file is sufficient to unfold a full game. 20 replay file is sufficient to unfold a full game.
21 """ 21 """
22 22
23 from struct import unpack 23 from struct import unpack, pack
24 from io import BytesIO 24 from io import BytesIO
25 from time import strftime
25 26
26 from pytouhou.utils.helpers import read_string, get_logger 27 from pytouhou.utils.helpers import read_string, get_logger
27 28
28 logger = get_logger(__name__) 29 logger = get_logger(__name__)
29 30
45 class T6RP(object): 46 class T6RP(object):
46 def __init__(self): 47 def __init__(self):
47 self.version = 0x102 48 self.version = 0x102
48 self.character = 0 49 self.character = 0
49 self.rank = 0 50 self.rank = 0
51 self.unknown1 = 0
52 self.unknown2 = 0
50 self.key = 0 53 self.key = 0
54 self.unknown3 = 0
55 self.date = strftime('%d/%m/%y')
56 self.name = 'PyTouhou'
57 self.unknown4 = 0
58 self.score = 0
59 self.unknown5 = 0
60 self.slowdown = 0.
61 self.unknown6 = 0
51 self.levels = [None] * 7 62 self.levels = [None] * 7
52 63
53 64
54 @classmethod 65 @classmethod
55 def read(cls, file, decrypt=True, verify=True): 66 def read(cls, file, decrypt=True, verify=True):
82 93
83 # Verify checksum 94 # Verify checksum
84 if verify: 95 if verify:
85 data = file.read() 96 data = file.read()
86 file.seek(15) 97 file.seek(15)
87 if checksum != (sum(ord(c) for c in data) + 0x3f000318 + replay.key) & 0xffffffff: 98 real_sum = (sum(ord(c) for c in data) + 0x3f000318 + replay.key) & 0xffffffff
88 raise Exception #TODO 99 if checksum != real_sum:
100 raise Exception('Checksum mismatch: %d ≠ %d.' % (checksum, real_sum))
89 101
90 replay.unknown3, = unpack('<B', file.read(1)) 102 replay.unknown3, = unpack('<B', file.read(1))
91 replay.date = file.read(9) #read_string(file, 9, 'ascii') 103 replay.date = file.read(9) #read_string(file, 9, 'ascii')
92 replay.name = file.read(9) #read_string(file, 9, 'ascii').rstrip() 104 replay.name = file.read(9) #read_string(file, 9, 'ascii').rstrip()
93 replay.unknown4, replay.score, replay.unknown5, replay.slowdown, replay.unknown6 = unpack('<HIIfI', file.read(18)) 105 replay.unknown4, replay.score, replay.unknown5, replay.slowdown, replay.unknown6 = unpack('<HIIfI', file.read(18))
112 break 124 break
113 125
114 level.keys.append((time, keys, unknown)) 126 level.keys.append((time, keys, unknown))
115 127
116 return replay 128 return replay
129
130
131 def write(self, file, encrypt=True):
132 if encrypt:
133 encrypted_file = file
134 file = BytesIO()
135
136 file.write(b'T6RP')
137 file.write(pack('<HBB', self.version, self.character, self.rank))
138
139 checksum_offset = file.tell()
140 file.seek(4, 1) # For checksum
141 file.write(pack('<BBB', self.unknown1, self.unknown2, self.key))
142
143 file.write(pack('<B', self.unknown3))
144
145 #TODO: find a more elegant method.
146 n = 9 - len(self.date)
147 file.write(self.date)
148 file.write('\0' * n)
149 n = 9 - len(self.name)
150 file.write(self.name)
151 file.write('\0' * n)
152
153 file.write(pack('<HIIfI', self.unknown4, self.score, self.unknown5, self.slowdown, self.unknown6))
154
155 stages_offsets_offset = file.tell()
156 file.seek(7*4, 1) # Skip the stages offsets.
157
158 stages_offsets = []
159 for level in self.levels:
160 if not level:
161 stages_offsets.append(0)
162 continue
163
164 stages_offsets.append(file.tell())
165 file.write(pack('<IHHBbbBI', level.score, level.random_seed,
166 level.point_items, level.power, level.lives,
167 level.bombs, level.difficulty, level.unknown))
168
169 for time, keys, unknown in level.keys:
170 file.write(pack('<IHH', time, keys, unknown))
171
172 file.write(pack('<IHH', 9999999, 0, 0))
173
174 file.seek(stages_offsets_offset)
175 file.write(pack('<7I', *stages_offsets))
176
177 # Write checksum
178 file.seek(15)
179 data = file.read()
180 checksum = (sum(ord(c) for c in data) + 0x3f000318 + self.key) & 0xffffffff
181 file.seek(checksum_offset)
182 file.write(pack('<I', checksum))
183
184 # Encrypt
185 if encrypt:
186 file.seek(0)
187 encrypted_file.write(file.read(15))
188 encrypted_file.write(b''.join(chr((ord(c) + self.key + 7*i) & 0xff) for i, c in enumerate(file.read())))
189