-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathparser.py
More file actions
201 lines (180 loc) · 6.92 KB
/
parser.py
File metadata and controls
201 lines (180 loc) · 6.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import struct
import sys
import traceback
import gc
from typing import List, Dict, NamedTuple, Tuple
from collections import defaultdict
from dataclasses import dataclass
AGENT_STRUCT = '<QIIHHHHHH64s4x' # Q: uint64, I: uint32, H: uint16, 64s: char[64]
AGENT_SIZE = struct.calcsize(AGENT_STRUCT)
EVENT_STRUCT = '<qQQiiIIHHHHBBBBBBBBBBBBI'
EVENT_SIZE = struct.calcsize(EVENT_STRUCT)
@dataclass
class EvtcHeader:
magic: str
version: str
instruction_set_id: int
revision: int
@dataclass
class EvtcAgent:
address: int
profession: int
is_elite: int
toughness: int
healing: int
condition: int
concentration: int
name: str
party: int
team: str
instid: int
@dataclass
class EvtcSkill:
skill_id: int
name: str
@dataclass
class EvtcEvent:
time: int
src_agent: int
dst_agent: int
value: int
buff_dmg: int
overstack_value: int
skill_id: int
src_instid: int
dst_instid: int
src_master_instid: int
dst_master_instid: int
iff: int
buff: int
result: int
is_activation: int
is_buffremove: int
is_ninety: int
is_fifty: int
is_moving: int
is_statechange: int
is_flanking: int
is_shields: int
is_offcycle: int
pad: int
def free_evtc_data(header, agents, skills, events):
"""
Explicitly delete EVTC objects and trigger garbage collection
to free memory once data has been processed.
"""
del header
del agents[:]
del agents
del skills[:]
del skills
del events[:]
del events
gc.collect()
print("---=== Memory freed ===---")
def parse_evtc(file_path: str) -> Tuple[EvtcHeader, List[EvtcAgent], List[EvtcSkill], List[EvtcEvent]]:
"""
Parse an EVTC binary log file and return its components.
(Same as previous implementation, included for completeness)
"""
try:
with open(file_path, 'rb') as f:
header_data = f.read(16)
if len(header_data) < 16:
raise EOFError("File too short to contain a valid header")
magic, version, instruction_set_id, revision, padding = struct.unpack('<4s8sBHB', header_data)
if magic[:4] != b'EVTC':
raise ValueError(f"Invalid EVTC file: magic number is {magic!r}, expected 'EVTC'")
header = EvtcHeader(
magic=magic.decode('utf8'),
version=version.decode('utf8').rstrip('\x00'),
instruction_set_id=instruction_set_id,
revision=revision
)
#print(f"Header parsed: version={version}, revision={revision}, instruction_set_id={instruction_set_id}")
agent_count_data = f.read(4)
if len(agent_count_data) < 4:
raise EOFError("Unexpected EOF while reading agent count")
agent_count = struct.unpack('<I', agent_count_data)[0]
agents = []
for _ in range(agent_count):
agent_data = f.read(AGENT_SIZE)
if len(agent_data) < AGENT_SIZE:
raise EOFError("Unexpected EOF while reading agent data")
addr, prof, is_elite, toughness, concentration, healing, hitbox_width, condition, hitbox_height, name = struct.unpack(AGENT_STRUCT, agent_data)
name = name.decode('utf-8').rstrip('\x00')
if "." in name:
party = name[-1]
else:
party = 0
agents.append(EvtcAgent(
address=addr,
profession=prof,
is_elite=is_elite,
toughness=toughness,
healing=healing,
condition=condition,
concentration=concentration,
name=name,
party=party,
team="",
instid = 0
))
skill_count_data = f.read(4)
if len(skill_count_data) < 4:
raise EOFError("Unexpected EOF while reading skill count")
skill_count = struct.unpack('<I', skill_count_data)[0]
skills = []
for _ in range(skill_count):
skill_data = f.read(68)
if len(skill_data) < 68:
raise EOFError("Unexpected EOF while reading skill data")
skill_id, name = struct.unpack('<i64s', skill_data)
name = name.decode('utf-8').rstrip('\x00')
skills.append(EvtcSkill(skill_id=skill_id, name=name))
events = []
while True:
event_data = f.read(EVENT_SIZE)
if not event_data:
break
if len(event_data) < EVENT_SIZE:
raise EOFError("Unexpected EOF while reading event data")
time, src_agent, dst_agent, value, buff_dmg, overstack_value, skill_id, \
src_instid, dst_instid, src_master_instid, dst_master_instid, \
iff, buff, result, is_activation, is_buffremove, is_ninety, is_fifty, \
is_moving, is_statechange, is_flanking, is_shields, is_offcycle, \
padding = struct.unpack(EVENT_STRUCT, event_data)
events.append(EvtcEvent(
time=time,
src_agent=src_agent,
dst_agent=dst_agent,
value=value,
buff_dmg=buff_dmg,
overstack_value=overstack_value,
skill_id=skill_id,
src_instid=src_instid,
dst_instid=dst_instid,
src_master_instid=src_master_instid,
dst_master_instid=dst_master_instid,
iff=iff,
buff=buff,
result=result,
is_activation=is_activation,
is_buffremove=is_buffremove,
is_ninety=is_ninety,
is_fifty=is_fifty,
is_moving=is_moving,
is_statechange=is_statechange,
is_flanking=is_flanking,
is_shields=is_shields,
is_offcycle=is_offcycle,
pad =padding
))
return header, agents, skills, events
except FileNotFoundError:
raise FileNotFoundError(f"EVTC file not found: {file_path}")
except struct.error as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
line_number = traceback.extract_tb(exc_traceback)[-1][1]
print(f"Struct error at line {line_number}: {e}")
raise ValueError(f"Error parsing EVTC file: {str(e)}")