From d367252091cdb2134e5f15733e4e55a4949301b6 Mon Sep 17 00:00:00 2001 From: Guus Waals <_@guusw.nl> Date: Fri, 20 Sep 2024 03:34:47 +0800 Subject: [PATCH] Rewrite java scripts --- .gitmodules | 3 + game_re/third_party/spdlog | 1 + scripts/Decompile.java | 523 +++++++++++++++++ scripts/DecompileC.java | 660 ---------------------- scripts/ExportData.java | 51 ++ scripts/NewScript.java | 15 + scripts/SanitizeGlobalSymbols.java | 103 ++++ scripts/re3lib/DataTypeWriter.java | 842 ++++++++++++++++++++++++++++ scripts/re3lib/DecompileCache.java | 33 ++ scripts/re3lib/PCallTracer.java | 87 +++ scripts/re3lib/RecompileConfig.java | 60 ++ scripts/re3lib/Utils.java | 70 +++ 12 files changed, 1788 insertions(+), 660 deletions(-) create mode 100644 .gitmodules create mode 160000 game_re/third_party/spdlog create mode 100644 scripts/Decompile.java delete mode 100644 scripts/DecompileC.java create mode 100644 scripts/ExportData.java create mode 100644 scripts/NewScript.java create mode 100644 scripts/SanitizeGlobalSymbols.java create mode 100644 scripts/re3lib/DataTypeWriter.java create mode 100644 scripts/re3lib/DecompileCache.java create mode 100644 scripts/re3lib/PCallTracer.java create mode 100644 scripts/re3lib/RecompileConfig.java create mode 100644 scripts/re3lib/Utils.java diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9f144335 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "game_re/third_party/spdlog"] + path = game_re/third_party/spdlog + url = https://github.com/gabime/spdlog.git diff --git a/game_re/third_party/spdlog b/game_re/third_party/spdlog new file mode 160000 index 00000000..bdd1dff3 --- /dev/null +++ b/game_re/third_party/spdlog @@ -0,0 +1 @@ +Subproject commit bdd1dff3788ebfe520f48f9ad216c60da6dd8f00 diff --git a/scripts/Decompile.java b/scripts/Decompile.java new file mode 100644 index 00000000..e1c528e7 --- /dev/null +++ b/scripts/Decompile.java @@ -0,0 +1,523 @@ +// Script to export decompiled C code from Ghidra +// @category _Reman3 +// @menupath Tools.Reman3.Decompile All + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Arrays; +import java.util.Dictionary; + +import ghidra.app.decompiler.ClangLine; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.decompiler.PrettyPrinter; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.AbstractStringDataType; +import ghidra.program.model.data.BitFieldDataType; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.DataTypeManager; +import re3lib.DataTypeWriter; +import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.data.ProgramBasedDataTypeManager; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.TypedefDataType; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.HighFunction; +import ghidra.program.model.pcode.HighSymbol; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.Symbol; +import ghidra.util.task.TaskMonitor; +import re3lib.*; + +public class Decompile extends GhidraScript { + // Auto rename invalid symbols + private static final boolean AUTO_RENAME_SYMBOLS = true; + + private static final HashSet ONLY_SYMBOLS = new HashSet<>(Arrays.asList( + "r3_main", // + "_strrchr")); + + static final boolean BUILD_BLACKLIST = true; + + HashSet
functionAddrBlackList = new HashSet<>(); + + boolean shouldDecompileFunction(Function function) { + if (ONLY_SYMBOLS != null && !ONLY_SYMBOLS.contains(function.getName())) { + return false; + } + return !functionAddrBlackList.contains(function.getEntryPoint()); + } + + void buildFunctionBlacklist() { + functionAddrBlackList = Utils.loadFunctionBlacklist(RecompileConfig.INSTANCE.functionBlacklistPath); + + if (BUILD_BLACKLIST) { + boolean modified = false; + + Iterator functionsIt = currentProgram.getFunctionManager().getFunctions(true).iterator(); + while (functionsIt.hasNext()) { + Function function = functionsIt.next(); + if (functionAddrBlackList.contains(function.getEntryPoint())) { + continue; + } + + String comment = function.getComment(); + boolean isIgnoredFunction = false; + if (comment != null && comment.contains("Library Function")) { + println("Adding library function " + function.getName() + " to blacklist"); + println("ac:" + functionAddrBlackList.size() + " jj:" + + functionAddrBlackList.contains(function.getEntryPoint()) + " " + function.getEntryPoint()); + isIgnoredFunction = true; + } + + if (function.getName().startsWith("crt_")) { + println("Adding crt function " + function.getName() + " to blacklist"); + isIgnoredFunction = true; + } + + if (isIgnoredFunction) { + // Decompile and trace + PCallTracer tracer = new PCallTracer(); + tracer.setBlacklist(functionAddrBlackList); + tracer.traceCalls(function); + for (Function f : tracer.out) { + println(" Adding " + f.getName() + " to blacklist"); + functionAddrBlackList.add(f.getEntryPoint()); + modified = true; + } + } + } + + if (modified) { + Utils.saveFunctionBlacklist(functionAddrBlackList, RecompileConfig.INSTANCE.functionBlacklistPath); + } + } + } + + String escapeCString(String str) { + str = str.replace("\\", "\\\\"); + str = str.replace("\"", "\\\""); + return str; + } + + String readCString(Address addr, int maxLen) throws Exception { + StringBuilder sb = new StringBuilder(); + int ofs = 0; + while (true) { + Address read = addr.add(ofs++); + // println("Reading: " + read); + byte b = currentProgram.getMemory().getByte(read); + // println("Read: " + b); + if (b == 0 || ofs >= maxLen) { + break; + } + sb.append((char) b); + } + if (sb.length() > 0) { + // println("STR \"" + sb.toString() + "\""); + } + return sb.toString(); + } + + public class GlobalRec { + public HighSymbol highSymbol; + public Address address; + // public boolean isFullyDefined; + + public GlobalRec(HighSymbol highSymbol, Address address) { + this.highSymbol = highSymbol; + this.address = address; + // this.isFullyDefined = isFullyDefined; + } + }; + + void decompileFunction(Hashtable outGlobalSymbols, Function function) + throws Exception { + String fileName = sanitizeFunctionName(function.getName()) + ".cxx"; + + File f1 = new File(RecompileConfig.INSTANCE.dirDecompFix, fileName); + if (f1.exists()) { + println("Func " + function.getName() + " skipped (gh_fix)"); + return; + } + + File f0 = new File(RecompileConfig.INSTANCE.dirDecompAuto, fileName); + if (f0.exists()) { + f0.delete(); + } + + println("Processing " + function.getName() + " => " + f0.toString()); + + DecompileResults decompRes = RecompileConfig.INSTANCE.decompCache.getOrInsert(function); + try (PrintWriter writer2 = new PrintWriter(f0, "UTF-8")) { + writer2.println("// AUTO-GENERATED FILE, MOVE TO 'gh_fix' FOLDER PREVENT OVERWRITING!!!!! "); + writer2.println(); + writer2.println("#include "); + writer2.println("#include \"../gh_global.h\""); + writer2.println(); + + // decompRes.get + HighFunction highFunction = decompRes.getHighFunction(); + + // Remap for dynamic symbols + // Dictionary symbolRemap = new Hashtable<>(); + + HashSet headers = new HashSet<>(); + StringWriter codeWriter = new StringWriter(); + + PrettyPrinter pp = new PrettyPrinter(decompRes.getFunction(), decompRes.getCCodeMarkup(), null); + Iterator lines = pp.getLines().iterator(); + while (lines.hasNext()) { + ClangLine line = lines.next(); + for (int i = 0; i < line.getIndent(); i++) { + codeWriter.write(' '); + } + for (int t = 0; t < line.getNumTokens(); t++) { + ClangToken token = line.getToken(t); + HighSymbol gsym = token.getHighSymbol(highFunction); + if (gsym != null) { + var symStorage = gsym.getStorage(); + var sym = gsym.getSymbol(); + + Address address; + if (symStorage.isUnassignedStorage()) { + address = sym.getAddress(); + } else { + address = gsym.getStorage().getMinAddress(); + } + + if (address.isMemoryAddress()) { + // println("Memory: " + token.toString() + " - " + gsym.getName() + " - " + + // address); + // boolean defined = address.isConstantAddress(); + // Symbol symAtAddr = getSymbolAt(address); + // if (defined && symAtAddr != null) { + // } else { + // // println("NOT FULLY DEFINED: " + address + " - " + symAtAddr); + // // defined = false; + // } + + outGlobalSymbols.put(gsym.getName(), new GlobalRec(gsym, address)); + } else { + // println("Unknown: " + token.toString() + " - " + gsym.getName() + " - " + + // address); + } + + // println("CONST: " + token.toString() + " - " + gsym.getName()); + // println( + // "Token: " + token.toString() + " - " + gsym.getName() + " - " + + // symStorage.getSerializationString()); + } + PcodeOp op = token.getPcodeOp(); + if (op != null && op.getOpcode() == PcodeOp.CALL) { + // println("PcodeOp: " + op.toString() + " - " + op.getInput(0).toString()); + Varnode target = op.getInput(0); + if (target.isAddress()) { + Address callAddr = target.getAddress(); + Function calledFunction = getFunctionAt(callAddr); + if (calledFunction != null) { + if (!functionAddrBlackList.contains(calledFunction.getEntryPoint())) { + // println("Adding header: " + calledFunction + " / " + + // calledFunction.getSignature().getPrototypeString(true)); + headers.add("extern " + calledFunction.getSignature().getPrototypeString(true) + + "; // " + calledFunction.getEntryPoint() + " // " + calledFunction.getName()); + } + } + } + } + codeWriter.write(token.toString()); + } + codeWriter.write('\n'); + } + + for (String header : headers) { + writer2.println(header); + } + writer2.println(); + writer2.println("// " + function.getEntryPoint()); + writer2.print(codeWriter.toString()); + writer2.println(); + + // Iterator it = decompRes.getCCodeMarkup().tokenIterator(true); + // int ln = 0; + // while(it.hasNext()) { + // ClangToken token = it.next(); + // ClangLine line = token.getLineParent(); + // while (line != null && ln < line.getLineNumber()) { + // writer2.println(); + // ln++; + // } + // writer2.print(token.toString()); + // } + + // Collect referenced global symbols + // Iterator smyIt = highFunction.getGlobalSymbolMap().getSymbols(); + // while (smyIt.hasNext()) { + // HighSymbol gsym = smyIt.next(); + + // Address addr = gsym.getSymbol().getAddress(); + // println("FunctionSym " + addr + " " + gsym.getName() + " " + + // gsym.getStorage().getMinAddress()); + // println(" IsMem: " + gsym.getStorage().isMemoryStorage() + " " + + // gsym.getStorage().getSerializationString()); + + // if (outGlobalSymbols.containsKey(gsym.getName())) + // continue; + // outGlobalSymbols.put(gsym.getName(), gsym); + // } + } + } + + void headerGuardPre(PrintWriter writer, String tag) { + writer.println("#ifndef GH_GENERATED_" + tag + "_H"); + writer.println("#define GH_GENERATED_" + tag + "_H"); + writer.println(); + } + + void headerGuardPost(PrintWriter writer, String tag) { + writer.println("#endif // GH_GENERATED_" + tag + "_H"); + } + + class DependencyTypeDumper { + HashSet types = new HashSet<>(); + DataTypeManager dtm; + TaskMonitor taskMonitor; + + public DependencyTypeDumper(DataTypeManager dtm) { + this.dtm = dtm; + this.taskMonitor = monitor; + } + + void visit(DataType dataType, PrintWriter writer) throws Exception { + // If not already written + if (types.contains(dataType)) { + println("Visiting: " + dataType.getName()); + types.remove(dataType); + + // Write dependencies, and then write self + if (dataType instanceof Structure) { + Structure struct = (Structure) dataType; + for (DataTypeComponent component : struct.getComponents()) { + DataType dt = component.getDataType(); + if (dt instanceof Structure) { + println("Dependency: " + dt.getName()); + visit((Structure) dt, writer); + } + } + } + writeNoDeps(dataType, writer); + } + } + + void writeNoDeps(DataType dt, PrintWriter writer) throws Exception { + // Check + // https://github.com/NationalSecurityAgency/ghidra/blob/17c93909bbf99f7f98dbf5737b38d8dd2c01bef0/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/exporter/CppExporter.java#L401 + + // DataTypeWriter dtw = new DataTypeWriter(dtm, writer); + // dtw.blacklistedTypes = + // dtw.write(new DataType[] { dt }, taskMonitor); + + // if (dt instanceof Structure) { + // Structure struct = (Structure) dt; + // writer.append("typedef struct " + struct.getName() + " {\n"); + // for (DataTypeComponent component : struct.getComponents()) { + // if (component.isBitFieldComponent()) { + // BitFieldDataType bfdt = (BitFieldDataType) component.getDataType(); + // writer.append( + // " " + bfdt.getDisplayName() + " " + component.getDefaultFieldName() + " : " + + // bfdt.getBitSize() + ";\n"); + // } else { + // writer.append( + // " " + component.getDataType().getDisplayName() + " " + + // component.getDefaultFieldName() + ";\n"); + // } + // writer.append("} " + struct.getDisplayName() + " ;\n"); + // writer.append("\n"); + // } else if (dt instanceof TypedefDataType) { + // TypedefDataType typedef = (TypedefDataType) dt; + // writer.append("typedef " + typedef.getDataType().getDisplayName() + " " + + // typedef.getName() + ";\n"); + // writer.append("\n"); + // } else { + // throw new Exception("Unsupported type: " + dt.getDisplayName()); + // } + } + + void writeAll(PrintWriter writer) throws Exception { + while (types.size() > 0) { + DataType first = types.iterator().next(); + visit(first, writer); + } + } + }; + + void dumpStructureTypes() throws Exception { + ProgramBasedDataTypeManager dtm = currentProgram.getDataTypeManager(); + + HashSet typeBlacklist = Utils.loadStructBlacklist(RecompileConfig.INSTANCE.typeBlacklistPath); + + if (typeBlacklist == null) { + println("Building struct blacklist from existing data types"); + typeBlacklist = new HashSet<>(); + Iterator it = dtm.getAllDataTypes(); + while (it.hasNext()) { + DataType dt = it.next(); + if (dt instanceof Structure || dt instanceof TypedefDataType) { + typeBlacklist.add(dt.getDisplayName()); + } + } + Utils.saveStructBlacklist(typeBlacklist, RecompileConfig.INSTANCE.typeBlacklistPath); + } + + List filteredTypes = new ArrayList<>(); + Iterator it = dtm.getAllDataTypes(); + // DependencyTypeDumper dumper = new DependencyTypeDumper(dtm); + while (it.hasNext()) { + DataType dt = it.next(); + if (dt instanceof Structure || dt instanceof TypedefDataType) { + if (typeBlacklist.contains(dt.getDisplayName())) + continue; + println("Adding: " + dt.getDisplayName() + " - " + dt.getClass().getSimpleName()); + filteredTypes.add(dt); + } + + // Structure struct = (Structure) dt; + // dumper.types.add(struct); + // } else if (dt instanceof TypedefDataType) { + // TypedefDataType typedef = (TypedefDataType) dt; + // dumper.types.add(typedef); + // } + } + + try (PrintWriter writer = new PrintWriter(new File(RecompileConfig.INSTANCE.outputDir, "gh_structs.h"), "UTF-8")) { + headerGuardPre(writer, "STRUCTS"); + writer.println("// AUTO-GENERATED FILE "); + writer.println("#include "); + // dumper.writeAll(writer); + + DataTypeWriter dtw = new DataTypeWriter(dtm, writer); + dtw.blacklistedTypes = typeBlacklist; + dtw.write(filteredTypes, monitor); + + headerGuardPost(writer, "STRUCTS"); + } + } + + void dumpGlobals(Hashtable globalSymbols) throws Exception { + File globalSymbolsListH = new File(RecompileConfig.INSTANCE.outputDir, "gh_global.h"); + PrintWriter hwriter = new PrintWriter(globalSymbolsListH, "UTF-8"); + hwriter.println("// AUTO-GENERATED FILE "); + headerGuardPre(hwriter, "GLOBALS"); + hwriter.println("#include "); + hwriter.println(); + + File globalSymbolsListC = new File(RecompileConfig.INSTANCE.outputDir, "gh_global.cxx"); + PrintWriter cwriter = new PrintWriter(globalSymbolsListC, "UTF-8"); + cwriter.println("// AUTO-GENERATED FILE "); + cwriter.println("#include "); + hwriter.println(); + + for (GlobalRec sym : globalSymbols.values()) { + HighSymbol highSym = sym.highSymbol; + DataType dt = highSym.getDataType(); + String dataType = dt.getDisplayName(); + String name = highSym.getName(); + String sanitizedName = sanitizeFunctionName(highSym.getName()); + if (!sanitizedName.equals(highSym.getName())) { + println("Invalid global symbol name: " + highSym.getName() + " - " + + highSym.getHighFunction().getFunction().getName()); + } else { + Address addr = sym.address; + // println("Symbol: " + symbol + " Addr: " + addr + " Size:" + symSize + " " + + // storage.getSerializationString()); + try { + String initBlk = " = "; + boolean fullyDefinedType = false; + if (dt instanceof AbstractStringDataType) { + AbstractStringDataType sdt = (AbstractStringDataType) dt; + dataType = "const char*"; + // String type + initBlk += "\"" + escapeCString(readCString(addr, 2048)) + "\""; + fullyDefinedType = true; + } else if (dt instanceof PointerDataType) { + PointerDataType pdt = (PointerDataType) dt; + DataType baseType = pdt.getDataType(); + dataType = baseType.getDisplayName() + "*"; + initBlk += "(" + dataType + ")&GH_MEM(0x" + addr + ")"; + fullyDefinedType = true; + } + if (fullyDefinedType) { + hwriter.println("extern " + dataType + " " + name + "; // " + addr); + cwriter.println(dataType + " " + name + initBlk + "; // " + addr); + } else { + String refTypeStr = dt.getDisplayName() + "&"; + hwriter.println("extern " + refTypeStr + " " + name + "; // " + addr); + cwriter.println(dataType + " " + name + "= (" + refTypeStr + ") GH_MEM(0x" + addr + ");"); + } + } catch (Exception e) { + println("Error processing global symbol: " + e); + println("Symbol: " + highSym.getName() + " - " + addr + " - " + + highSym.getHighFunction().getFunction().getName()); + } + } + } + + headerGuardPost(hwriter, "GLOBALS"); + hwriter.close(); + cwriter.close(); + } + + void decompileAll(List functions) throws Exception { + Hashtable globalSymbols = new Hashtable<>(); + + for (Function function : functions) { + decompileFunction(globalSymbols, function); + } + + dumpStructureTypes(); + dumpGlobals(globalSymbols); + } + + @Override + public void run() throws Exception { + if (currentProgram == null) { + return; + } + + RecompileConfig.INSTANCE = new RecompileConfig(this); + + if (!new File(RecompileConfig.INSTANCE.outputDir).exists()) { + throw new Exception("Output directory does not exist: " + RecompileConfig.INSTANCE.outputDir); + } + + buildFunctionBlacklist(); + + List functions = new ArrayList<>(); + + Iterator functionsIt = currentProgram.getFunctionManager().getFunctions(true).iterator(); + while (functionsIt.hasNext()) { + Function function = functionsIt.next(); + if (!shouldDecompileFunction(function)) { + continue; + } + + functions.add(function); + } + + decompileAll(functions); + } + + String sanitizeFunctionName(String name) { + return name.replaceAll("[^a-zA-Z0-9_]", "_"); + } +} diff --git a/scripts/DecompileC.java b/scripts/DecompileC.java deleted file mode 100644 index 4cde493c..00000000 --- a/scripts/DecompileC.java +++ /dev/null @@ -1,660 +0,0 @@ -/* ### - */ -// Script to export decompiled C code from Ghidra - -// -//@category Examples.Demangler -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Scanner; -import java.util.Arrays; -import java.util.Dictionary; - -import ghidra.app.cmd.label.AddLabelCmd; -import ghidra.app.decompiler.ClangLine; -import ghidra.app.decompiler.ClangMarkup; -import ghidra.app.decompiler.ClangNode; -import ghidra.app.decompiler.ClangToken; -import ghidra.app.decompiler.ClangTokenGroup; -import ghidra.app.decompiler.DecompInterface; -import ghidra.app.decompiler.DecompileResults; -import ghidra.app.decompiler.DecompiledFunction; -import ghidra.app.decompiler.PrettyPrinter; -import ghidra.app.script.GhidraScript; -import ghidra.docking.settings.Settings; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; -import ghidra.program.model.data.AbstractStringDataType; -import ghidra.program.model.data.BuiltInDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataOrganization; -import ghidra.program.model.data.DataOrganizationImpl; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeComponent; -import ghidra.program.model.data.PointerDataType; -import ghidra.program.model.data.ProgramBasedDataTypeManager; -import ghidra.program.model.data.SourceArchive; -import ghidra.program.model.data.StringDataInstance; -import ghidra.program.model.data.Structure; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Variable; -import ghidra.program.model.listing.VariableStorage; -import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.model.mem.MemoryBufferImpl; -import ghidra.program.model.pcode.HighFunction; -import ghidra.program.model.pcode.HighSymbol; -import ghidra.program.model.pcode.PcodeOp; -import ghidra.program.model.pcode.PcodeOpAST; -import ghidra.program.model.pcode.Varnode; -import ghidra.program.model.symbol.NameTransformer; -import ghidra.program.model.symbol.SourceType; -import ghidra.program.model.symbol.Symbol; -import ghidra.program.model.symbol.SymbolTable; -import ghidra.program.model.symbol.SymbolType; - -public class DecompileC extends GhidraScript { - public class DecompileCache { - private static final int TIMEOUT = 10000; - - Hashtable cache = new Hashtable<>(); - DecompInterface decomp; - - public DecompileCache(DecompInterface decomp) { - this.decomp = decomp; - } - - public DecompileResults get(Function function) { - return cache.get(function); - } - - public DecompileResults getOrInsert(Function function) { - DecompileResults res = cache.get(function); - if (res == null) { - res = decomp.decompileFunction(function, TIMEOUT, monitor); - cache.put(function, res); - } - return res; - } - } - - public class PCallTracer { - public class QueueItem { - Function function; - int depth; - - QueueItem(Function function, int depth) { - this.function = function; - this.depth = depth; - } - } - - public List out = new ArrayList<>(); - public boolean trace = false; - List queue = new ArrayList<>(); - HashSet
visited = new HashSet<>(); - DecompileCache decomp; - - PCallTracer(DecompileCache decomp) { - this.decomp = decomp; - } - - public void setBlacklist(HashSet
blacklist) { - this.visited = new HashSet<>(blacklist); - } - - void visit(HighFunction highFunction, int depth) { - Iterator opIter = highFunction.getPcodeOps(); - while (opIter.hasNext()) { - PcodeOpAST op = opIter.next(); - if (op.getOpcode() == PcodeOp.CALL) { - Varnode target = op.getInput(0); - if (target.isAddress()) { - Address callAddr = target.getAddress(); - Function calledFunction = getFunctionAt(callAddr); - if (calledFunction == null) { - - println("PCallTracer, called function not found: " + op.toString() + " - " - + highFunction.getFunction().getName()); - continue; - } - if (!visited.contains(calledFunction.getEntryPoint())) { - queue.add(new QueueItem(calledFunction, depth + 1)); - } - } - } - } - } - - void visit(Function function, int depth) { - if (!visited.contains(function.getEntryPoint())) { - visited.add(function.getEntryPoint()); - if (trace) { - println("PCallTracer, visiting " + function.getName() + " (depth:" + depth + ")"); - } - DecompileResults decompRes = decomp.getOrInsert(function); - visit(decompRes.getHighFunction(), depth); - out.add(function); - } - } - - public void traceCalls(Function inFunction) { - queue.add(new QueueItem(inFunction, 0)); - while (queue.size() > 0) { - QueueItem item = queue.remove(0); - visit(item.function, item.depth); - } - } - } - - private static final String OUTPUT_DIR = "game_re"; - - // The static memory block - private Address staticMemoryBlockStart; - private Address staticMemoryBlockEnd; - - private DecompileCache decompCache; - - // Auto rename invalid symbols - private static final boolean AUTO_RENAME_SYMBOLS = true; - - private static final HashSet ONLY_SYMBOLS = new HashSet<>(Arrays.asList( - "r3_main", // - "_strrchr")); - - File rootDir; - File outputDir; - File dirDecompAuto; - File dirDecompFix; - - HashSet
functionAddrBlackList = new HashSet<>(); - - void loadFunctionBlacklist() { - functionAddrBlackList.clear(); - File blacklistFile = new File(outputDir, "blacklist.txt"); - try (Scanner scanner = new Scanner(blacklistFile)) { - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - // Strip comment - String line1 = line.split("//")[0].trim(); - // Deserialize address - Address addr = currentProgram.getAddressFactory().getAddress(line1); - functionAddrBlackList.add(addr); - } - println("Loaded blacklist with " + functionAddrBlackList.size() + " entries"); - } catch (FileNotFoundException e) { - println("No blacklist found"); - return; - } - - // for (Address a : functionAddrBlackList) { - // Function fn = getFunctionAt(a); - // println("In blacklist: " + a + " (" + (fn != null ? fn.getName() : "unknown") - // + ")"); - // } - } - - void saveFunctionBlacklist() { - println("Saving blacklist"); - - File blacklistFile = new File(outputDir, "blacklist.txt"); - try (PrintWriter writer = new PrintWriter(blacklistFile)) { - for (Address addr : functionAddrBlackList) { - writer.println(addr.toString() + " // " + getFunctionAt(addr).getName()); - } - } catch (FileNotFoundException e) { - println("Error saving blacklist: " + e.getMessage()); - } - } - - boolean shouldDecompileFunction(Function function) { - if (ONLY_SYMBOLS != null && !ONLY_SYMBOLS.contains(function.getName())) { - return false; - } - return !functionAddrBlackList.contains(function.getEntryPoint()); - } - - static final boolean BUILD_BLACKLIST = true; - - void buildFunctionBlacklist() { - loadFunctionBlacklist(); - - if (BUILD_BLACKLIST) { - boolean modified = false; - - Iterator functionsIt = currentProgram.getFunctionManager().getFunctions(true).iterator(); - while (functionsIt.hasNext()) { - Function function = functionsIt.next(); - if (functionAddrBlackList.contains(function.getEntryPoint())) { - continue; - } - - String comment = function.getComment(); - boolean isIgnoredFunction = false; - if (comment != null && comment.contains("Library Function")) { - println("Adding library function " + function.getName() + " to blacklist"); - println("ac:" + functionAddrBlackList.size() + " jj:" - + functionAddrBlackList.contains(function.getEntryPoint()) + " " + function.getEntryPoint()); - isIgnoredFunction = true; - } - - if (function.getName().startsWith("crt_")) { - println("Adding crt function " + function.getName() + " to blacklist"); - isIgnoredFunction = true; - } - - if (isIgnoredFunction) { - // Decompile and trace - PCallTracer tracer = new PCallTracer(decompCache); - tracer.setBlacklist(functionAddrBlackList); - tracer.traceCalls(function); - for (Function f : tracer.out) { - println(" Adding " + f.getName() + " to blacklist"); - functionAddrBlackList.add(f.getEntryPoint()); - modified = true; - } - } - } - - if (modified) { - saveFunctionBlacklist(); - } - } - } - - void sanitizeGlobalSymbolsPass(List functions) { - Hashtable globalSymbols = new Hashtable<>(); - - for (Function function : functions) { - println("Processing global symbols for " + function.getName()); - - DecompileResults decompRes = decompCache.getOrInsert(function); - Iterator smyIt = decompRes.getHighFunction().getGlobalSymbolMap().getSymbols(); - - HighSymbol gsym = smyIt.next(); - if (globalSymbols.containsKey(gsym.getName())) - continue; - println("GLOBAL: " + gsym.getName()); - String sanitizedName = sanitizeFunctionName(gsym.getName()); - if (!sanitizedName.equals(gsym.getName())) { - if (AUTO_RENAME_SYMBOLS) { - Symbol symbol = gsym.getSymbol(); - VariableStorage storage = gsym.getStorage(); - Address addr = storage.getMinAddress(); - println("Renaming global symbol: " + gsym.getName() + " (" + addr - + ") -> " + sanitizedName); - if (symbol != null) { - AddLabelCmd cmd = new AddLabelCmd(addr, sanitizedName, symbol.getParentNamespace(), - SourceType.USER_DEFINED); - if (cmd.applyTo(currentProgram)) { - println("Renamed global symbol: " + gsym.getName() + " -> " + sanitizedName); - } else { - println("Error renaming symbol: " + cmd.getStatusMsg()); - } - } else { - println("Symbol is null: " + gsym.getName() + " - " + function.getName()); - } - } else { - println("Invalid global symbol name: " + gsym.getName() + " - " + function.getName()); - } - } - } - } - - String escapeCString(String str) { - str = str.replace("\\", "\\\\"); - str = str.replace("\"", "\\\""); - // str = str.replaceAll("\n", "\\n"); - // str = str.replaceAll("\r", "\\r"); - // str = str.replaceAll("\t", "\\t"); - // str = str.replaceAll("\b", "\\b"); - // str = str.replaceAll("\f", "\\f"); - // str = str.replaceAll("\0", "\\0"); - return str; - } - - String readCString(Address addr, int maxLen) throws Exception { - StringBuilder sb = new StringBuilder(); - int ofs = 0; - while (true) { - Address read = addr.add(ofs++); - // println("Reading: " + read); - byte b = currentProgram.getMemory().getByte(read); - // println("Read: " + b); - if (b == 0 || ofs >= maxLen) { - break; - } - sb.append((char) b); - } - if (sb.length() > 0) { - println("STR \"" + sb.toString() + "\""); - } - return sb.toString(); - } - - void decompileFunction(Hashtable outGlobalSymbols, Function function) - throws Exception { - String fileName = sanitizeFunctionName(function.getName()) + ".cxx"; - - File f1 = new File(dirDecompFix, fileName); - if (f1.exists()) { - println("Func " + function.getName() + " skipped (gh_fix)"); - return; - } - - File f0 = new File(dirDecompAuto, fileName); - if (f0.exists()) { - f0.delete(); - } - - println("Processing " + function.getName() + " => " + f0.toString()); - - DecompileResults decompRes = decompCache.getOrInsert(function); - try (PrintWriter writer2 = new PrintWriter(f0, "UTF-8")) { - writer2.println("// AUTO-GENERATED FILE, MOVE TO 'gh_fix' FOLDER PREVENT OVERWRITING!!!!! "); - writer2.println(); - writer2.println("#include "); - writer2.println("#include \"../gh_global.h\""); - writer2.println(); - - // decompRes.get - HighFunction highFunction = decompRes.getHighFunction(); - - // Remap for dynamic symbols - // Dictionary symbolRemap = new Hashtable<>(); - - HashSet headers = new HashSet<>(); - StringWriter codeWriter = new StringWriter(); - - PrettyPrinter pp = new PrettyPrinter(decompRes.getFunction(), decompRes.getCCodeMarkup(), null); - Iterator lines = pp.getLines().iterator(); - while (lines.hasNext()) { - ClangLine line = lines.next(); - for (int i = 0; i < line.getIndent(); i++) { - codeWriter.write(' '); - } - for (int t = 0; t < line.getNumTokens(); t++) { - ClangToken token = line.getToken(t); - HighSymbol gsym = token.getHighSymbol(highFunction); - if (gsym != null) { - var symStorage = gsym.getStorage(); - if (symStorage.isMemoryStorage() || symStorage.isConstantStorage()) { - // println("Token: " + token.toString() + " - " + gsym.getName()); - outGlobalSymbols.put(gsym.getName(), gsym); - } - } - PcodeOp op = token.getPcodeOp(); - if (op != null && op.getOpcode() == PcodeOp.CALL) { - println("PcodeOp: " + op.toString() + " - " + op.getInput(0).toString()); - Varnode target = op.getInput(0); - if (target.isAddress()) { - Address callAddr = target.getAddress(); - Function calledFunction = getFunctionAt(callAddr); - if (calledFunction != null) { - if (functionAddrBlackList.contains(calledFunction.getEntryPoint())) { - println("Adding header: " + calledFunction + " / " - + calledFunction.getSignature().getPrototypeString(true)); - headers.add("extern " + calledFunction.getSignature().getPrototypeString(true)); - } - } - } - } - codeWriter.write(token.toString()); - } - codeWriter.write('\n'); - } - - for (String header : headers) { - writer2.println(header + ";"); - } - writer2.println(); - writer2.println("// " + function.getEntryPoint()); - writer2.print(codeWriter.toString()); - writer2.println(); - - // Iterator it = decompRes.getCCodeMarkup().tokenIterator(true); - // int ln = 0; - // while(it.hasNext()) { - // ClangToken token = it.next(); - // ClangLine line = token.getLineParent(); - // while (line != null && ln < line.getLineNumber()) { - // writer2.println(); - // ln++; - // } - // writer2.print(token.toString()); - // } - - // Collect referenced global symbols - // Iterator smyIt = highFunction.getGlobalSymbolMap().getSymbols(); - // while (smyIt.hasNext()) { - // HighSymbol gsym = smyIt.next(); - - // Address addr = gsym.getSymbol().getAddress(); - // println("FunctionSym " + addr + " " + gsym.getName() + " " + - // gsym.getStorage().getMinAddress()); - // println(" IsMem: " + gsym.getStorage().isMemoryStorage() + " " + - // gsym.getStorage().getSerializationString()); - - // if (outGlobalSymbols.containsKey(gsym.getName())) - // continue; - // outGlobalSymbols.put(gsym.getName(), gsym); - // } - } - } - - HashSet loadStructBlacklist() { - File file = new File(outputDir, "struct_blacklist.txt"); - HashSet structBlacklist = new HashSet<>(); - try (Scanner scanner = new Scanner(file)) { - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - structBlacklist.add(line.trim()); - } - } catch (FileNotFoundException e) { - return null; - } - return structBlacklist; - } - - void saveStructBlacklist(HashSet structBlacklist) throws Exception { - String[] arr = structBlacklist.toArray(new String[0]); - Arrays.sort(arr); - File file = new File(outputDir, "struct_blacklist.txt"); - try (PrintWriter writer = new PrintWriter(file)) { - for (String structName : arr) { - writer.println(structName); - } - } - } - - void headerGuardPre(PrintWriter writer, String tag) { - writer.println("#ifndef GH_GENERATED_" + tag + "_H"); - writer.println("#define GH_GENERATED_" + tag + "_H"); - writer.println(); - } - - void headerGuardPost(PrintWriter writer, String tag) { - writer.println("#endif // GH_GENERATED_" + tag + "_H"); - } - - void dumpStructureTypes() throws Exception { - try (PrintWriter writer = new PrintWriter(new File(outputDir, "gh_structs.h"), "UTF-8")) { - headerGuardPre(writer, "STRUCTS"); - writer.println("// AUTO-GENERATED FILE "); - writer.println("#include "); - ProgramBasedDataTypeManager dtm = currentProgram.getDataTypeManager(); - - HashSet structBlacklist = loadStructBlacklist(); - if (structBlacklist == null) { - println("Building struct blacklist from existing data types"); - structBlacklist = new HashSet<>(); - Iterator it = dtm.getAllDataTypes(); - while (it.hasNext()) { - DataType dt = it.next(); - if (dt instanceof Structure) { - structBlacklist.add(dt.getName()); - } - } - saveStructBlacklist(structBlacklist); - } - - Iterator it = dtm.getAllDataTypes(); - while (it.hasNext()) { - DataType dt = it.next(); - if (dt instanceof Structure) { - Structure struct = (Structure) dt; - if (structBlacklist.contains(struct.getName())) - continue; - writer.println("struct " + struct.getName() + " {"); - for (DataTypeComponent component : struct.getComponents()) { - writer.println( - " " + component.getDataType().getDisplayName() + " " + component.getDefaultFieldName() + ";"); - } - writer.println("};"); - writer.println(); - } - } - - headerGuardPost(writer, "STRUCTS"); - } - } - - void dumpGlobals(Hashtable globalSymbols) throws Exception { - File globalSymbolsListH = new File(outputDir, "gh_global.h"); - PrintWriter hwriter = new PrintWriter(globalSymbolsListH, "UTF-8"); - hwriter.println("// AUTO-GENERATED FILE "); - headerGuardPre(hwriter, "GLOBALS"); - hwriter.println("#include "); - hwriter.println("#include \"gh_structs.h\""); - hwriter.println(); - - File globalSymbolsListC = new File(outputDir, "gh_global.cxx"); - PrintWriter cwriter = new PrintWriter(globalSymbolsListC, "UTF-8"); - cwriter.println("// AUTO-GENERATED FILE "); - cwriter.println("#include "); - cwriter.println("#include \"gh_structs.h\""); - hwriter.println(); - - for (HighSymbol highSym : globalSymbols.values()) { - DataType dt = highSym.getDataType(); - String dataType = dt.getDisplayName(); - String name = highSym.getName(); - String sanitizedName = sanitizeFunctionName(highSym.getName()); - if (!sanitizedName.equals(highSym.getName())) { - println("Invalid global symbol name: " + highSym.getName() + " - " - + highSym.getHighFunction().getFunction().getName()); - } else { - Symbol symbol = highSym.getSymbol(); - VariableStorage storage = highSym.getStorage(); - Address addr = storage.getMinAddress(); - int symSize = highSym.getSize(); - if (addr == null) { - // Not sure why this is sometimes null - // also when it is not null, Symbol.getAddress() is not correct but very small - // like 00000056 - // Not that storage will be so maybe can check that - addr = symbol.getAddress(); - } - - println("Symbol: " + symbol + " Addr: " + addr + " Size:" + symSize + " " + storage.getSerializationString()); - try { - String initBlk = " = "; - if (dt instanceof AbstractStringDataType) { - AbstractStringDataType sdt = (AbstractStringDataType) dt; - dataType = "const char*"; - // String type - initBlk += "\"" + escapeCString(readCString(addr, 2048)) + "\""; - } else if (dt instanceof PointerDataType) { - PointerDataType pdt = (PointerDataType) dt; - DataType baseType = pdt.getDataType(); - dataType = baseType.getDisplayName() + "*"; - initBlk += "gh_ptr(0x" + addr + ")"; - } else { - initBlk = " {}"; - } - cwriter.println(dataType + " " + name + initBlk + "; // " + addr); - } catch (Exception e) { - println("Error processing global symbol: " + e); - println("Symbol: " + highSym.getName() + " - " + addr + " - " - + highSym.getHighFunction().getFunction().getName()); - } - - hwriter.println("extern " + dataType + " " + name + "; // " + addr); - } - } - headerGuardPost(hwriter, "GLOBALS"); - hwriter.close(); - cwriter.close(); - } - - void decompileAll(List functions) throws Exception { - Hashtable globalSymbols = new Hashtable<>(); - - for (Function function : functions) { - decompileFunction(globalSymbols, function); - } - - dumpStructureTypes(); - dumpGlobals(globalSymbols); - } - - @Override - public void run() throws Exception { - if (currentProgram == null) { - return; - } - - DecompInterface decomp = new DecompInterface(); - decomp.openProgram(currentProgram); - decompCache = new DecompileCache(decomp); - - staticMemoryBlockStart = currentProgram.getAddressFactory().getAddress("005b6400"); - staticMemoryBlockEnd = currentProgram.getAddressFactory().getAddress("00843fff"); - - // Make sure to create OUTPUT_PATH - rootDir = new File(sourceFile.getAbsolutePath()).getParentFile().getParentFile(); - outputDir = new File(rootDir, OUTPUT_DIR); - - if (!outputDir.exists()) { - throw new Exception("Output directory does not exist: " + outputDir.getCanonicalPath()); - } - - dirDecompAuto = new File(outputDir, "gh_auto"); - dirDecompFix = new File(outputDir, "gh_fix"); - - println("Output path: " + outputDir.getCanonicalPath()); - - buildFunctionBlacklist(); - - List functions = new ArrayList<>(); - - Iterator functionsIt = currentProgram.getFunctionManager().getFunctions(true).iterator(); - while (functionsIt.hasNext()) { - Function function = functionsIt.next(); - if (!shouldDecompileFunction(function)) { - continue; - } - - functions.add(function); - } - - int mode = 1; - if (mode == 0) { // Sanitize symbols - sanitizeGlobalSymbolsPass(functions); - } else if (mode == 1) { // Decompile all functions - decompileAll(functions); - } - } - - String sanitizeFunctionName(String name) { - return name.replaceAll("[^a-zA-Z0-9_]", "_"); - } -} diff --git a/scripts/ExportData.java b/scripts/ExportData.java new file mode 100644 index 00000000..38a921eb --- /dev/null +++ b/scripts/ExportData.java @@ -0,0 +1,51 @@ +// Exports binary read only and data segments to a binary + header file +// @category _Reman3 +// @menupath Tools.Reman3.Export Data + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import re3lib.RecompileConfig; + +public class ExportData extends GhidraScript { + + @Override + protected void run() throws Exception { + if (currentProgram == null) { + return; + } + RecompileConfig.INSTANCE = new RecompileConfig(this); + + String dataFile = new File(RecompileConfig.INSTANCE.outputDir, "gh_datasegment.bin").toString(); + String headerFile = new File(RecompileConfig.INSTANCE.outputDir, "gh_datasegment.h").toString(); + + FileOutputStream dataOutputStream = new FileOutputStream(dataFile); + PrintWriter headerWriter = new PrintWriter(headerFile, "UTF-8"); + + Address startAddr = RecompileConfig.INSTANCE.staticMemoryBlockStart; + Address endAddr = RecompileConfig.INSTANCE.staticMemoryBlockEnd; + + // Dump all the memory to the bin file + int numBytes = (int) endAddr.subtract(startAddr); + byte[] buffer = new byte[numBytes]; + currentProgram.getMemory().getBytes(startAddr, buffer); + dataOutputStream.write(buffer); + + headerWriter.println("// AUTO-GENERATED FILE, DO NOT MODIFY!!!!!"); + headerWriter.println("// Use 'Export Data' script to export the data segment"); + headerWriter.println("#pragma once"); + headerWriter.println("#include "); + headerWriter.println(""); + headerWriter.println("#define GH_DATA_START 0x" + startAddr.toString()); + headerWriter.println("#define GH_DATA_END 0x" + endAddr.toString()); + headerWriter.println("#define GH_DATA_SIZE (GH_DATA_END - GH_DATA_START)"); + headerWriter.println("constexpr size_t gh_data_start = 0x" + startAddr.toString() + ";"); + headerWriter.println("constexpr size_t gh_data_end = 0x" + endAddr.toString() + ";"); + headerWriter.println("constexpr size_t gh_data_size = gh_data_end - gh_data_start;"); + + headerWriter.close(); + dataOutputStream.close(); + } +} diff --git a/scripts/NewScript.java b/scripts/NewScript.java new file mode 100644 index 00000000..556b9968 --- /dev/null +++ b/scripts/NewScript.java @@ -0,0 +1,15 @@ +// Script to categorize Stuff +// @menupath File.Run.ScriptName +// @category _Reman3 + +import ghidra.app.script.GhidraScript; +import re3lib.*; + +public class NewScript extends GhidraScript { + + @Override + protected void run() throws Exception { + // TODO: Add your script logic here + println("Hello from NewScript!"); + } +} diff --git a/scripts/SanitizeGlobalSymbols.java b/scripts/SanitizeGlobalSymbols.java new file mode 100644 index 00000000..c87d8a03 --- /dev/null +++ b/scripts/SanitizeGlobalSymbols.java @@ -0,0 +1,103 @@ +// Script to sanitize global symbols in Ghidra +// @category _Reman3 + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; + +import ghidra.app.cmd.label.AddLabelCmd; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.HighSymbol; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.Symbol; +import re3lib.RecompileConfig; +import re3lib.Utils; + +public class SanitizeGlobalSymbols extends GhidraScript { + private static final boolean AUTO_RENAME_SYMBOLS = true; + + HashSet
functionAddrBlackList = new HashSet<>(); + + boolean shouldDecompileFunction(Function function) { + return !functionAddrBlackList.contains(function.getEntryPoint()); + } + + void sanitizeGlobalSymbolsPass(List functions) { + Hashtable globalSymbols = new Hashtable<>(); + + for (Function function : functions) { + println("Processing global symbols for " + function.getName()); + + DecompileResults decompRes = RecompileConfig.INSTANCE.decompCache.getOrInsert(function); + Iterator smyIt = decompRes.getHighFunction().getGlobalSymbolMap().getSymbols(); + + HighSymbol gsym = smyIt.next(); + if (globalSymbols.containsKey(gsym.getName())) + continue; + println("GLOBAL: " + gsym.getName()); + String sanitizedName = sanitizeFunctionName(gsym.getName()); + if (!sanitizedName.equals(gsym.getName())) { + if (AUTO_RENAME_SYMBOLS) { + Symbol symbol = gsym.getSymbol(); + VariableStorage storage = gsym.getStorage(); + Address addr = storage.getMinAddress(); + println("Renaming global symbol: " + gsym.getName() + " (" + addr + + ") -> " + sanitizedName); + if (symbol != null) { + AddLabelCmd cmd = new AddLabelCmd(addr, sanitizedName, symbol.getParentNamespace(), + SourceType.USER_DEFINED); + if (cmd.applyTo(currentProgram)) { + println("Renamed global symbol: " + gsym.getName() + " -> " + sanitizedName); + } else { + println("Error renaming symbol: " + cmd.getStatusMsg()); + } + } else { + println("Symbol is null: " + gsym.getName() + " - " + function.getName()); + } + } else { + println("Invalid global symbol name: " + gsym.getName() + " - " + function.getName()); + } + } + } + } + + private static String sanitizeFunctionName(String name) { + return name.replaceAll("[^a-zA-Z0-9_]", "_"); + } + + @Override + public void run() throws Exception { + if (currentProgram == null) { + return; + } + + RecompileConfig.INSTANCE = new RecompileConfig(this); + + if (!new File(RecompileConfig.INSTANCE.outputDir).exists()) { + throw new Exception("Output directory does not exist: " + RecompileConfig.INSTANCE.outputDir); + } + + functionAddrBlackList = Utils.loadFunctionBlacklist(RecompileConfig.INSTANCE.functionBlacklistPath); + + List functions = new ArrayList<>(); + + Iterator functionsIt = currentProgram.getFunctionManager().getFunctions(true).iterator(); + while (functionsIt.hasNext()) { + Function function = functionsIt.next(); + if (!shouldDecompileFunction(function)) { + continue; + } + + functions.add(function); + } + + sanitizeGlobalSymbolsPass(functions); + } +} diff --git a/scripts/re3lib/DataTypeWriter.java b/scripts/re3lib/DataTypeWriter.java new file mode 100644 index 00000000..180f15d3 --- /dev/null +++ b/scripts/re3lib/DataTypeWriter.java @@ -0,0 +1,842 @@ +// Source code is decompiled from a .class file using FernFlower decompiler. +package re3lib; + +import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class DataTypeWriter { + public HashSet blacklistedTypes; + + private static String[] INTEGRAL_TYPES = new String[] { "char", "short", "int", "long", "long long", "__int64", + "float", "double", "long double", "void" }; + private static String[] INTEGRAL_MODIFIERS = new String[] { "signed", "unsigned", "const", "static", "volatile", + "mutable" }; + private static String EOL = System.getProperty("line.separator"); + private Set resolved; + private Map resolvedTypeMap; + private Set deferredCompositeDeclarations; + private ArrayDeque deferredTypeFIFO; + private Set deferredTypes; + private int writerDepth; + private Writer writer; + private DataTypeManager dtm; + private DataOrganization dataOrganization; + private AnnotationHandler annotator; + private boolean cppStyleComments; + + public DataTypeWriter(DataTypeManager dtm, Writer writer) throws IOException { + this(dtm, writer, new DefaultAnnotationHandler()); + } + + public DataTypeWriter(DataTypeManager dtm, Writer writer, boolean cppStyleComments) throws IOException { + this(dtm, writer, new DefaultAnnotationHandler(), cppStyleComments); + } + + public DataTypeWriter(DataTypeManager dtm, Writer writer, AnnotationHandler annotator) throws IOException { + this(dtm, writer, annotator, false); + } + + public DataTypeWriter(DataTypeManager dtm, Writer writer, AnnotationHandler annotator, boolean cppStyleComments) + throws IOException { + this.blacklistedTypes = new HashSet<>(); + this.resolved = new HashSet(); + this.resolvedTypeMap = new HashMap(); + this.deferredCompositeDeclarations = new HashSet(); + this.deferredTypeFIFO = new ArrayDeque(); + this.deferredTypes = new HashSet(); + this.writerDepth = 0; + this.cppStyleComments = false; + this.dtm = dtm; + if (dtm != null) { + this.dataOrganization = dtm.getDataOrganization(); + } + + if (this.dataOrganization == null) { + this.dataOrganization = DataOrganizationImpl.getDefaultOrganization(); + } + + this.writer = writer; + this.annotator = annotator; + this.cppStyleComments = cppStyleComments; + if (dtm != null) { + // this.writeBuiltInDeclarations(dtm); + } + } + + private String comment(String text) { + if (text == null) { + return ""; + } else { + return this.cppStyleComments ? "// " + text : "/* " + text + " */"; + } + } + + public void write(DataTypeManager dataTypeManager, TaskMonitor monitor) throws IOException, CancelledException { + this.write(dataTypeManager.getRootCategory(), monitor); + } + + public void write(Category category, TaskMonitor monitor) throws IOException, CancelledException { + DataType[] dataTypes = category.getDataTypes(); + this.write(dataTypes, monitor); + Category[] subCategories = category.getCategories(); + Category[] var5 = subCategories; + int var6 = subCategories.length; + + for (int var7 = 0; var7 < var6; ++var7) { + Category subCategory = var5[var7]; + if (monitor.isCancelled()) { + return; + } + + this.write(subCategory, monitor); + } + + } + + public void write(DataType[] dataTypes, TaskMonitor monitor) throws IOException, CancelledException { + monitor.initialize((long) dataTypes.length); + int cnt = 0; + DataType[] var4 = dataTypes; + int var5 = dataTypes.length; + + for (int var6 = 0; var6 < var5; ++var6) { + DataType dataType = var4[var6]; + monitor.checkCancelled(); + this.write(dataType, monitor); + ++cnt; + monitor.setProgress((long) cnt); + } + + } + + public void write(List dataTypes, TaskMonitor monitor) throws IOException, CancelledException { + this.write(dataTypes, monitor, true); + } + + public void write(List dataTypes, TaskMonitor monitor, boolean throwExceptionOnInvalidType) + throws IOException, CancelledException { + monitor.initialize((long) dataTypes.size()); + int cnt = 0; + Iterator var5 = dataTypes.iterator(); + + while (var5.hasNext()) { + DataType dataType = (DataType) var5.next(); + monitor.checkCancelled(); + this.write(dataType, monitor, throwExceptionOnInvalidType); + ++cnt; + monitor.setProgress((long) cnt); + } + + } + + private void deferWrite(DataType dt) { + if (!this.resolved.contains(dt) && !this.deferredTypes.contains(dt)) { + this.deferredTypes.add(dt); + this.deferredTypeFIFO.addLast(dt); + } + + } + + void write(DataType dt, TaskMonitor monitor) throws IOException, CancelledException { + this.doWrite(dt, monitor, true); + } + + void write(DataType dt, TaskMonitor monitor, boolean throwExceptionOnInvalidType) + throws IOException, CancelledException { + this.doWrite(dt, monitor, throwExceptionOnInvalidType); + } + + private void doWrite(DataType dt, TaskMonitor monitor, boolean throwExceptionOnInvalidType) + throws IOException, CancelledException { + if (dt != null) { + if (blacklistedTypes.contains(dt.getDisplayName())) + return; + if (!(dt instanceof FunctionDefinition)) { + if (dt instanceof FactoryDataType) { + IllegalArgumentException iae = new IllegalArgumentException("Factory data types may not be written"); + if (throwExceptionOnInvalidType) { + throw iae; + } + + Msg.error(this, "Factory data types may not be written - type: " + dt); + } + + if (!(dt instanceof Pointer) && !(dt instanceof Array) && !(dt instanceof BitFieldDataType)) { + dt = dt.clone(this.dtm); + if (!this.resolved.contains(dt)) { + this.resolved.add(dt); + DataType resolvedType = (DataType) this.resolvedTypeMap.get(dt.getName()); + if (resolvedType != null) { + if (!resolvedType.isEquivalent(dt)) { + if (dt instanceof TypeDef) { + DataType baseType = ((TypeDef) dt).getBaseDataType(); + if ((resolvedType instanceof Composite || resolvedType instanceof Enum) + && baseType.isEquivalent(resolvedType)) { + return; + } + } + + this.writer.write(EOL); + Writer var10000 = this.writer; + String var10002 = dt.getPathName(); + var10000.write(this + .comment("WARNING! conflicting data type names: " + var10002 + " - " + resolvedType.getPathName())); + this.writer.write(EOL); + this.writer.write(EOL); + } + } else { + this.resolvedTypeMap.put(dt.getName(), dt); + ++this.writerDepth; + if (dt.equals(DataType.DEFAULT)) { + this.writer.write("typedef unsigned char " + DataType.DEFAULT.getName() + ";"); + this.writer.write(EOL); + this.writer.write(EOL); + } else if (dt instanceof Dynamic) { + this.writeDynamicBuiltIn((Dynamic) dt, monitor); + } else if (dt instanceof Structure) { + Structure struct = (Structure) dt; + this.writeCompositePreDeclaration(struct, monitor); + this.deferredCompositeDeclarations.add(struct); + } else if (dt instanceof Union) { + Union union = (Union) dt; + this.writeCompositePreDeclaration(union, monitor); + this.deferredCompositeDeclarations.add(union); + } else if (dt instanceof Enum) { + this.writeEnum((Enum) dt, monitor); + } else if (dt instanceof TypeDef) { + this.writeTypeDef((TypeDef) dt, monitor); + } else if (dt instanceof BuiltInDataType) { + this.writeBuiltIn((BuiltInDataType) dt, monitor); + } else if (!(dt instanceof BitFieldDataType)) { + this.writer.write(EOL); + this.writer.write(EOL); + this.writer.write(this.comment("Unable to write datatype. Type unrecognized: " + dt.getClass())); + this.writer.write(EOL); + this.writer.write(EOL); + } + + if (this.writerDepth == 1) { + this.writeDeferredDeclarations(monitor); + } + + --this.writerDepth; + } + } + } else { + this.write(this.getBaseDataType(dt), monitor); + } + } + } + } + + private void writeDeferredDeclarations(TaskMonitor monitor) throws IOException, CancelledException { + while (!this.deferredTypes.isEmpty()) { + DataType dt = (DataType) this.deferredTypeFIFO.removeFirst(); + this.deferredTypes.remove(dt); + this.write(dt, monitor); + } + + this.writeDeferredCompositeDeclarations(monitor); + this.deferredCompositeDeclarations.clear(); + } + + private DataType getBaseArrayTypedefType(DataType dt) { + while (true) { + if (dt != null) { + if (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + continue; + } + + if (dt instanceof Array) { + dt = ((Array) dt).getDataType(); + continue; + } + } + + return dt; + } + } + + private boolean containsComposite(Composite container, Composite contained) { + DataTypeComponent[] var3 = container.getDefinedComponents(); + int var4 = var3.length; + + for (int var5 = 0; var5 < var4; ++var5) { + DataTypeComponent component = var3[var5]; + DataType dt = this.getBaseArrayTypedefType(component.getDataType()); + if (dt instanceof Composite && dt.getName().equals(contained.getName()) && dt.isEquivalent(contained)) { + return true; + } + } + + return false; + } + + private void writeDeferredCompositeDeclarations(TaskMonitor monitor) throws IOException, CancelledException { + int cnt = this.deferredCompositeDeclarations.size(); + if (cnt != 0) { + LinkedList list = new LinkedList(this.deferredCompositeDeclarations); + if (list.size() > 1) { + int sortChange = 1; + + while (sortChange != 0) { + sortChange = 0; + + for (int i = cnt - 1; i > 0; --i) { + if (this.resortComposites(list, i)) { + ++sortChange; + } + } + } + } + + Iterator var6 = list.iterator(); + + while (var6.hasNext()) { + Composite composite = (Composite) var6.next(); + this.writeCompositeBody(composite, monitor); + } + + } + } + + private boolean resortComposites(List list, int index) { + int listSize = list.size(); + if (listSize <= 0) { + return false; + } else { + Composite composite = (Composite) list.get(index); + + for (int i = 0; i < index; ++i) { + Composite other = (Composite) list.get(i); + if (this.containsComposite(other, composite)) { + list.remove(index); + list.add(i, composite); + composite = null; + return true; + } + } + + return false; + } + } + + private String getDynamicComponentString(Dynamic dynamicType, String fieldName, int length) { + if (dynamicType.canSpecifyLength()) { + DataType replacementBaseType = dynamicType.getReplacementBaseType(); + if (replacementBaseType != null) { + replacementBaseType = replacementBaseType.clone(this.dtm); + int elementLen = replacementBaseType.getLength(); + if (elementLen > 0) { + int elementCnt = (length + elementLen - 1) / elementLen; + return replacementBaseType.getDisplayName() + " " + fieldName + "[" + elementCnt + "]"; + } + + String var10001 = dynamicType.getClass().getSimpleName(); + Msg.error(this, + var10001 + " returned bad replacementBaseType: " + replacementBaseType.getClass().getSimpleName()); + } + } + + return null; + } + + private void writeCompositePreDeclaration(Composite composite, TaskMonitor monitor) + throws IOException, CancelledException { + String compositeType = composite instanceof Structure ? "struct" : "union"; + this.writer.write("typedef " + compositeType + " " + composite.getDisplayName() + " " + composite.getDisplayName() + + ", *P" + composite.getDisplayName() + ";"); + this.writer.write(EOL); + this.writer.write(EOL); + DataTypeComponent[] var4 = composite.getComponents(); + int var5 = var4.length; + + for (int var6 = 0; var6 < var5; ++var6) { + DataTypeComponent component = var4[var6]; + if (monitor.isCancelled()) { + break; + } + + DataType componentType = component.getDataType(); + this.deferWrite(componentType); + this.getTypeDeclaration((String) null, componentType, component.getLength(), true, monitor); + } + + } + + private void writeCompositeBody(Composite composite, TaskMonitor monitor) throws IOException, CancelledException { + String compositeType = composite instanceof Structure ? "struct" : "union"; + StringBuilder sb = new StringBuilder(); + sb.append(compositeType + " " + composite.getDisplayName() + " {"); + String descrip = composite.getDescription(); + if (descrip != null && descrip.length() > 0) { + String var10001 = this.comment(descrip); + sb.append(" " + var10001); + } + + sb.append(EOL); + DataTypeComponent[] var6 = composite.getComponents(); + int var7 = var6.length; + + for (int var8 = 0; var8 < var7; ++var8) { + DataTypeComponent component = var6[var8]; + monitor.checkCancelled(); + this.writeComponent(component, composite, sb, monitor); + } + + sb.append(this.annotator.getSuffix(composite, (DataTypeComponent) null)); + sb.append("};"); + this.writer.write(sb.toString()); + this.writer.write(EOL); + this.writer.write(EOL); + } + + private void writeComponent(DataTypeComponent component, Composite composite, StringBuilder sb, TaskMonitor monitor) + throws IOException, CancelledException { + sb.append(" "); + sb.append(this.annotator.getPrefix(composite, component)); + String fieldName = component.getFieldName(); + if (fieldName == null || fieldName.length() == 0) { + fieldName = component.getDefaultFieldName(); + } + + DataType componentDataType = component.getDataType(); + sb.append(this.getTypeDeclaration(fieldName, componentDataType, component.getLength(), false, monitor)); + sb.append(";"); + sb.append(this.annotator.getSuffix(composite, component)); + String comment = component.getComment(); + if (comment != null && comment.length() > 0) { + String var10001 = this.comment(comment); + sb.append(" " + var10001); + } + + sb.append(EOL); + } + + private String getTypeDeclaration(String name, DataType dataType, int instanceLength, boolean writeEnabled, + TaskMonitor monitor) throws IOException, CancelledException { + if (name == null) { + name = ""; + } + + StringBuilder sb = new StringBuilder(); + String componentString = null; + if (dataType instanceof Dynamic) { + componentString = this.getDynamicComponentString((Dynamic) dataType, name, instanceLength); + if (componentString != null) { + sb.append(componentString); + } else { + sb.append(this.comment("ignoring dynamic datatype inside composite: " + dataType.getDisplayName())); + sb.append(EOL); + } + } + + if (componentString == null) { + if (dataType instanceof BitFieldDataType) { + BitFieldDataType bfDt = (BitFieldDataType) dataType; + name = name + ":" + bfDt.getDeclaredBitSize(); + dataType = bfDt.getBaseDataType(); + } + + label44: while (true) { + while (!(dataType instanceof Array)) { + if (!(dataType instanceof Pointer)) { + break label44; + } + + Pointer pointer = (Pointer) dataType; + DataType elem = pointer.getDataType(); + if (elem == null) { + break label44; + } + + name = "*" + name; + dataType = elem; + if (elem instanceof Array) { + name = "(" + name + ")"; + } + } + + Array array = (Array) dataType; + name = name + "[" + array.getNumElements() + "]"; + dataType = array.getDataType(); + } + + DataType baseDataType = this.getBaseDataType(dataType); + if (baseDataType instanceof FunctionDefinition) { + componentString = this.getFunctionPointerString((FunctionDefinition) baseDataType, name, dataType, writeEnabled, + monitor); + } else { + String var10000 = this.getDataTypePrefix(dataType); + componentString = var10000 + dataType.getDisplayName(); + if (name.length() != 0) { + componentString = componentString + " " + name; + } + } + + sb.append(componentString); + } + + return sb.toString(); + } + + private String getDataTypePrefix(DataType dataType) { + dataType = this.getBaseDataType(dataType); + if (dataType instanceof Structure) { + return "struct "; + } else if (dataType instanceof Union) { + return "union "; + } else { + return dataType instanceof Enum ? "enum " : ""; + } + } + + private void writeEnum(Enum enumm, TaskMonitor monitor) throws IOException { + String enumName = enumm.getDisplayName(); + Writer var10000; + String var10001; + if (enumName.startsWith("define_") && enumName.length() > 7 && enumm.getCount() == 1) { + long val = enumm.getValues()[0]; + var10000 = this.writer; + var10001 = enumName.substring(7); + var10000.append("#define " + var10001 + " " + Long.toString(val)); + this.writer.write(EOL); + this.writer.write(EOL); + } else { + this.writer.write("typedef enum " + enumName + " {"); + String description = enumm.getDescription(); + if (description != null && description.length() != 0) { + var10000 = this.writer; + var10001 = this.comment(description); + var10000.write(" " + var10001); + } + + this.writer.write(EOL); + String[] names = enumm.getNames(); + + for (int j = 0; j < names.length; ++j) { + this.writer.write(" "); + this.writer.write(this.annotator.getPrefix(enumm, names[j])); + this.writer.write(names[j]); + this.writer.write("="); + this.writer.write(Long.toString(enumm.getValue(names[j]))); + String comment = enumm.getComment(names[j]); + if (!comment.isBlank()) { + var10000 = this.writer; + var10001 = this.comment(comment); + var10000.write(" " + var10001); + } + + this.writer.write(this.annotator.getSuffix(enumm, names[j])); + if (j < names.length - 1) { + this.writer.write(","); + } + + this.writer.write(EOL); + } + + this.writer.write("} " + enumName + ";"); + this.writer.write(EOL); + this.writer.write(EOL); + } + } + + private void writeTypeDef(TypeDef typeDef, TaskMonitor monitor) throws IOException, CancelledException { + String typedefName = typeDef.getDisplayName(); + DataType dataType = typeDef.getDataType(); + String dataTypeName = dataType.getDisplayName(); + if (!this.isIntegral(typedefName, dataTypeName)) { + DataType baseType = typeDef.getBaseDataType(); + + label125: { + try { + if (!(baseType instanceof Composite) && !(baseType instanceof Enum)) { + if (!(baseType instanceof Pointer) || !typedefName.startsWith("P")) { + break label125; + } + + DataType dt = ((Pointer) baseType).getDataType(); + if (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + } + + if (!(dt instanceof Composite) || !dt.getName().equals(typedefName.substring(1))) { + break label125; + } + + this.resolvedTypeMap.remove(typedefName); + return; + } + + if (!typedefName.equals(baseType.getName())) { + break label125; + } + + this.resolvedTypeMap.remove(typedefName); + } finally { + this.write(dataType, monitor); + } + + return; + } + + if (baseType instanceof Array && this.getBaseArrayTypedefType(baseType) instanceof Composite) { + this.writeDeferredDeclarations(monitor); + } + + String typedefString = this.getTypeDeclaration(typedefName, dataType, -1, true, monitor); + this.writer.write("typedef " + typedefString + ";"); + this.writer.write(EOL); + this.writer.write(EOL); + } + } + + private boolean isIntegral(String typedefName, String basetypeName) { + String[] var3 = INTEGRAL_TYPES; + int var4 = var3.length; + + int var5; + for (var5 = 0; var5 < var4; ++var5) { + String type = var3[var5]; + if (typedefName.equals(type)) { + return true; + } + } + + boolean endsWithIntegralType = false; + String[] var10 = INTEGRAL_TYPES; + var5 = var10.length; + + int var13; + for (var13 = 0; var13 < var5; ++var13) { + String type = var10[var13]; + if (typedefName.endsWith(" " + type)) { + endsWithIntegralType = true; + break; + } + } + + boolean containsIntegralModifier = false; + String[] var12 = INTEGRAL_MODIFIERS; + var13 = var12.length; + + for (int var14 = 0; var14 < var13; ++var14) { + String modifier = var12[var14]; + if (typedefName.indexOf(modifier + " ") >= 0 || typedefName.indexOf(" " + modifier) >= 0) { + return true; + } + } + + if (endsWithIntegralType && containsIntegralModifier) { + return true; + } else if (typedefName.endsWith(" " + basetypeName)) { + return containsIntegralModifier; + } else { + return false; + } + } + + private void writeDynamicBuiltIn(Dynamic dt, TaskMonitor monitor) throws IOException, CancelledException { + DataType baseDt = dt.getReplacementBaseType(); + if (baseDt != null) { + this.write(baseDt, monitor); + } + + } + + private void writeBuiltIn(BuiltInDataType dt, TaskMonitor monitor) throws IOException { + String declaration = dt.getCTypeDeclaration(this.dataOrganization); + if (declaration != null) { + if (dt.getDisplayName() == "bool") + return; + this.writer.write(declaration); + this.writer.write(EOL); + } + } + + private void writeBuiltInDeclarations(DataTypeManager manager) throws IOException { + try { + this.write(DataType.DEFAULT, TaskMonitor.DUMMY); + SourceArchive builtInArchive = manager.getSourceArchive(DataTypeManager.BUILT_IN_ARCHIVE_UNIVERSAL_ID); + if (builtInArchive == null) { + return; + } + + Iterator var3 = manager.getDataTypes(builtInArchive).iterator(); + + while (var3.hasNext()) { + DataType dt = (DataType) var3.next(); + if (!(dt instanceof Pointer) && !(dt instanceof FactoryDataType) && !(dt instanceof Dynamic)) { + this.write(dt, TaskMonitor.DUMMY); + } + } + } catch (CancelledException var5) { + } + + this.writer.flush(); + } + + private static String getArrayDimensions(Array arrayDt) { + String dimensionString = "[" + arrayDt.getNumElements() + "]"; + DataType dataType = arrayDt.getDataType(); + if (dataType instanceof Array) { + dimensionString = dimensionString + getArrayDimensions((Array) dataType); + } + + return dimensionString; + } + + private DataType getBaseDataType(DataType dt) { + while (true) { + if (dt != null) { + if (dt instanceof Array) { + Array array = (Array) dt; + dt = array.getDataType(); + continue; + } + + if (dt instanceof Pointer) { + Pointer pointer = (Pointer) dt; + dt = pointer.getDataType(); + continue; + } + + if (dt instanceof BitFieldDataType) { + BitFieldDataType bitfieldDt = (BitFieldDataType) dt; + dt = bitfieldDt.getBaseDataType(); + continue; + } + } + + return dt; + } + } + + private DataType getArrayBaseType(Array arrayDt) { + DataType dataType; + for (dataType = arrayDt.getDataType(); dataType instanceof Array; dataType = ((Array) dataType).getDataType()) { + } + + return dataType; + } + + private DataType getPointerBaseDataType(Pointer p) { + DataType dt; + for (dt = p.getDataType(); dt instanceof Pointer; dt = ((Pointer) dt).getDataType()) { + } + + return dt; + } + + private int getPointerDepth(Pointer p) { + int depth = 1; + + for (DataType dt = p.getDataType(); dt instanceof Pointer; dt = ((Pointer) dt).getDataType()) { + ++depth; + } + + return depth; + } + + private String getFunctionPointerString(FunctionDefinition fd, String name, DataType functionPointerArrayType, + boolean writeEnabled, TaskMonitor monitor) throws IOException, CancelledException { + DataType originalType = functionPointerArrayType; + StringBuilder sb = new StringBuilder(); + DataType returnType = fd.getReturnType(); + if (writeEnabled) { + this.write(returnType, monitor); + } + + sb.append("("); + String arrayDecorations = ""; + if (functionPointerArrayType instanceof Array a) { + functionPointerArrayType = this.getArrayBaseType(a); + arrayDecorations = getArrayDimensions(a); + } + + if (functionPointerArrayType instanceof Pointer p) { + for (int i = 0; i < this.getPointerDepth(p); ++i) { + sb.append('*'); + } + + if (name != null) { + sb.append(' '); + } + + functionPointerArrayType = this.getPointerBaseDataType(p); + } + + if (!(functionPointerArrayType instanceof FunctionDefinition)) { + this.writer.append(this + .comment("Attempting output of invalid function pointer type declaration: " + originalType.getDisplayName())); + } + + if (name != null) { + sb.append(name); + } + + if (arrayDecorations.length() != 0) { + sb.append(arrayDecorations); + } + + sb.append(")"); + sb.append(this.getParameterListString(fd, false, writeEnabled, monitor)); + DataType baseReturnType = this.getBaseDataType(returnType); + if (baseReturnType instanceof FunctionDefinition) { + return this.getFunctionPointerString((FunctionDefinition) baseReturnType, sb.toString(), returnType, writeEnabled, + monitor); + } else { + String var10000 = returnType.getDisplayName(); + return var10000 + " " + sb.toString(); + } + } + + private String getParameterListString(FunctionDefinition fd, boolean includeParamNames, boolean writeEnabled, + TaskMonitor monitor) throws IOException, CancelledException { + StringBuilder buf = new StringBuilder(); + buf.append("("); + boolean hasVarArgs = fd.hasVarArgs(); + ParameterDefinition[] parameters = fd.getArguments(); + int n = parameters.length; + + for (int i = 0; i < n; ++i) { + ParameterDefinition param = parameters[i]; + String paramName = includeParamNames ? param.getName() : null; + DataType dataType = param.getDataType(); + if (writeEnabled) { + this.write(dataType, monitor); + } + + String argument = this.getTypeDeclaration(paramName, dataType, param.getLength(), writeEnabled, monitor); + buf.append(argument); + if (i < n - 1 || hasVarArgs) { + buf.append(", "); + } + } + + if (hasVarArgs) { + buf.append("..."); + } + + if (n == 0 && !hasVarArgs) { + buf.append(VoidDataType.dataType.getName()); + } + + buf.append(")"); + return buf.toString(); + } +} diff --git a/scripts/re3lib/DecompileCache.java b/scripts/re3lib/DecompileCache.java new file mode 100644 index 00000000..a84f10ef --- /dev/null +++ b/scripts/re3lib/DecompileCache.java @@ -0,0 +1,33 @@ +package re3lib; +import java.util.Hashtable; + +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.program.model.listing.Function; +import ghidra.util.task.TaskMonitor; + +public class DecompileCache { + private static final int TIMEOUT = 10000; + + Hashtable cache = new Hashtable<>(); + DecompInterface decomp; + TaskMonitor monitor; + + public DecompileCache(DecompInterface decomp, TaskMonitor monitor) { + this.decomp = decomp; + this.monitor = monitor; + } + + public DecompileResults get(Function function) { + return cache.get(function); + } + + public DecompileResults getOrInsert(Function function) { + DecompileResults res = cache.get(function); + if (res == null) { + res = decomp.decompileFunction(function, TIMEOUT, monitor); + cache.put(function, res); + } + return res; + } +} \ No newline at end of file diff --git a/scripts/re3lib/PCallTracer.java b/scripts/re3lib/PCallTracer.java new file mode 100644 index 00000000..d1ef5309 --- /dev/null +++ b/scripts/re3lib/PCallTracer.java @@ -0,0 +1,87 @@ +package re3lib; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.pcode.HighFunction; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.PcodeOpAST; +import ghidra.program.model.pcode.Varnode; + +public class PCallTracer { + public class QueueItem { + Function function; + int depth; + + QueueItem(Function function, int depth) { + this.function = function; + this.depth = depth; + } + } + + public List out = new ArrayList<>(); + public boolean trace = false; + List queue = new ArrayList<>(); + HashSet
visited = new HashSet<>(); + + DecompileCache decomp; + GhidraScript script; + + public PCallTracer() { + this.script = RecompileConfig.INSTANCE.script; + this.decomp = RecompileConfig.INSTANCE.decompCache; + } + + public void setBlacklist(HashSet
blacklist) { + this.visited = new HashSet<>(blacklist); + } + + void visit(HighFunction highFunction, int depth) { + Iterator opIter = highFunction.getPcodeOps(); + while (opIter.hasNext()) { + PcodeOpAST op = opIter.next(); + if (op.getOpcode() == PcodeOp.CALL) { + Varnode target = op.getInput(0); + if (target.isAddress()) { + Address callAddr = target.getAddress(); + Function calledFunction = script.getFunctionAt(callAddr); + if (calledFunction == null) { + + script.println("PCallTracer, called function not found: " + op.toString() + " - " + + highFunction.getFunction().getName()); + continue; + } + if (!visited.contains(calledFunction.getEntryPoint())) { + queue.add(new QueueItem(calledFunction, depth + 1)); + } + } + } + } + } + + void visit(Function function, int depth) { + if (!visited.contains(function.getEntryPoint())) { + visited.add(function.getEntryPoint()); + if (trace) { + // script.println("PCallTracer, visiting " + function.getName() + " (depth:" + depth + ")"); + } + DecompileResults decompRes = decomp.getOrInsert(function); + visit(decompRes.getHighFunction(), depth); + out.add(function); + } + } + + public void traceCalls(Function inFunction) { + queue.add(new QueueItem(inFunction, 0)); + while (queue.size() > 0) { + QueueItem item = queue.remove(0); + visit(item.function, item.depth); + } + } +} \ No newline at end of file diff --git a/scripts/re3lib/RecompileConfig.java b/scripts/re3lib/RecompileConfig.java new file mode 100644 index 00000000..4baf5bdc --- /dev/null +++ b/scripts/re3lib/RecompileConfig.java @@ -0,0 +1,60 @@ +package re3lib; + +import java.io.File; + +import generic.jar.ResourceFile; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.script.GhidraScript; +import ghidra.program.flatapi.FlatProgramAPI; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; + +public class RecompileConfig { + private static final String RECOMPILE_PREFIX = "game_re"; + + // Version control project root + public final String rootDir; + // The output directory for the recompiled game + public final String outputDir; + public final String typeBlacklistPath; + public final String functionBlacklistPath; + // The static memory block + public final Address staticMemoryBlockStart; + public final Address staticMemoryBlockEnd; + + // The automatically decompiled files + public final File dirDecompAuto; + // The manually decompiled files (will not be overwritten by the auto + // decompiler) + public final File dirDecompFix; + + public final Program currentProgram; + public final DecompileCache decompCache; + + public final GhidraScript script; + + public static RecompileConfig INSTANCE; + + public RecompileConfig(GhidraScript script) { + staticMemoryBlockStart = script.getCurrentProgram().getAddressFactory().getAddress("00597000"); + staticMemoryBlockEnd = script.getCurrentProgram().getAddressFactory().getAddress("00843fff"); + + this.script = script; + + rootDir = new File(script.getSourceFile().getAbsolutePath()).getParentFile().getParentFile().toString(); + outputDir = new File(rootDir, RECOMPILE_PREFIX).toString(); + script.println("Output path: " + outputDir); + + typeBlacklistPath = new File(outputDir, "type_blacklist.txt").toString(); + functionBlacklistPath = new File(outputDir, "function_blacklist.txt").toString(); + + dirDecompAuto = new File(outputDir, "gh_auto"); + dirDecompFix = new File(outputDir, "gh_fix"); + + currentProgram = script.getCurrentProgram(); + + DecompInterface decomp = new DecompInterface(); + decomp.openProgram(currentProgram); + decompCache = new DecompileCache(decomp, script.getMonitor()); + } +} diff --git a/scripts/re3lib/Utils.java b/scripts/re3lib/Utils.java new file mode 100644 index 00000000..9bf585c4 --- /dev/null +++ b/scripts/re3lib/Utils.java @@ -0,0 +1,70 @@ +package re3lib; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Scanner; + +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; + +public class Utils { + public static HashSet loadStructBlacklist(String path) { + File file = new File(path); + HashSet structBlacklist = new HashSet<>(); + try (Scanner scanner = new Scanner(file)) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + structBlacklist.add(line.trim()); + } + } catch (FileNotFoundException e) { + return null; + } + return structBlacklist; + } + + public static void saveStructBlacklist(HashSet structBlacklist, String path) throws Exception { + String[] arr = structBlacklist.toArray(new String[0]); + Arrays.sort(arr); + File file = new File(path); + try (PrintWriter writer = new PrintWriter(file)) { + for (String structName : arr) { + writer.println(structName); + } + } + } + + public static HashSet
loadFunctionBlacklist(String path) { + GhidraScript script = RecompileConfig.INSTANCE.script; + HashSet
fnBlacklist = new HashSet<>(); + File blacklistFile = new File(path); + try (Scanner scanner = new Scanner(blacklistFile)) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + // Strip comment + String line1 = line.split("//")[0].trim(); + // Deserialize address + Address addr = RecompileConfig.INSTANCE.currentProgram.getAddressFactory().getAddress(line1); + fnBlacklist.add(addr); + } + script.println("Loaded blacklist with " + fnBlacklist.size() + " entries"); + } catch (FileNotFoundException e) { + script.println("No blacklist found"); + } + return fnBlacklist; + } + + public static void saveFunctionBlacklist(HashSet
fnBlacklist, String path) { + GhidraScript script = RecompileConfig.INSTANCE.script; + File blacklistFile = new File(path); + try (PrintWriter writer = new PrintWriter(blacklistFile)) { + for (Address addr : fnBlacklist) { + writer.println(addr.toString() + " // " + script.getFunctionAt(addr).getName()); + } + } catch (FileNotFoundException e) { + script.println("Error saving blacklist: " + e.getMessage()); + } + } +}