def decompile(func, funcNum): global localVars, funcTypes, allLocalVarTypes f = c_ast.FuncDef(funcTypes[funcNum], func.name, c_ast.DeclList(), c_ast.Statements()) #try: # If non-empty function that doesn't start with 0x2 if len(func.cmds) != 0 and func.cmds[0].command != 0x2: raise DecompilerError("Script {} doesn't start with a begin".format( func.name)) beginCommand = func.cmds[0] argc = beginCommand.parameters[0] varc = beginCommand.parameters[1] localVarTypes = getLocalVarTypes(func, varc) allLocalVarTypes.append(localVarTypes) localVars = [] localVarDecls = [] for i in range(argc): f.args.append(c_ast.Decl(localVarTypes[i], "arg{}".format(i))) localVars.append(c_ast.ID("arg{}".format(i))) for i in range(varc - argc): localVarDecls.append( c_ast.Decl(localVarTypes[i + argc], "var{}".format(i + argc))) localVars.append(c_ast.ID("var{}".format(i + argc))) s = f.statements decompileFunc(func, s) # Insert local var declarations at the beginning of the function, in order for i, decl in enumerate(localVarDecls): s.insert(i, decl) #except Exception as e: # f.statements = c_ast.Statements([c_ast.Comment("Error occurred while decompiling:\n{}".format(str(e)))]) return f
def main(args): global globalVars, globalVarDecls, funcTypes, funcNames, allLocalVarTypes, xmlInfo # Use path passed by argument if it exists, # else use the path found from getXmlInfoPath() # if no XmlInfo file is found, xmlPath will be None # MscXmlInfo(None) (aka filename=None) will be an empty MscXmlInfo object xmlPath = args.xmlPath if args.xmlPath != None else getXmlInfoPath() xmlInfo = MscXmlInfo(xmlPath) print("Analyzing...") mscFile = mscsb_disasm(args.file) print("Decompiling...") globalVarDecls = getGlobalVars(mscFile) if args.assumeCharStd: for g in xmlInfo.globals: globalVarDecls[g.id].name = g.name globalVars = [c_ast.ID(decl.name) for decl in globalVarDecls] funcs = [] funcTypes = [None for _ in range(len(mscFile))] # Rename entrypoint function to "main" mscFile.getScriptAtLocation(mscFile.entryPoint).name = 'main' funcNames = [] for script in mscFile: funcNames.append(script.name) if args.assumeCharStd: for f in xmlInfo.functions: funcNames[f.id] = f.name allLocalVarTypes = [] for i, script in enumerate(mscFile): funcs.append(decompile(script, i)) funcTypes = getFuncTypes(mscFile) for i, func in enumerate(funcs): func.type = funcTypes[i] if args.split: stdlibFuncs = [] while funcs[0].name != "main": stdlibFuncs.append(funcs.pop(0)) with open("stdlib.c", "w") as f: printC(globalVarDecls, stdlibFuncs, f) with open( args.filename if args.filename != None else (os.path.basename(os.path.splitext(args.file)[0]) + '.c'), "w") as f: print('#include "stdlib.c"', file=f) printC([], funcs, f) else: with open( args.filename if args.filename != None else (os.path.basename(os.path.splitext(args.file)[0]) + '.c'), "w") as f: printC(globalVarDecls, funcs, f)
def decompileCmd(cmd): global currentFunc, index, localVars, globalVars, funcNames, xmlInfo funcHolder = currentFunc if type(cmd) == Label: return None if type(cmd) == Command: c = cmd.command if c in [0x0, 0x1, 0x2, 0x3]: # Useless garbage return None elif c in [0x4, 0x5]: # Jumps pass elif c in [0x6, 0x8]: # Return item other, args = getArgs(1) return other + [c_ast.Return(args[0])] elif c in [0x7, 0x9]: # Return nothing return c_ast.Return() elif c in [0xA, 0xD]: # Push constant if type(cmd.parameters[0]) == ScriptRef: return c_ast.ID(str(cmd.parameters[0])) if type(cmd.parameters[0]) == float: # Check if float is equal to a math constant for m_const in math_constants: if math.isclose(cmd.parameters[0], m_const, abs_tol=0.0001): return c_ast.ID(math_constants[m_const]) return c_ast.Constant(cmd.parameters[0]) elif c == 0xB: # Push variable if cmd.parameters[0] == 0: return localVars[cmd.parameters[1]] else: return globalVars[cmd.parameters[1]] elif c in [ 0xe, 0xf, 0x10, 0x11, 0x12, 0x16, 0x17, 0x19, 0x1a, 0x1b, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x3a, 0x3b, 0x3c, 0x3d, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b ]: other, args = getArgs(2) return other + [ c_ast.BinaryOp(BINARY_OPERATIONS[c], args[1], args[0]) ] elif c in [ 0x13, 0x18, 0x2b, 0x3e ]: # Negation, bit not, logic not, etc. (Unary Op not applied to variable) other, args = getArgs(1) return other + [c_ast.UnaryOp(UNARY_OPERATIONS[c], args[0])] elif c in [0x14, 0x15, 0x3f, 0x40]: # ++, --, etc. (Unary Op applied to variable) if cmd.parameters[0] == 0: return c_ast.UnaryOp(UNARY_OPERATIONS[c], localVars[cmd.parameters[1]]) else: return c_ast.UnaryOp(UNARY_OPERATIONS[c], globalVars[cmd.parameters[1]]) elif c in [ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x41, 0x42, 0x43, 0x44, 0x45 ]: # varset, floatvarset, etc. other, args = getArgs(1) if cmd.parameters[0] == 0: variable = localVars[cmd.parameters[1]] else: variable = globalVars[cmd.parameters[1]] return other + [ c_ast.Assignment(ASSIGNMENT_OPERATIONS[c], variable, args[0]) ] elif c == 0x2c: # printf other, args = getArgs(cmd.parameters[0]) return other + [ c_ast.FuncCall("printf", c_ast.DeclList(args[::-1])) ] elif c == 0x2d: # syscall other, args = getArgs(cmd.parameters[0]) syscallInfo = xmlInfo.getSyscall(cmd.parameters[1]) if syscallInfo != None: if RepresentsInt(str(args[-1])): methodInfo = syscallInfo.getMethod(int(str(args[-1]), 0)) if methodInfo != None: return other + [ c_ast.FuncCall( c_ast.StructRef(syscallInfo.name, methodInfo.name), c_ast.DeclList(args[-2::-1])) ] return other + [ c_ast.FuncCall(syscallInfo.name, c_ast.DeclList( args[::-1])) ] return other + [ c_ast.FuncCall("sys_%X" % cmd.parameters[1], c_ast.DeclList(args[::-1])) ] elif c == 0x30: # set_main other, args = getArgs(cmd.parameters[0] + 1) if type(args[0]) == c_ast.Constant and type(args[0].value) == str: args[0] = c_ast.ID(args[0].value) return other + [ c_ast.FuncCall("set_main", c_ast.DeclList(args[0:1] + args[:0:-1])) ] #args[0:1] + args[:0:-1] is the first arg then the rest are in opposite order elif c == 0x31: # callFunc3 other, args = getArgs(cmd.parameters[0] + 1) if type(args[0]) == c_ast.Constant and type(args[0].value) == str: args[0] = c_ast.ID(args[0].value) return other + [ c_ast.FuncCall("callFunc3", c_ast.DeclList(args[0:1] + args[:0:-1])) ] elif type(cmd) == FunctionCallGroup: oldFunc = currentFunc oldIndex = index currentFunc = cmd index = len( currentFunc) - 2 # (ignore the label that will be at the end) if currentFunc[index].command != 0x2f: raise DecompilerError("Function improperly formatted") cmd = currentFunc[index] other, args = getArgs(cmd.parameters[0] + 1) while index > 0: d = decompileCmd(currentFunc[index]) if type(d) == list: other = d + other else: other.insert(0, d) index -= 1 if type(args[0]) == c_ast.ID and not args[0].name in funcNames: args[0] = c_ast.UnaryOp("*", args[0]) if type(args[0]) == c_ast.Constant and type(args[0].value) == str: args[0] = c_ast.ID(args[0].value) currentFunc = oldFunc index = oldIndex return other + [c_ast.FuncCall(args[0], c_ast.DeclList(args[:0:-1]))] elif type(cmd) == Cast: other, args = getArgs(1) return other + [c_ast.Cast(cmd.type, args[0])] elif type(cmd) == IfElseIntermediate: beforeIf, args = getArgs(1) if len(args) == 0: return beforeIf ifCondition = args[0] oldFunc = currentFunc oldIndex = index trueStatements = c_ast.Statements() currentFunc = cmd.ifCommands index = len(currentFunc) - 1 while index >= 0: d = decompileCmd(currentFunc[index]) if type(d) == list: for i in d[::-1]: if i != None: trueStatements.insert(0, i) elif d != None: trueStatements.insert(0, d) index -= 1 if cmd.elseCommands != None: currentFunc = cmd.elseCommands index = len(currentFunc) - 1 falseStatements = c_ast.Statements() while index >= 0: d = decompileCmd(currentFunc[index]) if type(d) == list: for i in d[::-1]: if i != None: falseStatements.insert(0, i) elif d != None: falseStatements.insert(0, d) index -= 1 else: falseStatements = None currentFunc = oldFunc index = oldIndex if cmd.isNot: ifCondition = c_ast.UnaryOp("!", ifCondition) return beforeIf + [ c_ast.If(ifCondition, trueStatements, falseStatements) ] elif type(cmd) == WhileIntermediate: oldFunc = currentFunc oldIndex = index loopStatements = c_ast.Statements() currentFunc = cmd.commands index = len(currentFunc) other, condition = getArgs(1) index -= 1 condition = condition[0] for i in other[::-1]: loopStatements.insert(0, i) while index >= 0: d = decompileCmd(currentFunc[index]) if type(d) == list: for i in d[::-1]: if i != None: loopStatements.insert(0, i) elif d != None: loopStatements.insert(0, d) index -= 1 currentFunc = oldFunc index = oldIndex if not cmd.isIfNot: condition = c_ast.UnaryOp("!", condition) if cmd.isDoWhile: return c_ast.DoWhile(condition, loopStatements) else: return c_ast.While(condition, loopStatements) elif type(cmd) == c_ast.Break: return cmd