Skip to content

Commit 7def4b9

Browse files
committed
Some changes to the parser:
* Pull out reading replay events to another method * Make sure to fallback to the previous position in case we failed to parse header data * More failsafes in the parser
1 parent 3091026 commit 7def4b9

File tree

3 files changed

+76
-44
lines changed

3 files changed

+76
-44
lines changed

pygbx/bytereader.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import struct
33
from io import IOBase
44
from pygbx.headers import Vector3
5+
from os import SEEK_END
56

67
class PositionInfo(object):
78
"""
@@ -46,8 +47,12 @@ def __init__(self, obj):
4647
self.data = obj
4748
if isinstance(obj, IOBase):
4849
self.get_bytes = self.__get_bytes_file
50+
self.data.seek(0, SEEK_END)
51+
self.size = self.data.tell()
52+
self.data.seek(0)
4953
else:
5054
self.get_bytes = self.__get_bytes_generic
55+
self.size = len(self.data)
5156

5257
self.pos = 0
5358
self.seen_loopback = False
@@ -93,6 +98,12 @@ def read(self, num_bytes, typestr=None):
9398
logging.error(e)
9499
return 0
95100

101+
def size(self):
102+
if isinstance(self.data, IOBase):
103+
return self.data.tell()
104+
else:
105+
return len(self.data)
106+
96107
def __get_bytes_file(self, num_bytes):
97108
self.data.seek(self.pos)
98109
return self.data.read(num_bytes)

pygbx/gbx.py

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def __init__(self, obj):
132132
cdata = self.root_parser.read(compressed_data_size)
133133
self.data = bytearray(lzo.decompress(cdata, False, data_size))
134134

135-
bp = ByteReader(self.data[:])
135+
bp = ByteReader(self.data)
136136
self._read_node(self.class_id, -1, bp)
137137

138138
def __read_sub_folder(self):
@@ -141,19 +141,19 @@ def __read_sub_folder(self):
141141
self.root_parser.read_string()
142142
self.__read_sub_folder()
143143

144-
def find_raw_chunk_id(self, chunk_id):
145-
"""Finds a raw chunk ID in the file, skipping through any data that does not match the chunk ID provided.
144+
"""Finds a raw chunk ID in the file, skipping through any data that does not match the chunk ID provided.
146145
147-
It is not guaranteed that the chunk found is indeed the desired data, as it could be other unrelated
148-
chunk that bytes happened to form the chunk ID provided.
146+
It is not guaranteed that the chunk found is indeed the desired data, as it could be other unrelated
147+
chunk that bytes happened to form the chunk ID provided.
149148
150-
Args:
151-
chunk_id (int): the chunk ID to search for
149+
Args:
150+
chunk_id (int): the chunk ID to search for
152151
153-
Returns:
154-
ByteParser with the current position set right after the chunk ID, or None
155-
if no specified chunk ID was found
156-
"""
152+
Returns:
153+
ByteParser with the current position set right after the chunk ID, or None
154+
if no specified chunk ID was found
155+
"""
156+
def find_raw_chunk_id(self, chunk_id):
157157
bp = ByteReader(self.data[:])
158158
for i in range(len(self.data) - 4):
159159
bp.pos = i
@@ -209,6 +209,10 @@ def _read_user_data(self):
209209
user_data_pos = self.root_parser.pos
210210
num_chunks = self.root_parser.read_uint32()
211211
for _ in range(num_chunks):
212+
if self.root_parser.pos >= self.root_parser.size - 1:
213+
self.root_parser.pos = user_data_pos + self.user_data_size
214+
return
215+
212216
cid = self.root_parser.read_uint32()
213217
self.root_parser.push_info()
214218
size = self.root_parser.read_uint32()
@@ -671,15 +675,21 @@ def _read_node(self, class_id, depth, bp, add=True):
671675
# For TM2
672676
if ('version' in self.__replay_header_info
673677
and self.__replay_header_info['version'] >= 8):
674-
game_class.login = bp.read_string()
678+
pos = bp.pos
679+
try:
680+
game_class.login = bp.read_string()
681+
except:
682+
bp.pos = pos
683+
675684
elif cid == 0x309200F or cid == 0x2401B00F:
676685
game_class.login = bp.read_string()
677686
elif cid == 0x3092010 or cid == 0x2401B010:
678687
bp.read_string_lookback()
679688
elif cid == 0x3092012 or cid == 0x2401B012:
680689
bp.skip(4 + 16)
681690
# import binascii
682-
# bp.read(4)
691+
# # bp.read(4)
692+
# print(bp.read_uint32())
683693
# print(f'{binascii.hexlify(bp.read(16))}')
684694
# print()
685695
elif cid == 0x3092013 or cid == 0x2401B013:
@@ -692,35 +702,8 @@ def _read_node(self, class_id, depth, bp, add=True):
692702
bp.read_string_lookback()
693703
bp.read_string_lookback()
694704
bp.read_string_lookback()
695-
elif cid == 0x3092019 or cid == 0x03092025 or cid == 0x2401B019:
696-
if cid == 0x03092025:
697-
bp.skip(4)
698-
699-
game_class.events_duration = bp.read_uint32()
700-
bp.skip(4)
701-
702-
num_control_names = bp.read_uint32()
703-
game_class.control_names = []
704-
for _ in range(num_control_names):
705-
name = bp.read_string_lookback()
706-
if name != '':
707-
game_class.control_names.append(name)
708-
709-
if len(game_class.control_names) == 0:
710-
continue
711-
712-
num_control_entries = bp.read_uint32()
713-
bp.skip(4)
714-
for _ in range(num_control_entries):
715-
time = bp.read_uint32() - 100000
716-
name = game_class.control_names[bp.read_byte()]
717-
entry = headers.ControlEntry(time, name, bp.read_uint16(), bp.read_uint16())
718-
game_class.control_entries.append(entry)
719-
720-
game_class.game_version = bp.read_string()
721-
bp.skip(3 * 4)
722-
bp.read_string()
723-
bp.skip(4)
705+
elif cid == 0x3092019 or cid == 0x03092025 or cid == 0x2401B019 or cid == 0x2401B011:
706+
Gbx.read_ghost_events(game_class, bp, cid)
724707
elif cid == 0x309201c:
725708
bp.skip(32)
726709
elif cid == 0x03093004 or cid == 0x2403f004:
@@ -731,6 +714,39 @@ def _read_node(self, class_id, depth, bp, add=True):
731714
else:
732715
return
733716

717+
@staticmethod
718+
def read_ghost_events(game_class, bp, cid):
719+
if cid == 0x03092025:
720+
game_class.is_maniaplanet = True
721+
bp.skip(4)
722+
723+
game_class.events_duration = bp.read_uint32()
724+
if game_class.events_duration != 0:
725+
bp.skip(4)
726+
727+
num_control_names = bp.read_uint32()
728+
game_class.control_names = []
729+
for _ in range(num_control_names):
730+
name = bp.read_string_lookback()
731+
if name != '':
732+
game_class.control_names.append(name)
733+
734+
if len(game_class.control_names) == 0:
735+
return
736+
737+
num_control_entries = bp.read_uint32()
738+
bp.skip(4)
739+
for _ in range(num_control_entries):
740+
time = bp.read_uint32() - 100000
741+
name = game_class.control_names[bp.read_byte()]
742+
entry = headers.ControlEntry(time, name, bp.read_uint16(), bp.read_uint16())
743+
game_class.control_entries.append(entry)
744+
745+
game_class.game_version = bp.read_string()
746+
bp.skip(3 * 4)
747+
bp.read_string()
748+
bp.skip(4)
749+
734750
@staticmethod
735751
def read_ghost(game_class, bp):
736752
uncomp_sz = bp.read_uint32()
@@ -771,8 +787,12 @@ def read_ghost(game_class, bp):
771787
gr.read_int16(), gr.read_int16(),
772788
gr.read_int8(), gr.read_int8())
773789

774-
if len(sample_sizes) == 1:
775-
sample_sz = sample_sizes[0]
790+
len_sizes = len(sample_sizes)
791+
if i >= len_sizes:
792+
if len_sizes >= 1:
793+
sample_sz = sample_sizes[0]
794+
else:
795+
sample_sz = 0
776796
else:
777797
sample_sz = sample_sizes[i]
778798

pygbx/headers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def __init__(self, id):
168168
self.game_version = ''
169169
self.control_names = []
170170
self.events_duration = 0
171+
self.is_maniaplanet = False
171172
super(CGameCtnGhost, self).__init__(id)
172173

173174
class ControlEntry(object):

0 commit comments

Comments
 (0)