Mercurial > touhou
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 |