Path: blob/master/SonicMania/autostatic.py
338 views
import os, sys, hashlib1from typing import IO, BinaryIO, Dict, Iterable, Iterator, List, Tuple2from pathlib import Path34USE64 = False56for arg in sys.argv:7print(f"{arg}")89if len(sys.argv) < 2:10print(f"Not enough arguments supplied... Exiting...")11exit(1)1213TYPEMAP = {14"uint8": 1,15"uint16": 2,16"uint32": 4,17"int8": 1,18"int16": 2,19"int32": 4,20"bool32": 4,21"Pointer": 0,22"Vector2": 0,23"String": 0,24"Animator": 0,25"Hitbox": 0,26"SpriteFrame": 027}2829ALIASES = {30"color": "int32",31"char": "uint8"32}3334DEFINES = {35"PLAYER_COUNT": 4,36"CAMERA_COUNT": 4,37"SCREEN_COUNT": 4,38}3940objects: Dict[str, List[Tuple[str, int, int, List[int]]]] = {}41currentfile: IO42curpos = 043lastpos = 044errflag = 045path = ""4647def relseek(where):48currentfile.seek(currentfile.tell() + where, 0)4950def prepare(filename):51global currentfile, curpos, lastpos, errflag, path52currentfile = open(filename)53curpos = 054lastpos = 055errflag = 056path = filename5758def readlines() -> Iterator[str]:59global currentfile, curpos, lastpos60while currentfile.readable():61lastpos = curpos62r = currentfile.readline()63if not r:64break65curpos = currentfile.tell()66if r.strip() == "" or r.strip().startswith("//"):67continue68yield r[:-1]6970def readuntil(delim) -> str:71r = ""72c = ""73global currentfile, curpos, lastpos74lastpos = curpos75while currentfile.readable():76c = currentfile.read(1)77curpos += 178if c == delim: break79r += c80return r8182def readuntiltext():83r = ""84c = ""85global currentfile, curpos, lastpos86lastpos = curpos87while currentfile.readable():88c = currentfile.read(1)89if c.isalpha():90relseek(-1)91break92curpos += 193r += c94return r9596def infront():97r = currentfile.read(1)98relseek(-1)99return r100101def backward():102global lastpos, curpos103currentfile.seek(lastpos, 0)104curpos = lastpos105106t: List[Tuple[str, int, int, List[int]]] = []107108def deduce(delim):109global errflag, path110readuntiltext()111type = readuntil(" ")112113if infront() == "*":114type = "Pointer"115if type.endswith("*"):116type = "Pointer"117type.replace("*", "")118119if infront() == "(":120type = "Pointer"121readuntil("*") #da name!!!122123if type in ALIASES:124type = ALIASES[type]125126if type not in TYPEMAP:127print(f"UNKNOWN TYPE {type} IN {os.path.basename(path)} OBJECT STRUCT, info: {type}")128errflag = 1129return130name = readuntil(delim)131currentfile.seek(curpos - 2, 0) # relseek didn't wanna work so132133asize = 1134if currentfile.read(1) == "]":135backward()136readuntil("[")137sp = curpos138try: asize = eval(readuntil("]").strip(), DEFINES)139except:140print(f"INVALID ARRAY SIZE IN {os.path.basename(path)} OBJECT STRUCT, info: {name}")141errflag = 1142return143144name = name[:(sp - curpos - 1)]145backward()146readuntil(delim) #read just to be sure147t.append((name, tuple(TYPEMAP.keys()).index(type), asize, []))148149debugMode = False if len(sys.argv) < 5 else sys.argv[4] == "debug"150plus = len(sys.argv) < 4 or sys.argv[3] == "plus"151folder = "" if len(sys.argv) < 3 else sys.argv[2]152staticCount = 0153154try: os.makedirs(f"{folder}Static")155except: pass156157for path in Path(sys.argv[1]).rglob("*.h"):158mode = 0159prevMode = 0160objName = "[Object]"161prepare(path)162if debugMode:163print(f"Scanning '{path}'")164for l in readlines():165if errflag:166break167clnL = "".join(l.split())168169if mode == 5:170if clnL.startswith("#else") or clnL.startswith("#endif"):171mode = prevMode #return to where we left off172continue173174if (clnL.startswith("#ifMANIA_USE_PLUS") and not plus) or (clnL.startswith("#if!MANIA_USE_PLUS") and plus):175prevMode = mode176mode = 5177continue178elif (clnL.startswith("#ifMANIA_USE_PLUS") and plus) or (clnL.startswith("#if!MANIA_USE_PLUS") and not plus):179continue #ok idc180elif clnL.startswith("#endif"):181continue #ok idc182elif clnL.startswith("#else"):183prevMode = mode184mode = 5185continue #ok do care186187if mode < 5 and l.strip().startswith("struct Object"):188backward()189readuntil(" ")190objName = readuntil(" ")191readuntil("{")192if debugMode:193print(f"Found Object: '{objName}'")194195mode = 1196continue197198if mode == 1 and l.strip() == "RSDK_OBJECT":199mode = 2200t = []201continue202203if mode == 2:204if l.strip().startswith("TABLE"):205mode = 3206backward()207continue208if l.strip().startswith("STATIC"):209mode = 4210backward()211continue212if l.strip().startswith("StateMachine"):213backward()214readuntil("(")215stateName = readuntil(")")216t.append((stateName, tuple(TYPEMAP.keys()).index("Pointer"), 1, []))217readuntil("\n") #hack :LOL:218continue219if l.strip().startswith("}"):220backward()221readuntil(";")222if not objName.startswith("Object"):223print(f"{objName} NOT AN OBJECT STRUCT BUT HAS RSDK_OBJECT ({os.path.basename(path)})")224break225objName = objName[6:]226if debugMode:227print(f"{objName} FINISHED")228objects[objName] = t229t = []230mode = 0231continue #I changed this specifically because of "SPZSetup" & "SPZ2Setup" sharing a file :LOL:232backward()233deduce(";")234235if mode == 3:236backward()237readuntil("(")238deduce(",")239if errflag:240break241if t[-1][1] > tuple(TYPEMAP.keys()).index("bool32"):242print(f"INCORRECT TABLE TYPE {tuple(TYPEMAP.keys())[t[-1][1]]} ({t[-1][0]} of {os.path.basename(path)}")243break244expected = t[-1][2]245readuntil("{")246for i in range(expected - 1):247try: t[-1][3].append(eval(readuntil(",").strip(), DEFINES))248except:249print(f"INCORRECT INT IN {t[-1][0]} TABLE OR TABLE TOO SMALL IN {os.path.basename(path)}, EXPECTED: {expected}")250errflag = 1251break252if errflag:253break254try: t[-1][3].append(eval(readuntil("}").strip(), DEFINES))255except:256print(f"INCORRECT INT IN {t[-1][0]} TABLE OR TABLE TOO BIG IN {os.path.basename(path)}, EXPECTED: {expected}")257break258readuntil(";")259mode = 2260continue261262if mode == 4:263backward()264readuntil("(")265deduce(",")266if t[-1][1] > tuple(TYPEMAP.keys()).index("bool32"):267print(f"INCORRECT STATIC TYPE {tuple(TYPEMAP.keys())[t[-1][1]]} ({t[-1][0]} of {os.path.basename(path)}")268break269try: t[-1][3].append(eval(readuntil(")").strip(), DEFINES))270except:271print(f"INCORRECT STATIC INT IN {t[-1][0]} OF {os.path.basename(path)}")272break273readuntil(";")274mode = 2275continue276277for key in objects:278b = bytearray([ord('O'), ord('B'), ord('J'), 0])279hasVal = False280281hash = key282if len(sys.argv) < 5 or sys.argv[4] != "debug":283hash = hashlib.md5(key.encode("utf-8")).hexdigest()284hash = (''.join([c[1] + c[0]285for c in zip(hash[::2], hash[1::2])])).upper()286287if debugMode:288print(key, hash)289290for name, type, size, arr in objects[key]:291if arr:292hasVal = True293type |= 0x80294b.append(type)295b.extend(size.to_bytes(4, 'little')) #array size296if type >= 0x80:297b.extend(size.to_bytes(4, 'little')) #data size298bs = tuple(TYPEMAP.values())[type & (~0x80)]299for val in arr:300b.extend(val.to_bytes(bs, byteorder='little', signed=(type & (~0x80)) in range(4, 7))) #val301302if debugMode:303print(" ", tuple(TYPEMAP.keys())[type & (~0x80)], name + (f"[{size}]" if arr else ""))304305if hasVal:306if debugMode:307print(f"Building Object: '{key} [{hash}]'")308309with open(f"{folder}Static/{hash}.bin", "wb") as file:310file.write(b)311file.close()312staticCount = staticCount + 1313314315print(f"Built {staticCount} Static Objects in: {folder}Static/")316317318319320321322