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