sots-re/ghidra/scripts/TechFx.java

117 lines
12 KiB
Java

import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.scalar.*;
import ghidra.program.model.lang.*;
import ghidra.app.decompiler.*;
import java.util.*;
import java.util.regex.*;
import java.io.*;
// Tech-effects recon: ServerPlayer vtable callback, 196-entry tech table, readers of per-species flag words, part-B decompiles.
public class TechFx extends GhidraScript {
DecompInterface decomp; Memory mem; ReferenceManager rm;
static final String OUT = "/tmp/techfx";
String cstr(Address a) { try { byte[] b = new byte[80]; mem.getBytes(a, b); int i = 0; while (i < 80 && b[i] != 0 && (b[i]&0xff) >= 0x20 && (b[i]&0xff) < 0x7f) i++; if (i >= 1 && i < 80 && b[i] == 0) return new String(b, 0, i, "ISO-8859-1"); } catch (Exception e) {} return null; }
String constAt(long a) {
try { Address ad = toAddr(a); int iv = mem.getInt(ad); long lv = mem.getLong(ad);
float f = Float.intBitsToFloat(iv); double d = Double.longBitsToDouble(lv);
String s = cstr(ad); if (s != null) return "\"" + s + "\"";
StringBuilder sb = new StringBuilder(); sb.append("i=" + iv);
if (!Float.isNaN(f) && Math.abs(f) < 1e12 && (Math.abs(f) > 1e-7 || f == 0)) sb.append(" f=" + f);
if (!Double.isNaN(d) && Math.abs(d) < 1e12 && (Math.abs(d) > 1e-7 || d == 0)) sb.append(" d=" + d);
return sb.toString(); } catch (Exception e) { return "?"; }
}
String subst(String c) {
Matcher m = Pattern.compile("&?(DAT|PTR_s_|s_[A-Za-z0-9_]*|PTR_DAT|u_[A-Za-z0-9_]*|_DAT|PTR_PTR|_PTR)_([0-9a-f]{8})").matcher(c);
StringBuffer sb = new StringBuffer();
while (m.find()) { long a = Long.parseLong(m.group(2), 16); Address ad = toAddr(a); String s = cstr(ad); String rep = m.group(0);
if (s != null && s.length() <= 64 && !m.group(1).equals("PTR_DAT")) rep = "\"" + s + "\"";
else if (a >= 0x9dd000L && a < 0xb40000L) { rep = m.group(0) + "/*" + constAt(a) + "*/"; }
m.appendReplacement(sb, Matcher.quoteReplacement(rep)); }
m.appendTail(sb); return sb.toString();
}
String decompRaw(Function f) { try { DecompileResults res = decomp.decompileFunction(f, 600, monitor); if (res == null || !res.decompileCompleted()) return "[decompile failed]"; return res.getDecompiledFunction().getC(); } catch (Exception e) { return "[exception " + e.getMessage() + "]"; } }
Set<Long> done = new HashSet<>();
void dumpFunc(Function f, PrintWriter idx, boolean disasm) throws Exception {
if (f == null || done.contains(f.getEntryPoint().getOffset())) return;
done.add(f.getEntryPoint().getOffset());
String t = String.format("%08x", f.getEntryPoint().getOffset());
PrintWriter w = new PrintWriter(new FileWriter(OUT + "/" + t + ".c"));
w.println("// " + f.getName(true) + " @ " + f.getEntryPoint() + " size=" + f.getBody().getNumAddresses());
StringBuilder calls = new StringBuilder(); for (Function cf : f.getCalledFunctions(monitor)) calls.append(cf.getName(true) + "@" + cf.getEntryPoint() + " ");
w.println("// CALLS: " + calls);
StringBuilder callers = new StringBuilder(); for (Function cf : f.getCallingFunctions(monitor)) callers.append(cf.getName(true) + "@" + cf.getEntryPoint() + " ");
w.println("// CALLERS: " + callers);
w.println(subst(decompRaw(f)));
if (disasm) { w.println("// ---- DISASM ----"); Listing l = currentProgram.getListing(); InstructionIterator ii = l.getInstructions(f.getBody(), true);
while (ii.hasNext()) { Instruction in = ii.next(); StringBuilder ann = new StringBuilder();
for (Reference r : in.getReferencesFrom()) { long ta = r.getToAddress().getOffset(); if (ta >= 0x9dd000L && ta < 0xb40000L) { Symbol s = currentProgram.getSymbolTable().getPrimarySymbol(r.getToAddress()); ann.append(" ; " + (s != null ? s.getName() : String.format("%08x", ta)) + "=" + constAt(ta)); } else if (r.getReferenceType().isCall()) { Function cf = getFunctionAt(r.getToAddress()); if (cf != null) ann.append(" ; ->" + cf.getName(true)); } }
w.println(String.format("%08x %-40s%s", in.getAddress().getOffset(), in.toString(), ann)); } }
w.close();
idx.println("decompiled " + t + " " + f.getName(true) + " size=" + f.getBody().getNumAddresses()); idx.flush();
}
String readersOf(Address ga) { Set<String> seen = new TreeSet<>(); ReferenceIterator gi = rm.getReferencesTo(ga); while (gi.hasNext()) { Reference gr = gi.next(); Function rf = getFunctionContaining(gr.getFromAddress()); seen.add(rf == null ? "(nofunc " + gr.getFromAddress() + ")" : rf.getName(true) + "@" + rf.getEntryPoint()); } return String.join(" ", seen); }
@Override public void run() throws Exception {
mem = currentProgram.getMemory(); rm = currentProgram.getReferenceManager();
decomp = new DecompInterface(); decomp.openProgram(currentProgram);
new File(OUT).mkdirs();
PrintWriter idx = new PrintWriter(new FileWriter(OUT + "/index.txt"));
PrintWriter rep = new PrintWriter(new FileWriter(OUT + "/report.txt"));
// 1. ServerPlayer vtable
long vt = 0x00a327a4L;
rep.println("== ServerPlayer vtable 0x00a327a4 ==");
List<Function> vfns = new ArrayList<>();
for (int i = 0; i < 8; i++) { int p = mem.getInt(toAddr(vt + 4*i)); Function f = getFunctionAt(toAddr(p & 0xffffffffL)); rep.println(String.format("slot %d (+0x%x): %08x %s", i, 4*i, p, f == null ? "?" : f.getName(true) + " size=" + f.getBody().getNumAddresses())); if (f != null) vfns.add(f); }
Function cb = getFunctionAt(toAddr(mem.getInt(toAddr(vt + 0x10)) & 0xffffffffL));
if (cb != null) { dumpFunc(cb, idx, true);
for (Function c1 : cb.getCalledFunctions(monitor)) { if (c1.getBody().getNumAddresses() < 30000) dumpFunc(c1, idx, false);
for (Function c2 : c1.getCalledFunctions(monitor)) if (c2.getBody().getNumAddresses() < 6000 && c2.getBody().getNumAddresses() > 40) dumpFunc(c2, idx, false); } }
for (Function f : vfns) if (f.getBody().getNumAddresses() < 30000) dumpFunc(f, idx, false);
// 2. 0x0058b870 and the 196-entry table
Function ld = getFunctionAt(toAddr(0x0058b870L)); dumpFunc(ld, idx, false);
rep.println("== data refs from FUN_0058b870 ==");
Set<Long> cands = new TreeSet<>();
if (ld != null) { InstructionIterator ii = currentProgram.getListing().getInstructions(ld.getBody(), true);
while (ii.hasNext()) { Instruction in = ii.next(); for (Reference r : in.getReferencesFrom()) { long ta = r.getToAddress().getOffset(); if (ta >= 0x9dd000L && ta < 0xb40000L && r.getReferenceType().isData()) { rep.println(String.format(" %08x -> %08x %s", in.getAddress().getOffset(), ta, constAt(ta))); cands.add(ta); } }
for (int oi = 0; oi < in.getNumOperands(); oi++) for (Object o : in.getOpObjects(oi)) if (o instanceof Scalar) { long v = ((Scalar)o).getUnsignedValue(); if (v >= 0xad9000L && v < 0xb40000L) { rep.println(String.format(" %08x imm %08x", in.getAddress().getOffset(), v)); cands.add(v); } } } }
PrintWriter tw = new PrintWriter(new FileWriter(OUT + "/techtable196.txt"));
List<String> names = new ArrayList<>();
for (int i = 0; i < 196; i++) { long a = 0x009ff9e4L + 8*i; int p = mem.getInt(toAddr(a)); int q = mem.getInt(toAddr(a+4)); String s = (p > 0x9dd000 && p < 0xad9000) ? cstr(toAddr(p & 0xffffffffL)) : null; names.add(s == null ? "?" : s); tw.println(String.format("%3d %08x name=%s second=%08x(%d)", i, a, s, q, q)); }
tw.close();
// Try candidates as base of TechDef*[196]: score by xref count
rep.println("== candidate array bases ==");
long best = 0; int bestScore = -1;
for (long c : cands) { int score = 0; for (int i = 0; i < 196; i++) { ReferenceIterator gi = rm.getReferencesTo(toAddr(c + 4*i)); while (gi.hasNext()) { gi.next(); score++; } } rep.println(String.format(" %08x xrefs-over-196-slots=%d", c, score)); if (score > bestScore) { bestScore = score; best = c; } }
rep.println("best base " + String.format("%08x", best));
PrintWriter rw = new PrintWriter(new FileWriter(OUT + "/techreaders.txt"));
Set<Function> readerFns = new LinkedHashSet<>();
if (best != 0) for (int i = 0; i < 196; i++) { Address ea = toAddr(best + 4*i); Set<String> seen = new TreeSet<>(); ReferenceIterator gi = rm.getReferencesTo(ea); while (gi.hasNext()) { Reference gr = gi.next(); Function rf = getFunctionContaining(gr.getFromAddress()); if (rf != null) { readerFns.add(rf); seen.add(rf.getName(true) + "@" + rf.getEntryPoint() + "(" + String.format("%08x", gr.getFromAddress().getOffset()) + ")"); } else seen.add("(nofunc " + gr.getFromAddress() + ")"); } rw.println(String.format("%3d %08x %-16s %s", i, best + 4*i, names.get(i), String.join(" ", seen))); }
rw.close();
for (Function f : readerFns) if (f.getBody().getNumAddresses() < 40000 && !f.getEntryPoint().equals(toAddr(0x0058b870L))) dumpFunc(f, idx, false);
// 3. instruction scan for ServerPlayer field displacements
long[] disp = {0x348,0x34c,0x350,0x354,0x358,0x35c,0x360,0x364,0x224,0x228,0x22c,0x124,0x128,0x12c,0xc0,0xc4,0x104,0x160,0xd0,0xdc,0xe0};
Map<Long, Map<String,Integer>> hits = new TreeMap<>();
for (long d : disp) hits.put(d, new TreeMap<>());
InstructionIterator all = currentProgram.getListing().getInstructions(true);
while (all.hasNext()) { Instruction in = all.next(); for (int oi = 0; oi < in.getNumOperands(); oi++) { if ((in.getOperandType(oi) & OperandType.ADDRESS) == 0 && (in.getOperandType(oi) & OperandType.DYNAMIC) == 0) continue; boolean hasReg = false; Scalar sc = null; for (Object o : in.getOpObjects(oi)) { if (o instanceof Register) hasReg = true; if (o instanceof Scalar) sc = (Scalar)o; } if (!hasReg || sc == null) continue; long v = sc.getSignedValue(); if (hits.containsKey(v)) { Function f = getFunctionContaining(in.getAddress()); String k = f == null ? "(nofunc)" : f.getName(true) + "@" + f.getEntryPoint(); hits.get(v).merge(k, 1, Integer::sum); } } }
PrintWriter dw = new PrintWriter(new FileWriter(OUT + "/dispscan.txt"));
for (long d : disp) { dw.println(String.format("== disp 0x%x ==", d)); for (Map.Entry<String,Integer> e : hits.get(d).entrySet()) dw.println(" " + e.getKey() + " x" + e.getValue()); }
dw.close();
// dump readers of 0x348..0x364 that are strategy-side (0x0074xxxx..0x0089xxxx) and small
for (long d : new long[]{0x348,0x34c,0x350,0x354,0x358,0x35c,0x360,0x364}) for (String k : hits.get(d).keySet()) { Matcher m = Pattern.compile("@([0-9a-f]{8})").matcher(k); if (m.find()) { long a = Long.parseLong(m.group(1), 16); if (a >= 0x740000L && a < 0x8a0000L) { Function f = getFunctionAt(toAddr(a)); if (f != null && f.getBody().getNumAddresses() < 12000) dumpFunc(f, idx, false); } } }
// 4. part-B functions
String[] partB = {"00818600","007c0a50","0074d4f0","00863030","00705510","005876c0","00747ae0","007505b0","0080db10","00754220","0074b230","00536fb0","00537140","0074ab20","00746910","0080dd10","0080dd20","0074c680","0074f1e0","007d92a0","00705280","00705940","00817f90","0080dd30"};
String[] partBdis = {"00818600","007505b0","0074d4f0","0080dd10","0080dd20","00747ae0","00705510","0074c680"};
Set<String> disSet = new HashSet<>(Arrays.asList(partBdis));
for (String t : partB) { done.remove(Long.parseLong(t, 16)); dumpFunc(getFunctionAt(toAddr(Long.parseLong(t, 16))), idx, disSet.contains(t)); }
rep.close(); idx.close(); decomp.dispose(); println("done");
}
}