Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-Mania-Decompilation
Path: blob/master/SonicMania/autostatic.py
338 views
1
import os, sys, hashlib
2
from typing import IO, BinaryIO, Dict, Iterable, Iterator, List, Tuple
3
from pathlib import Path
4
5
USE64 = False
6
7
for arg in sys.argv:
8
print(f"{arg}")
9
10
if len(sys.argv) < 2:
11
print(f"Not enough arguments supplied... Exiting...")
12
exit(1)
13
14
TYPEMAP = {
15
"uint8": 1,
16
"uint16": 2,
17
"uint32": 4,
18
"int8": 1,
19
"int16": 2,
20
"int32": 4,
21
"bool32": 4,
22
"Pointer": 0,
23
"Vector2": 0,
24
"String": 0,
25
"Animator": 0,
26
"Hitbox": 0,
27
"SpriteFrame": 0
28
}
29
30
ALIASES = {
31
"color": "int32",
32
"char": "uint8"
33
}
34
35
DEFINES = {
36
"PLAYER_COUNT": 4,
37
"CAMERA_COUNT": 4,
38
"SCREEN_COUNT": 4,
39
}
40
41
objects: Dict[str, List[Tuple[str, int, int, List[int]]]] = {}
42
currentfile: IO
43
curpos = 0
44
lastpos = 0
45
errflag = 0
46
path = ""
47
48
def relseek(where):
49
currentfile.seek(currentfile.tell() + where, 0)
50
51
def prepare(filename):
52
global currentfile, curpos, lastpos, errflag, path
53
currentfile = open(filename)
54
curpos = 0
55
lastpos = 0
56
errflag = 0
57
path = filename
58
59
def readlines() -> Iterator[str]:
60
global currentfile, curpos, lastpos
61
while currentfile.readable():
62
lastpos = curpos
63
r = currentfile.readline()
64
if not r:
65
break
66
curpos = currentfile.tell()
67
if r.strip() == "" or r.strip().startswith("//"):
68
continue
69
yield r[:-1]
70
71
def readuntil(delim) -> str:
72
r = ""
73
c = ""
74
global currentfile, curpos, lastpos
75
lastpos = curpos
76
while currentfile.readable():
77
c = currentfile.read(1)
78
curpos += 1
79
if c == delim: break
80
r += c
81
return r
82
83
def readuntiltext():
84
r = ""
85
c = ""
86
global currentfile, curpos, lastpos
87
lastpos = curpos
88
while currentfile.readable():
89
c = currentfile.read(1)
90
if c.isalpha():
91
relseek(-1)
92
break
93
curpos += 1
94
r += c
95
return r
96
97
def infront():
98
r = currentfile.read(1)
99
relseek(-1)
100
return r
101
102
def backward():
103
global lastpos, curpos
104
currentfile.seek(lastpos, 0)
105
curpos = lastpos
106
107
t: List[Tuple[str, int, int, List[int]]] = []
108
109
def deduce(delim):
110
global errflag, path
111
readuntiltext()
112
type = readuntil(" ")
113
114
if infront() == "*":
115
type = "Pointer"
116
if type.endswith("*"):
117
type = "Pointer"
118
type.replace("*", "")
119
120
if infront() == "(":
121
type = "Pointer"
122
readuntil("*") #da name!!!
123
124
if type in ALIASES:
125
type = ALIASES[type]
126
127
if type not in TYPEMAP:
128
print(f"UNKNOWN TYPE {type} IN {os.path.basename(path)} OBJECT STRUCT, info: {type}")
129
errflag = 1
130
return
131
name = readuntil(delim)
132
currentfile.seek(curpos - 2, 0) # relseek didn't wanna work so
133
134
asize = 1
135
if currentfile.read(1) == "]":
136
backward()
137
readuntil("[")
138
sp = curpos
139
try: asize = eval(readuntil("]").strip(), DEFINES)
140
except:
141
print(f"INVALID ARRAY SIZE IN {os.path.basename(path)} OBJECT STRUCT, info: {name}")
142
errflag = 1
143
return
144
145
name = name[:(sp - curpos - 1)]
146
backward()
147
readuntil(delim) #read just to be sure
148
t.append((name, tuple(TYPEMAP.keys()).index(type), asize, []))
149
150
debugMode = False if len(sys.argv) < 5 else sys.argv[4] == "debug"
151
plus = len(sys.argv) < 4 or sys.argv[3] == "plus"
152
folder = "" if len(sys.argv) < 3 else sys.argv[2]
153
staticCount = 0
154
155
try: os.makedirs(f"{folder}Static")
156
except: pass
157
158
for path in Path(sys.argv[1]).rglob("*.h"):
159
mode = 0
160
prevMode = 0
161
objName = "[Object]"
162
prepare(path)
163
if debugMode:
164
print(f"Scanning '{path}'")
165
for l in readlines():
166
if errflag:
167
break
168
clnL = "".join(l.split())
169
170
if mode == 5:
171
if clnL.startswith("#else") or clnL.startswith("#endif"):
172
mode = prevMode #return to where we left off
173
continue
174
175
if (clnL.startswith("#ifMANIA_USE_PLUS") and not plus) or (clnL.startswith("#if!MANIA_USE_PLUS") and plus):
176
prevMode = mode
177
mode = 5
178
continue
179
elif (clnL.startswith("#ifMANIA_USE_PLUS") and plus) or (clnL.startswith("#if!MANIA_USE_PLUS") and not plus):
180
continue #ok idc
181
elif clnL.startswith("#endif"):
182
continue #ok idc
183
elif clnL.startswith("#else"):
184
prevMode = mode
185
mode = 5
186
continue #ok do care
187
188
if mode < 5 and l.strip().startswith("struct Object"):
189
backward()
190
readuntil(" ")
191
objName = readuntil(" ")
192
readuntil("{")
193
if debugMode:
194
print(f"Found Object: '{objName}'")
195
196
mode = 1
197
continue
198
199
if mode == 1 and l.strip() == "RSDK_OBJECT":
200
mode = 2
201
t = []
202
continue
203
204
if mode == 2:
205
if l.strip().startswith("TABLE"):
206
mode = 3
207
backward()
208
continue
209
if l.strip().startswith("STATIC"):
210
mode = 4
211
backward()
212
continue
213
if l.strip().startswith("StateMachine"):
214
backward()
215
readuntil("(")
216
stateName = readuntil(")")
217
t.append((stateName, tuple(TYPEMAP.keys()).index("Pointer"), 1, []))
218
readuntil("\n") #hack :LOL:
219
continue
220
if l.strip().startswith("}"):
221
backward()
222
readuntil(";")
223
if not objName.startswith("Object"):
224
print(f"{objName} NOT AN OBJECT STRUCT BUT HAS RSDK_OBJECT ({os.path.basename(path)})")
225
break
226
objName = objName[6:]
227
if debugMode:
228
print(f"{objName} FINISHED")
229
objects[objName] = t
230
t = []
231
mode = 0
232
continue #I changed this specifically because of "SPZSetup" & "SPZ2Setup" sharing a file :LOL:
233
backward()
234
deduce(";")
235
236
if mode == 3:
237
backward()
238
readuntil("(")
239
deduce(",")
240
if errflag:
241
break
242
if t[-1][1] > tuple(TYPEMAP.keys()).index("bool32"):
243
print(f"INCORRECT TABLE TYPE {tuple(TYPEMAP.keys())[t[-1][1]]} ({t[-1][0]} of {os.path.basename(path)}")
244
break
245
expected = t[-1][2]
246
readuntil("{")
247
for i in range(expected - 1):
248
try: t[-1][3].append(eval(readuntil(",").strip(), DEFINES))
249
except:
250
print(f"INCORRECT INT IN {t[-1][0]} TABLE OR TABLE TOO SMALL IN {os.path.basename(path)}, EXPECTED: {expected}")
251
errflag = 1
252
break
253
if errflag:
254
break
255
try: t[-1][3].append(eval(readuntil("}").strip(), DEFINES))
256
except:
257
print(f"INCORRECT INT IN {t[-1][0]} TABLE OR TABLE TOO BIG IN {os.path.basename(path)}, EXPECTED: {expected}")
258
break
259
readuntil(";")
260
mode = 2
261
continue
262
263
if mode == 4:
264
backward()
265
readuntil("(")
266
deduce(",")
267
if t[-1][1] > tuple(TYPEMAP.keys()).index("bool32"):
268
print(f"INCORRECT STATIC TYPE {tuple(TYPEMAP.keys())[t[-1][1]]} ({t[-1][0]} of {os.path.basename(path)}")
269
break
270
try: t[-1][3].append(eval(readuntil(")").strip(), DEFINES))
271
except:
272
print(f"INCORRECT STATIC INT IN {t[-1][0]} OF {os.path.basename(path)}")
273
break
274
readuntil(";")
275
mode = 2
276
continue
277
278
for key in objects:
279
b = bytearray([ord('O'), ord('B'), ord('J'), 0])
280
hasVal = False
281
282
hash = key
283
if len(sys.argv) < 5 or sys.argv[4] != "debug":
284
hash = hashlib.md5(key.encode("utf-8")).hexdigest()
285
hash = (''.join([c[1] + c[0]
286
for c in zip(hash[::2], hash[1::2])])).upper()
287
288
if debugMode:
289
print(key, hash)
290
291
for name, type, size, arr in objects[key]:
292
if arr:
293
hasVal = True
294
type |= 0x80
295
b.append(type)
296
b.extend(size.to_bytes(4, 'little')) #array size
297
if type >= 0x80:
298
b.extend(size.to_bytes(4, 'little')) #data size
299
bs = tuple(TYPEMAP.values())[type & (~0x80)]
300
for val in arr:
301
b.extend(val.to_bytes(bs, byteorder='little', signed=(type & (~0x80)) in range(4, 7))) #val
302
303
if debugMode:
304
print(" ", tuple(TYPEMAP.keys())[type & (~0x80)], name + (f"[{size}]" if arr else ""))
305
306
if hasVal:
307
if debugMode:
308
print(f"Building Object: '{key} [{hash}]'")
309
310
with open(f"{folder}Static/{hash}.bin", "wb") as file:
311
file.write(b)
312
file.close()
313
staticCount = staticCount + 1
314
315
316
print(f"Built {staticCount} Static Objects in: {folder}Static/")
317
318
319
320
321
322