Skip to content

Commit 46e76fe

Browse files
committed
converter script
1 parent 6d3a3e6 commit 46e76fe

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

converter.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from dataclasses import dataclass
2+
from struct import pack, unpack
3+
import sys
4+
import os
5+
6+
@dataclass
7+
class Action:
8+
x: float
9+
hold: bool
10+
player_2: bool
11+
12+
@dataclass
13+
class Replay:
14+
fps: float
15+
actions: list
16+
17+
def slice_per(l, n):
18+
yield from (l[i:i + n] for i in range(0, len(l), n))
19+
20+
def parse_replaybot(data: bytes) -> Replay:
21+
fps = unpack('f', data[:4])[0]
22+
actions = []
23+
for action_data in slice_per(data[4:], 6):
24+
if len(action_data) != 6:
25+
print('wtf', action_data)
26+
else:
27+
x, hold, player_2 = unpack('fbb', action_data)
28+
actions.append(Action(x, bool(hold), bool(player_2)))
29+
return Replay(fps, actions)
30+
31+
def parse_zbot(data: bytes) -> Replay:
32+
delta, speed_hack = unpack('ff', data[:8])
33+
fps = 1 / delta / speed_hack
34+
actions = []
35+
for action_data in slice_per(data[8:], 6):
36+
if len(action_data) != 6:
37+
print('wtf', action_data)
38+
else:
39+
x, hold, player_1 = unpack('fbb', action_data)
40+
# why the fuck are they 0x30 and 0x31
41+
# 0x30 = '0'
42+
# 0x31 = '1'
43+
# good job fig
44+
actions.append(Action(x, hold == 0x31, player_1 != 0x31))
45+
return Replay(fps, actions)
46+
47+
def dump_replaybot(replay: Replay) -> bytearray:
48+
data = bytearray()
49+
data.extend(pack('f', replay.fps))
50+
for action in replay.actions:
51+
data.extend(pack('fbb', action.x, action.hold, action.player_2))
52+
return data
53+
54+
def dump_zbot(replay: Replay) -> bytearray:
55+
data = bytearray()
56+
delta = 1 / replay.fps
57+
speed_hack = 1 # lol
58+
data.extend(pack('ff', delta, speed_hack))
59+
for action in replay.actions:
60+
data.extend(pack('fbb', action.x, 0x31 if action.hold else 0x30, 0x31 if (not action.player_2) else 0x30))
61+
return data
62+
63+
def parse_txt(data: bytes) -> Replay:
64+
lines = data.decode().splitlines()
65+
fps = float(lines.pop(0))
66+
actions = []
67+
for line in lines:
68+
if line:
69+
x, hold, player_2 = line.split(' ')
70+
actions.append(Action(x=float(x), hold=hold == '1', player_2=player_2 == '1'))
71+
return Replay(fps, actions)
72+
73+
def dump_txt(replay: Replay) -> str:
74+
final = ''
75+
final += f'{replay.fps}\n'
76+
for action in replay.actions:
77+
final += f'{action.x} {int(action.hold)} {int(action.player_2)}\n'
78+
return final[:-1]
79+
80+
if len(sys.argv) != 4:
81+
print('''\
82+
Usage: python converter.py (from) format (to)
83+
format can be either zbot, replaybot or txt
84+
Example:
85+
Converts from zBot to ReplayBot
86+
python converter.py "Sonic Wave.zbot" replaybot "Sonic Wave.replay"
87+
88+
Converts from zBot to txt
89+
python converter.py "Tartarus.zbot" txt "tartarus.txt"
90+
''')
91+
exit(1)
92+
93+
if os.path.exists(sys.argv[3]):
94+
print(f'{sys.argv[3]} already exists')
95+
exit(1)
96+
97+
FORMATS = {'txt', 'replaybot', 'zbot'}
98+
99+
from_format = os.path.splitext(sys.argv[1])[1][1:]
100+
101+
to = sys.argv[2]
102+
103+
if to not in FORMATS:
104+
print(f'format can only be {FORMATS}')
105+
exit(1)
106+
107+
with open(sys.argv[1], 'rb') as file:
108+
data = file.read()
109+
110+
if from_format == 'zbot':
111+
replay = parse_zbot(data)
112+
elif from_format == 'txt':
113+
replay = parse_txt(data)
114+
else:
115+
replay = parse_replaybot(data)
116+
117+
with open(sys.argv[3], 'wb') as file:
118+
if to == 'zbot':
119+
file.write(dump_zbot(replay))
120+
elif to == 'txt':
121+
file.write(dump_txt(replay).encode())
122+
elif to == 'replaybot':
123+
file.write(dump_replaybot(replay))

0 commit comments

Comments
 (0)