Skip to content

Commit 22297f2

Browse files
committed
Commit a game events debugging script and experimental fixes.
1 parent 96eef15 commit 22297f2

File tree

3 files changed

+194
-3
lines changed

3 files changed

+194
-3
lines changed

game_events_debug.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
""" Game Event Parsing Debugger
2+
3+
Recursively searches for unique replays specified by command line argument
4+
and attempts a parse in order to catch the ReadError. The ReadError will
5+
contain all the context information we need to inspect the error.
6+
7+
Outputs the following information in the following format:
8+
9+
ggtracker/ggtracker_replay_202597.SC2Replay
10+
....... 00:04 P2 AbilityEvent - 00 22 0b 01 08 34 07 00 31 00 00 01 1f 0d 21 f8 00 8c c0 80 00 05 ff 00
11+
....... 00:05 P1 SelectionEvent - 38 21 ac 00 06 00 01 05 15 01 03 03 05 18 00 01 05 20 00 01 05 28 00 01
12+
0x000F5 00:05 P2 SelectionEvent - 00 22 ac 00 02 f4 00 00 | 00 0c a1 89 00 10 00 00 00 21 0b 01 00 01 5c 01 00 0c 22 0b
13+
01 08 34 07 00 23 80 00 01 1f 0d 41 18 00 8a c0 80 00 05 ff 00 08 21 ac 00 02 11 01 02
14+
15+
The first line is the full path to the file followed by an indented list
16+
of the last 2 events to be parsed with their associated bytes.
17+
18+
The 3rd line for the event the parser failed on is in the following format:
19+
20+
cursor event_type - event_bytes | following bytes
21+
and the next bytes after that
22+
23+
With a pipe character delimiting where the parser ended the event.
24+
25+
The general workflow is something like this:
26+
27+
1) Run the debugger over a set of replays
28+
2) Figure out where the failed event boundry should have been
29+
3) Alter the reader code to make it happen
30+
4) Repeat
31+
32+
To figure out how to alter the reader code you'll probably need to compile
33+
lists of failed events with the correct bytes and look for patterns that
34+
would help you read them the right way. Sometimes it also helps to think
35+
about what that event represents and what would make its content length
36+
vary. Cross checking against a replay can also be useful.
37+
38+
When altering the reader code, NEVER change code in an older game events
39+
reader unless you really really know what you are doing. Instead copy the
40+
function you'd like to change into the GameEventsReader_22612 class and
41+
make your changes there.
42+
"""
43+
import sys
44+
import sc2reader
45+
from sc2reader.exceptions import ReadError
46+
from sc2reader.utils import get_files
47+
48+
BYTES_WIDTH = 45
49+
50+
def format_bytes(bytes):
51+
"""I find it easier to read hex when spaced by byte"""
52+
bytes = bytes.encode("hex")
53+
ret = ""
54+
for i in range(len(bytes)):
55+
if i%2:
56+
ret += " "
57+
else:
58+
ret += bytes[i:i+2]
59+
return ret
60+
61+
def get_name(obj):
62+
return obj.__class__.__name__
63+
64+
for filename in set(sum((list(get_files(arg)) for arg in sys.argv[1:]),list())):
65+
try:
66+
replay = sc2reader.load_replay(filename, debug=True)
67+
except ReadError as e:
68+
print filename
69+
if len(e.game_events):
70+
for event in e.game_events[-3:-1]:
71+
print " ....... {:0>2}:{:0>2} P{} {: <19} - {}".format(event.second/60, event.second%60, event.pid, get_name(event), format_bytes(event.bytes))
72+
last_event = e.game_events[-1]
73+
line1_end = e.location+BYTES_WIDTH-len(last_event.bytes)-1
74+
line1 = e.buffer.read_range(e.location, line1_end)
75+
line2 = e.buffer.read_range(line1_end,line1_end+BYTES_WIDTH)
76+
print " 0x{:0>5X} {:0>2}:{:0>2} P{} {: <19} - {} | {}".format(e.location, last_event.second/60, last_event.second%60, last_event.pid, get_name(last_event), format_bytes(last_event.bytes), format_bytes(line1))
77+
print " "*41+format_bytes(line2)
78+
else:
79+
print " 0x{:0>5X} {: <19} - {}".format(0, "None", format_bytes(e.buffer.read_range(0,BYTES_WIDTH+1)))
80+
print

sc2reader/readers.py

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ def __call__(self, buffer, replay):
203203
0x01: self.get_action_parser,
204204
0x02: self.get_unknown2_parser,
205205
0x03: self.get_camera_parser,
206-
0x04: self.get_unknown4_parser
206+
0x04: self.get_unknown4_parser,
207+
0x05: self.get_unknown5_parser
207208
}
208209

209210
game_events, frames = list(), 0
@@ -275,7 +276,7 @@ def get_setup_parser(self, code):
275276
# the game starting immediately afterwords. On occasion, for unknown
276277
# reasons, other events (particularly camera events) will register
277278
# before the game has actually started. Weird.
278-
if code in (0x0B, 0x0C, 0x2C): return self.parse_join_event
279+
if code in (0x0B, 0x0C, 0x1C, 0x2C): return self.parse_join_event
279280
elif code in (0x05,): return self.parse_start_event
280281
else:
281282
raise ParseError("Unknown Setup Parser Code {0}".format(code))
@@ -329,6 +330,9 @@ def get_unknown4_parser(self, code):
329330
else:
330331
raise ParseError("Unknown Unknown4 Parser Code {0}".format(code))
331332

333+
def get_unknown5_parser(self, code):
334+
return self.parse_05XX_event
335+
332336
def parse_join_event(self, buffer, frames, type, code, pid):
333337
return PlayerJoinEvent(frames, pid, type, code)
334338

@@ -742,3 +746,109 @@ def right_click_target(self, buffer, frames, type, code, pid, flag, atype):
742746
target = (buffer.read_int(BIG_ENDIAN), buffer.read_short(BIG_ENDIAN))
743747
buffer.skip(10)
744748
return TargetAbilityEvent(frames, pid, type, code, ability, target)
749+
750+
class GameEventsReader_22612(GameEventsReader_19595):
751+
def parse_join_event(self, buffer, frames, type, code, pid):
752+
buffer.read_byte()
753+
return PlayerJoinEvent(frames, pid, type, code)
754+
755+
def cancel(self, buffer, frames, type, code, pid, flag, atype):
756+
ability = buffer.read_short(endian=BIG_ENDIAN)
757+
ability = ability << 8 | buffer.read_byte()
758+
759+
# creation autoid number / object id
760+
created_id = buffer.read_object_id()
761+
# TODO : expose the id
762+
buffer.read_byte()
763+
return AbilityEvent(frames, pid, type, code, ability)
764+
765+
def parse_ability_event(self, buffer, frames, type, code, pid):
766+
"""Moves the right click move to the top level"""
767+
flag = buffer.read_byte()
768+
atype = buffer.read_byte()
769+
770+
if atype & 0x20: # cancels only now
771+
return self.cancel(buffer, frames, type, code, pid, flag, atype)
772+
elif atype & 0x40: # all command card abilities?
773+
return self.command_card(buffer, frames, type, code, pid, flag, atype)
774+
elif atype & 0x80: # right-click on target?
775+
return self.right_click_target(buffer, frames, type, code, pid, flag, atype)
776+
elif atype < 0x10: #new to patch 1.3.3, location now??
777+
return self.right_click_move(buffer, frames, type, code, pid, flag, atype)
778+
779+
raise ParseError()
780+
781+
def right_click_move(self, buffer, frames, type, code, pid, flag, atype):
782+
# This may port back to previous versions. Haven't checked
783+
# Can differ by up to (+/-1, +/-8) from sc2gears readings
784+
# See command_card implementation for details
785+
if atype in (0x00,0x02):
786+
buffer.skip(3)
787+
while buffer.read_byte() & 0xF0 != 0:
788+
buffer.skip(8)
789+
return AbilityEvent(frames,pid,type,code,0x3601)
790+
791+
792+
else: # atype == 0x08:
793+
#!?!?!?!?!?
794+
buffer.skip(6)
795+
if buffer.read_byte() == 0x00:
796+
buffer.skip(3)
797+
else:
798+
buffer.skip(12)
799+
"""
800+
buffer.skip(8)
801+
if buffer.read_byte() == 0x0D:
802+
buffer.skip(9)
803+
buffer.read_byte()
804+
"""
805+
return AbilityEvent(frames,pid,type,code,0x3601)
806+
807+
808+
"""
809+
x = buffer.read_short(BIG_ENDIAN)/256.0
810+
buffer.shift(5) # what is this for, why 5 bits instead of 4?
811+
y = buffer.read_short(BIG_ENDIAN)/256.0
812+
buffer.read(bits=5) # I'll just assume we should do it again
813+
"""
814+
815+
#buffer.skip(19)
816+
817+
return AbilityEvent(frames, pid, type, code, 0x3601)
818+
819+
def parse_05XX_event(self, buffer, frames, type, code, pid):
820+
buffer.skip(4)
821+
return UnknownEvent(frames, pid, type, code)
822+
823+
def parse_overlay(self, buffer, mode):
824+
mode = mode >> 1
825+
if mode == 0x01: # deselect overlay mask
826+
data = buffer.read_bitmask()
827+
elif mode == 0x02: # deselect mask
828+
data = [buffer.read_byte() for i in range(buffer.read_byte())]
829+
elif mode == 0x03: # replace mask
830+
data = [buffer.read_byte() for i in range(buffer.read_byte())]
831+
else:
832+
data=None
833+
834+
buffer.align()
835+
836+
return mode, data
837+
838+
def parse_selection_event(self, buffer, frames, type, code, pid):
839+
840+
bank = code >> 4
841+
first = buffer.read_byte() # TODO ?
842+
843+
deselect = self.parse_overlay(buffer, buffer.shift(4))
844+
845+
# <count> (<type_id>, <count>,)*
846+
object_types = [ (buffer.read_object_type(read_modifier=True), buffer.read_byte(), ) for i in range(buffer.read_byte()) ]
847+
# <count> (<object_id>,)*
848+
object_ids = [ buffer.read_object_id() for i in range(buffer.read_byte()) ]
849+
850+
# repeat types count times
851+
object_types = chain(*[[object_type,]*count for (object_type, count) in object_types])
852+
objects = zip(object_ids, object_types)
853+
854+
return SelectionEvent(frames, pid, type, code, bank, objects, deselect)

sc2reader/resources.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ def register_default_readers(self):
512512
self.register_reader('replay.game.events', readers.GameEventsReader_Base(), lambda r: r.build < 16561)
513513
self.register_reader('replay.game.events', readers.GameEventsReader_16561(), lambda r: 16561 <= r.build < 18574)
514514
self.register_reader('replay.game.events', readers.GameEventsReader_18574(), lambda r: 18574 <= r.build < 19595)
515-
self.register_reader('replay.game.events', readers.GameEventsReader_19595(), lambda r: 19595 <= r.build)
515+
self.register_reader('replay.game.events', readers.GameEventsReader_19595(), lambda r: 19595 <= r.build < 22612)
516+
self.register_reader('replay.game.events', readers.GameEventsReader_22612(), lambda r: 22612 <= r.build)
516517

517518
def register_default_datapacks(self):
518519
"""Registers factory default datapacks."""

0 commit comments

Comments
 (0)