363 lines
55 KiB
Java
363 lines
55 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.data.*;
|
|
import ghidra.app.decompiler.*;
|
|
import java.util.*;
|
|
import java.util.regex.*;
|
|
import java.io.*;
|
|
|
|
// Write-back: struct data types from findings/objects/struct-recovery.md, function renames (serializers, affinity,
|
|
// turn/main-loop spine), plate comments, net-message registry labels; then verification decompiles.
|
|
public class WriteBack extends GhidraScript {
|
|
DataTypeManager dtm; SymbolTable st; PrintWriter log; DecompInterface decomp; Memory mem;
|
|
Map<String, DataType> types = new HashMap<String, DataType>();
|
|
static final String DOC = "Source: /srv/re-lab/notes/findings/objects/struct-recovery.md (save-field xref recovery, 2026-09-07)";
|
|
static final String DOC2 = "Source: /srv/re-lab/handoff/ghidra-writeback-and-spine.md (turn/main-loop spine, 2026-09-07)";
|
|
CategoryPath CAT = new CategoryPath("/SOTS");
|
|
|
|
// ---------- type helpers ----------
|
|
DataType prim(String k) {
|
|
if (k.equals("int")) return IntegerDataType.dataType;
|
|
if (k.equals("uint")) return UnsignedIntegerDataType.dataType;
|
|
if (k.equals("short")) return ShortDataType.dataType;
|
|
if (k.equals("i64")) return LongLongDataType.dataType;
|
|
if (k.equals("float")) return FloatDataType.dataType;
|
|
if (k.equals("double")) return DoubleDataType.dataType;
|
|
if (k.equals("bool")) return BooleanDataType.dataType;
|
|
if (k.equals("i8")) return SignedByteDataType.dataType;
|
|
if (k.equals("u8")) return ByteDataType.dataType;
|
|
if (k.equals("ptr")) return new PointerDataType(VoidDataType.dataType);
|
|
if (k.equals("pfn")) return new PointerDataType(VoidDataType.dataType);
|
|
if (k.endsWith("*")) { DataType t = types.get(k.substring(0, k.length()-1)); return new PointerDataType(t == null ? VoidDataType.dataType : t); }
|
|
Matcher m = Pattern.compile("^(.+)\\[(\\d+)\\]$").matcher(k);
|
|
if (m.matches()) { DataType b = prim(m.group(1)); int n = Integer.parseInt(m.group(2)); return new ArrayDataType(b, n, b.getLength()); }
|
|
DataType t = types.get(k); if (t != null) return t;
|
|
throw new RuntimeException("unknown type " + k);
|
|
}
|
|
Structure mk(String name, int size) throws Exception {
|
|
String nm = DataUtilities.isValidDataTypeName(name) ? name : name.replace("::", "_");
|
|
StructureDataType s = new StructureDataType(CAT, nm, size, dtm);
|
|
Structure r = (Structure) dtm.addDataType(s, DataTypeConflictHandler.REPLACE_HANDLER);
|
|
types.put(name, r); log.println(" type " + r.getPathName() + " size=" + r.getLength());
|
|
return r;
|
|
}
|
|
Structure opaque(String name) throws Exception { // forward-declared class, only used through pointers
|
|
String nm = DataUtilities.isValidDataTypeName(name) ? name : name.replace("::", "_");
|
|
StructureDataType s = new StructureDataType(CAT, nm, 0, dtm); s.setDescription("opaque (only referenced by pointer)");
|
|
Structure r = (Structure) dtm.addDataType(s, DataTypeConflictHandler.REPLACE_HANDLER); types.put(name, r); return r;
|
|
}
|
|
void fields(Structure s, Object[][] rows) {
|
|
for (Object[] r : rows) {
|
|
int off = ((Number) r[0]).intValue(); DataType dt = prim((String) r[1]); String name = (String) r[2]; String cmt = r.length > 3 ? (String) r[3] : null;
|
|
if (off + dt.getLength() > s.getLength()) { log.println(" !! " + s.getName() + "." + name + " @0x" + Integer.toHexString(off) + " exceeds size"); continue; }
|
|
try { s.replaceAtOffset(off, dt, dt.getLength(), name, cmt); }
|
|
catch (Exception e) { log.println(" !! " + s.getName() + "." + name + " @0x" + Integer.toHexString(off) + ": " + e.getMessage()); }
|
|
}
|
|
}
|
|
Structure fill(String name, int size, String desc, Object[][] rows) throws Exception {
|
|
Structure s = types.containsKey(name) ? (Structure) types.get(name) : mk(name, size);
|
|
if (s.getLength() != size) { s = mk(name, size); }
|
|
s.setDescription(desc + "\n" + DOC); fields(s, rows); return s;
|
|
}
|
|
// shifted copy for serializer 'this' (= object + colOff)
|
|
Structure serView(String name, int colOff) throws Exception {
|
|
Structure src = (Structure) types.get(name);
|
|
Structure v = mk(name + "_ser" + colOff, src.getLength() - colOff);
|
|
v.setDescription("Serializer view of " + name + ": this = object + 0x" + Integer.toHexString(colOff) + " (IStreamable sub-object). Use only to read " + name + "::Read/Write.");
|
|
for (DataTypeComponent c : src.getDefinedComponents()) { if (c.getOffset() < colOff) continue; try { v.replaceAtOffset(c.getOffset() - colOff, c.getDataType(), c.getLength(), c.getFieldName(), c.getComment()); } catch (Exception e) {} }
|
|
return v;
|
|
}
|
|
|
|
// ---------- symbol helpers ----------
|
|
Namespace ns(String path) throws Exception {
|
|
Namespace cur = currentProgram.getGlobalNamespace();
|
|
for (String part : path.split("::")) {
|
|
Namespace n = st.getNamespace(part, cur);
|
|
if (n == null) n = st.createClass(cur, part, SourceType.USER_DEFINED);
|
|
cur = n;
|
|
}
|
|
return cur;
|
|
}
|
|
void fn(long addr, String qualified, String plate) {
|
|
try {
|
|
Function f = getFunctionAt(toAddr(addr));
|
|
if (f == null) { log.println(" !! no function at " + Long.toHexString(addr)); return; }
|
|
int i = qualified.lastIndexOf("::");
|
|
String name = i < 0 ? qualified : qualified.substring(i + 2);
|
|
Namespace n = i < 0 ? currentProgram.getGlobalNamespace() : ns(qualified.substring(0, i));
|
|
f.setParentNamespace(n);
|
|
f.setName(name, SourceType.USER_DEFINED);
|
|
if (plate != null) f.setComment(plate);
|
|
log.println(" fn " + toAddr(addr) + " -> " + qualified);
|
|
} catch (Exception e) { log.println(" !! rename " + Long.toHexString(addr) + " " + qualified + ": " + e.getMessage()); }
|
|
}
|
|
void lbl(long addr, String name, String cmt) {
|
|
try { Address a = toAddr(addr); createLabel(a, name, true, SourceType.USER_DEFINED); if (cmt != null) setEOLComment(a, cmt); log.println(" label " + a + " " + name); }
|
|
catch (Exception e) { log.println(" !! label " + Long.toHexString(addr) + ": " + e.getMessage()); }
|
|
}
|
|
void retypeThis(long addr, DataType ptr) {
|
|
try { Function f = getFunctionAt(toAddr(addr));
|
|
if (f.getParameterCount() > 0) f.getParameter(0).setDataType(ptr, SourceType.USER_DEFINED);
|
|
else { List<Variable> ps = new ArrayList<Variable>(); ps.add(new ParameterImpl("this", ptr, currentProgram)); ps.add(new ParameterImpl("stream", new PointerDataType(VoidDataType.dataType), currentProgram));
|
|
f.replaceParameters(ps, Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.USER_DEFINED); f.setCallingConvention("__thiscall"); }
|
|
log.println(" retyped this of " + f.getName() + " -> " + ptr.getName());
|
|
} catch (Exception e) { log.println(" !! retype " + Long.toHexString(addr) + ": " + e.getMessage()); }
|
|
}
|
|
String cstr(Address a, int max) {
|
|
try { byte[] b = new byte[max]; int got = mem.getBytes(a, b); int i = 0;
|
|
while (i < got && b[i] != 0 && (b[i]&0xff) >= 0x20 && (b[i]&0xff) < 0x7f) i++;
|
|
if (i >= 1 && i < got && b[i] == 0) return new String(b, 0, i, "ISO-8859-1"); } catch (Exception e) {}
|
|
return null;
|
|
}
|
|
void verify(long addr, String outName) throws Exception {
|
|
Function f = getFunctionAt(toAddr(addr));
|
|
DecompileResults res = decomp.decompileFunction(f, 240, monitor);
|
|
PrintWriter w = new PrintWriter(new FileWriter("/tmp/spine/" + outName));
|
|
if (res == null || !res.decompileCompleted()) { w.println("[decompile failed]"); w.close(); return; }
|
|
String c = res.getDecompiledFunction().getC();
|
|
Matcher m = Pattern.compile("&?(DAT|PTR_s_|s_[A-Za-z0-9_]*|PTR_DAT|u_[A-Za-z0-9_]*)_([0-9a-f]{8})").matcher(c);
|
|
StringBuffer sb = new StringBuffer();
|
|
while (m.find()) { Address a = toAddr(Long.parseLong(m.group(2), 16)); String s = cstr(a, 64); String rep = m.group(0); if (s != null && s.length() <= 60) rep = "\"" + s + "\""; m.appendReplacement(sb, Matcher.quoteReplacement(rep)); }
|
|
m.appendTail(sb); w.println(sb); w.close(); log.println(" verify decompile written " + outName + " (" + sb.length() + " chars)");
|
|
}
|
|
|
|
@Override
|
|
public void run() throws Exception {
|
|
dtm = currentProgram.getDataTypeManager(); st = currentProgram.getSymbolTable(); mem = currentProgram.getMemory();
|
|
new File("/tmp/spine").mkdirs();
|
|
log = new PrintWriter(new FileWriter("/tmp/spine/writeback.log"));
|
|
log.println("==== TYPES ====");
|
|
// --- std / Mars primitives ---
|
|
fill("Mars::Vector3", 12, "3 floats", new Object[][]{{0,"float","x"},{4,"float","y"},{8,"float","z"}});
|
|
Structure bx = mk("std::string::_Bxty", 16); bx.setDescription("SSO buffer / heap pointer union");
|
|
fields(bx, new Object[][]{{0,"ptr","_Ptr","heap pointer when _Myres >= 16 (else the 16-byte SSO buffer starts here)"}});
|
|
fill("std::string", 0x1c, "MSVC10 basic_string<char>: _Bx@0 (SSO buf / _Ptr), _Mysize@0x10, _Myres@0x14, _Alval@0x18. Verified from Stream::WriteString (FUN_008b9d70): if (str->_Myres > 15) p = str->_Ptr.",
|
|
new Object[][]{{0,"std::string::_Bxty","_Bx"},{0x10,"uint","_Mysize"},{0x14,"uint","_Myres"},{0x18,"u8","_Alval"}});
|
|
fill("std::vector", 12, "MSVC10 vector<T>: _Myfirst/_Mylast/_Myend (element type noted in field comments)", new Object[][]{{0,"ptr","_Myfirst"},{4,"ptr","_Mylast"},{8,"ptr","_Myend"}});
|
|
fill("std::list", 8, "MSVC10 list<T>: _Myhead (sentinel node: next@0,prev@4,value@8), _Mysize", new Object[][]{{0,"ptr","_Myhead"},{4,"uint","_Mysize"}});
|
|
fill("std::map", 8, "MSVC10 map/set: _Myhead (node: left@0,parent@4,right@8,key@0xc,value@0x10.. ,isnil byte at tail), _Mysize", new Object[][]{{0,"ptr","_Myhead"},{4,"uint","_Mysize"}});
|
|
// opaque classes referenced by pointer
|
|
for (String o : new String[]{"Game::BuildQueue","Game::TechTree","Game::ShipDesign","Game::CommMessageContainer","Game::Tech","Game::FleetNameGenerator","Game::AIRebellion","Game::AIEncounterFlags",
|
|
"Game::ServerNodeGraph","Game::ServerTradeManager","Game::IServerSpyManager","Game::AttribMap","Game::SVScriptObject","Game::StarSystem","Game::Plague","Game::DefenceLayout","Game::SpecialProjectImpl","Game::StrategyEvent","Mars::Stream"}) opaque(o);
|
|
// main structs first as empty shells so cross pointers resolve
|
|
Structure sys = mk("Game::ServerSystem", 0x2d8); Structure ply = mk("Game::ServerPlayer", 0x3e0); Structure flt = mk("Game::StarFleet", 0x120); Structure shp = mk("Game::StarShip", 0xb0); Structure srv = mk("Game::StrategyServer", 0x320);
|
|
// --- nested ---
|
|
fill("Game::PlayerColorID", 4, "index (-1 = custom rgb) + r,g,b. Write FUN_0053c080", new Object[][]{{0,"i8","index"},{1,"u8","r"},{2,"u8","g"},{3,"u8","b"}});
|
|
fill("Game::PopulationGroup", 0x18, "Read FUN_00536a80 / Write FUN_00536af0", new Object[][]{{0,"ptr","vptr"},{4,"int","PopT"},{8,"int","PopS"},{0x10,"i64","PopC"}});
|
|
fill("Game::Population", 0x14, "vector<PopulationGroup>; Read FUN_005390c0 / Write FUN_00537ef0 (PopNG count, PopG entries)", new Object[][]{{0,"ptr","vptr"},{4,"std::vector","groups","vector<PopulationGroup> (stride 0x18)"}});
|
|
fill("Game::Morale", 0x20, "int[7]; disk: mnsp then (msp,mv) sparse pairs. Read FUN_00744dd0 / Write FUN_00744ea0", new Object[][]{{0,"ptr","vptr"},{4,"int[7]","values"}});
|
|
fill("Game::MoraleEvent", 0x50, "Read FUN_007490b0 / Write FUN_007491b0", new Object[][]{{0,"ptr","vptr"},{4,"int","mid"},{8,"int","mtr"},{0xc,"int","mn"},{0x10,"int","mtp"},{0x14,"Game::Morale","mfx"},{0x34,"std::string","mdsc"}});
|
|
fill("Game::IndependenceInfo", 0x70, "Read FUN_00748df0 / Write FUN_00748ee0", new Object[][]{{0,"ptr","vptr"},{4,"int","indsp"},{8,"Game::PlayerColorID","indcl"},{0x1c,"std::string","indnm"},{0x38,"std::string","indav"},{0x54,"std::string","indba"}});
|
|
fill("Game::StarSystem::OutputRates", 0x1c, "POD; Read FUN_007472a0 / Write FUN_00745190. Disk order SRs,SRt,SRsc,SRtf,SRi,SRoh,SRnr", new Object[][]{{0,"float","SRt"},{4,"float","SRsc"},{8,"float","SRtf"},{0xc,"float","SRi"},{0x10,"float","SRoh"},{0x14,"float","SRs"},{0x18,"int","SRnr"}});
|
|
fill("Game::StarSystem::PlayerView", 0x9c, "per-player seen snapshot (map value in ServerSystem.NVs). Read FUN_00752af0 / Write FUN_007492d0", new Object[][]{{0,"ptr","vptr","0x00a201ac"},{8,"int","VTrn"},{0xc,"int","Pop"},{0x10,"Game::Population","Pop2"},{0x24,"float","Infra"},{0x28,"float","Suit"},{0x2c,"int","Res"},{0x30,"int","ARes2"},{0x34,"int","MRes"},{0x38,"bool","NoRebAI"},{0x3c,"int","pbon"},{0x40,"Game::Population","pbon2"},{0x54,"float","ibon"},{0x58,"int","TerrFl"}});
|
|
fill("Game::ShipBuildOrder", 0x18, "build-queue entry. Read FUN_00813770 / Write FUN_00813800", new Object[][]{{0,"ptr","vptr"},{4,"int","desID"},{8,"int","con"},{0xc,"int","sav"},{0x10,"int","conleft"},{0x14,"int","ordID"}});
|
|
fill("Game::DiplomacyStats", 0x24, "int16 counters (int32 on disk). Write FUN_00818cb0", new Object[][]{{0,"ptr","vptr"},{4,"int","other"},{8,"short","lastnap"},{0xa,"short","lastnapbty"},{0xc,"short","bknnap"},{0xe,"short","btynap"},{0x10,"short","lastally"},{0x12,"short","lastallybty"},{0x14,"short","bknally"},{0x16,"short","btyally"},{0x18,"short","lastcf"},{0x1a,"short","lastcfbty"},{0x1c,"short","bkncf"},{0x1e,"short","btycf"},{0x20,"short","deadhome"}});
|
|
fill("Game::PlayerReport", 0x30, "preps entry. Read FUN_008200a0 / Write FUN_00817480", new Object[][]{{0,"ptr","vptr"},{4,"int","oid"},{8,"int","pid"},{0xc,"int","flds"},{0x10,"int","sav"},{0x14,"int","home"},{0x18,"int","ncol"},{0x1c,"int","mpwr"},{0x20,"int","mcls"},{0x24,"int","mmsl"},{0x28,"int","nshp"},{0x2c,"int","nsat"}});
|
|
fill("Game::PlayerAlliances", 0x10, "Write FUN_006d2e10 (disk tag Team)", new Object[][]{{0,"int","ALid"},{4,"int","AL"},{8,"int","NA"},{0xc,"int","CF"}});
|
|
fill("Game::NodeRoute", 0x10, "Read FUN_006e2260 / Write FUN_006e22e0", new Object[][]{{0,"ptr","vptr"},{4,"int","nrp"},{8,"int","nrf"},{0xc,"int","nrt"}});
|
|
fill("Game::Waypoint", 0x1c, "Read FUN_00701860 / Write FUN_00700ed0", new Object[][]{{0,"ptr","vptr"},{4,"int","Wpt"},{8,"int","Tp"},{0xc,"Game::NodeRoute","nrt"}});
|
|
fill("Game::FlightPlan", 0x38, "Read FUN_00704c70 / Write FUN_00700f60", new Object[][]{{0,"ptr","vptr"},{4,"std::vector","wpts","vector<Waypoint> (stride 0x1c)"},{0x14,"float","FPsp2"},{0x18,"int","FPeta2"},{0x1c,"Mars::Vector3","FPogn2"},{0x28,"Mars::Vector3","FPdpos"},{0x34,"int","pnd"}});
|
|
fill("Game::ShipHealth", 0x10, "3 unnamed floats on disk. Write FUN_00813e50", new Object[][]{{0,"ptr","vptr"},{4,"float[3]","hp"}});
|
|
fill("Game::PrisonerHold", 0x18, "Read FUN_0056eb00 / Write FUN_0056ec00; counts[0]=PrMax, [2..8] per species", new Object[][]{{0,"ptr","vptr"},{0x14,"int*","counts"}});
|
|
fill("Game::EventStorage", 0x1c, "Write FUN_00825cc0", new Object[][]{{0,"ptr","vptr"},{4,"std::vector","Events","vector<TurnEvents>"},{0x14,"int","EvNxID"}});
|
|
fill("Game::CivilianRatios", 0x2c, "Write FUN_0082c740 (body not recovered)", new Object[][]{{0,"ptr","vptr"}});
|
|
fill("Game::ShipRecords", 0x44, "Write FUN_008176a0 (body not recovered)", new Object[][]{{0,"ptr","vptr"}});
|
|
fill("Game::FleetLayout", 0x24, "two vectors; HLay gate = either non-empty", new Object[][]{{0,"ptr","vptr"},{4,"std::vector","v0"},{0x14,"std::vector","v1"}});
|
|
fill("Game::SpyReport", 0x34, "four std::lists. Read FUN_008843d0 / Write FUN_00828ec0", new Object[][]{{0,"ptr","vptr"},{4,"std::list","defences","SpyReportDefences (count defc2)"},{0x10,"std::list","trade","SpyReportTrade (rtc)"},{0x1c,"std::list","events","SpyReportEvents (evc)"},{0x28,"std::list","techtree","SpyReportTechTree (ttc)"}});
|
|
// --- ServerSystem ---
|
|
fill("Game::ServerSystem", 0x2d8, "Game::ServerSystem : StarSystem : StarMapNode (+NetworkObject@0, IStreamable@8, HandleObject@0xc). Serializer this = obj+8. Read FUN_0075d4b0 / Write FUN_00749630.", new Object[][]{
|
|
{0,"ptr","vptr_StarSystem","0x00a200e4"},{4,"int","netId","NetworkObject id (handle id written by Stream::WriteHandleId)"},{8,"ptr","vptr_IStreamable","0x00a2043c; serializer this"},{0xc,"ptr","vptr_HandleObject"},{0x10,"Game::StrategyServer*","owner","owner->Players used as player table"},
|
|
{0x18,"Mars::Vector3","Pos"},{0x4c,"float","R"},{0x50,"float","G"},{0x54,"float","B"},{0x58,"float","A"},{0x5c,"int","Idx"},{0x60,"int","Size"},{0x64,"float","Suit"},{0x68,"int","Res"},{0x6c,"int","ARes2"},{0x70,"int","MRes"},{0x74,"int","TRes"},
|
|
{0x78,"bool[3]","haltv"},{0x7c,"float","OutMod"},{0x80,"int","TAcq"},{0x84,"int","TFAcq"},{0x88,"Game::StarSystem::OutputRates","Rts"},{0xa4,"Game::BuildQueue*","BQ"},{0xa8,"std::string","Name"},
|
|
{0xc4,"bool","Abdn"},{0xc5,"bool","Dstyd"},{0xc6,"bool","vnh"},{0xc7,"bool","vnd"},{0xc8,"bool","vnex3"},{0xc9,"bool","vnpex3"},{0xcc,"int","VFlags"},{0xd0,"int","EFlags"},{0xd4,"int","AFlags"},{0xd8,"int","FFlags"},{0xdc,"int","GFlags"},{0xe0,"int","MnRFlags"},{0xe4,"int","RfRFlags"},{0xe8,"int","ClkFlags"},
|
|
{0xf0,"i64","Bats2"},{0xf8,"i64","rcex"},{0x100,"Game::ServerPlayer*","PID","owner player (handle id on disk)"},{0x104,"Game::Population","dcs"},{0x118,"float","dsu"},{0x11c,"Game::Morale","cm"},{0x13c,"std::vector","cme2","vector<MoraleEvent>"},{0x14c,"Game::Morale","PvCM"},
|
|
{0x16c,"std::vector","Flts","vector<StarFleet*> (NumFlts/Flt)"},{0x17c,"float","RepCur"},{0x180,"float","RepMax"},{0x184,"int","EggScio"},{0x188,"bool","NoRebAI"},{0x189,"bool","PvNoRebAI"},{0x18c,"int","Pop"},{0x190,"float","Infra"},{0x194,"int","pbon"},{0x198,"float","ibon"},{0x19c,"int","TerrFl"},
|
|
{0x1a0,"Game::Population","Pop2"},{0x1b4,"Game::Population","pbon2"},{0x1c8,"Game::IndependenceInfo*","indi","hindi = (indi != NULL)"},{0x1cc,"std::vector","spies2","vector<int>"},{0x1dc,"int","rbfl"},{0x1e0,"bool","hsrg"},{0x1e4,"int[7]","adt","addiction table: nadct + sparse (ads,adt)"},
|
|
{0x200,"int","PvPop"},{0x204,"float","PvInfra"},{0x208,"float","PvSuit"},{0x20c,"int","PvRes"},{0x210,"int","PvARes2"},{0x214,"int","PvMRes"},{0x218,"Game::Population","PvPop2"},{0x238,"Game::StarFleet*","DefF"},{0x23c,"Game::StarFleet*","DefSF"},
|
|
{0x240,"std::vector","GFs","gates (NumGFs/GF)"},{0x250,"std::vector","SnF","stations (NumSnF/SnF)"},{0x260,"std::vector","MnF","monitors (NumMnF/MnF)"},{0x274,"std::map","NVO","colonies: key player idx -> {TShn i16@+2, OID@+4, isind@+8, indi IndependenceInfo@+0xc}"},{0x284,"std::map","NVE","key player idx -> {ETS i16@+2, Eid@+4}"},{0x294,"std::map","NVs","key player idx -> PlayerView (pview)"},
|
|
{0x2a8,"std::vector","Plgs","vector<Plague*> (NumPlgs2/PlgT/Plg)"},{0x2b8,"int","TnsOH"},{0x2bc,"int","TDst"},{0x2c4,"int","ntdev"},{0x2c8,"int","ltis"},{0x2cc,"int","rbtn"},{0x2d0,"int","rbfr"},{0x2d4,"int","rbwn"}});
|
|
// --- ServerPlayer ---
|
|
fill("Game::ServerPlayer", 0x3e0, "Game::ServerPlayer : StrategyPlayer (NetworkObject@0). IStreamable sub-object at +0x3a0 (serializer this = obj+0x3a0, most member offsets negative there). Read FUN_008804d0 / Write FUN_008563e0.", new Object[][]{
|
|
{0,"ptr","vptr","0x00a327a4"},{4,"int","netId"},{0x28,"int","PlyrIdx"},{0x2c,"Game::ServerSystem*","HomeSys"},{0x30,"std::vector","Own","vector<ServerPlayer*> (NumOwn/OwnId)"},{0x40,"std::string","PlryName"},{0x5c,"int","Species","0 Human .. 6 Morrigi"},{0x60,"Game::PlayerColorID","ClrID"},
|
|
{0x74,"std::string","Bdg"},{0x90,"std::string","Avt"},{0xac,"int","Team"},{0xb0,"float","IdealSuit"},{0xb4,"float","SuitTol"},{0xb8,"float","MaxOH"},{0xbc,"float","ResRate"},{0xc0,"float","ResMod"},{0xc4,"float","ResScl"},{0xd0,"float","TRM"},{0xd4,"int","TRA"},{0xd8,"int","TRP"},
|
|
{0xe4,"std::vector","Des","vector<ShipDesign*> (NumDes/DesID/Des)"},{0xf4,"Game::TechTree*","TechTree"},{0xf8,"bool","Elim"},{0xf9,"bool","bTurnDone_nonser","not serialized; tested by StrategyServer::OnPlayerEndTurn"},{0xfb,"bool","NPC"},{0xfc,"bool","RebAI"},{0xfd,"bool","ReqCL"},{0xfe,"bool","AIBn"},{0xff,"bool","CnTrd"},{0x100,"bool","CnRad"},{0x101,"bool","CnVItl"},{0x102,"bool","hgs"},{0x103,"bool","hadvs"},{0x104,"bool","harcc"},
|
|
{0x108,"float","pddm"},{0x10c,"float[3]","ConMod"},{0x118,"float[3]","SavMod"},{0x124,"float","OutMod"},{0x128,"float","RebOutMod"},{0x12c,"float","ScOutMod"},{0x130,"float","PopMod"},{0x134,"float","TerraMod"},{0x138,"bool","AMine"},{0x13c,"float","MinPure"},{0x140,"float","MinRate"},{0x144,"int","NGts"},{0x148,"int","PrGtTrf"},{0x14c,"int","GTraf"},
|
|
{0x150,"float","CstR"},{0x154,"float","CstE"},{0x158,"float","CstT"},{0x15c,"int","Maint"},{0x160,"float","shrm"},{0x164,"int","Status","also set by SNMSetPlayerStatus; 4 = turn done"},{0x168,"Game::PlayerAlliances","Team2","disk tag Team {ALid,AL,NA,CF}"},{0x178,"std::vector","Leg","vector<ShipDesign*> (NumLeg)"},{0x188,"int","PvSav"},{0x18c,"bool","PvMA"},
|
|
{0x19c,"int","HasDisc"},{0x1a0,"int","HasDiscSp"},{0x1a4,"int","HasDiscCl"},{0x1a8,"int","HasEnc"},{0x1ac,"int","HasEng"},{0x1b0,"Game::ShipRecords","ShipRecs"},{0x1f4,"std::vector","Ojvs","vector<Objective>"},{0x204,"std::vector","Nexp","vector<{int xid,xmin,xmax; float xper}> (16 B)"},{0x214,"std::vector","WeapXcl","vector<int>"},
|
|
{0x230,"std::vector","dipstats","vector<DiplomacyStats>"},{0x240,"Game::CommMessageContainer*","comms"},{0x244,"std::vector","preps","vector<PlayerReport>"},{0x254,"std::vector","odes","vector<ObservedDesign>"},{0x264,"std::vector","owep","vector<ObservedWeapon>"},{0x274,"std::vector","otch","vector<ObservedTech>"},
|
|
{0x284,"int","Sav"},{0x288,"int","HasImm"},{0x28c,"int","HasVac"},{0x290,"int","NPTrk"},{0x294,"Game::Tech*","ResT","current research (ResTNm = tech->name)"},{0x298,"Game::FleetNameGenerator*","FNG"},{0x29c,"Game::EventStorage","Events"},{0x2b8,"std::list","Nts","list<PlayerNotes> (NumNotes/Nts)"},
|
|
{0x2c4,"int","BnkWrn"},{0x2c8,"int","BnkTrn"},{0x2cc,"int","BnkEl"},{0x2d0,"int","BnkPr"},{0x2d8,"int","plcy"},{0x2dc,"std::string","pswd"},{0x2f8,"bool","Srn"},{0x2fc,"ptr","SrnTo","handle id on disk"},{0x300,"int","lboid"},{0x304,"int","lcid2"},{0x30c,"float","IncMod"},{0x310,"std::vector","aid","vector<PlayerAid>"},
|
|
{0x320,"std::vector","deflay","vector<DefenceLayout*> (ndeflay/deflay)"},{0x330,"bool","cdp"},{0x334,"Game::SpyReport*","spy2"},{0x338,"std::vector","rdt","vector<RaidTargets> (stride 0x20)"},{0x368,"int","aidf"},{0x370,"Game::CivilianRatios","civr"},{0x39c,"int","tnc","written as max(v,1)"},
|
|
{0x3a0,"ptr","vptr_IStreamable","0x00a32794; serializer this"},{0x3a4,"std::vector","PR","vector<{float PRm; int PRBt}>"},{0x3b4,"bool","ResErrRoll"},{0x3b5,"bool","cta"},{0x3b8,"Game::AIRebellion*","AIR","HasAIR = (AIR != NULL)"},{0x3bc,"Game::AIEncounterFlags*","AIEnf"},{0x3c0,"std::vector","Sprj","vector<SpecialProjectImpl*> (NSprj/SprjT/Sprj)"},{0x3d0,"int","NextPrjID"},{0x3d4,"int","lret"},{0x3dc,"int","nmeid"}});
|
|
// --- StarFleet ---
|
|
fill("Game::StarFleet", 0x120, "Game::StarFleet : StarMapNode (+NetworkObject@0, IStreamable@8, HandleObject@0xc). Serializer this = obj+8. Read FUN_00702470 / Write FUN_00701070.", new Object[][]{
|
|
{0,"ptr","vptr","0x00a1d608"},{4,"int","netId"},{8,"ptr","vptr_IStreamable","0x00a1d5f8"},{0xc,"ptr","vptr_HandleObject"},{0x10,"ptr","owner"},{0x18,"Mars::Vector3","Pos"},{0x4c,"Mars::Vector3","PrvPos"},{0x58,"Game::ServerPlayer*","PID"},{0x5c,"std::string","FtName"},{0x78,"bool","Perm"},
|
|
{0x7c,"Game::FleetLayout","Lay","HLay gate"},{0xa0,"Game::StarSystem*","LocID"},{0xa4,"std::vector","Ships","vector<StarShip*> (NShips/ShipID/Ship)"},{0xc4,"Game::FlightPlan","FPlan","HFPlan gate = wpts non-empty"},{0xfc,"int","FtTrans"},{0x100,"Mars::Vector3","FtOrig"},{0x10c,"int","FtFlg"},{0x110,"int","Ftae"},{0x114,"int","Ftpae"},{0x118,"int","FtEnc"},{0x11c,"int","FtMS"}});
|
|
// --- StarShip ---
|
|
fill("Game::StarShip", 0xb0, "Game::StarShip (NetworkObject@0, IStreamable@8). Serializer this = obj+8. Read FUN_00853fa0 / Write FUN_008291f0.", new Object[][]{
|
|
{0,"ptr","vptr","0x00a31418"},{4,"int","netId"},{8,"ptr","vptr_IStreamable","0x00a31408"},{0x10,"Game::ServerPlayer*","PlrID"},{0x14,"Game::ShipDesign*","Des","DesID = design->+0xa4"},{0x20,"float","Range"},{0x24,"Game::ShipHealth","Health"},{0x34,"int","MineCap"},{0x38,"std::vector","TH","vector<{float th,thm}> (NTH/TH/THM)"},
|
|
{0x48,"int","Plg"},{0x4c,"int","Act"},{0x50,"bool","Dep"},{0x51,"bool","Atq"},{0x5c,"int","LCT"},{0x60,"int","tsd"},{0x64,"Game::StarFleet*","FltID"},{0x68,"int","ConCap"},{0x6c,"float","RefCap"},{0x70,"float","RepCap"},{0x7c,"int","EncID"},{0x80,"Game::PrisonerHold","PrisH"},{0x98,"Game::BuildQueue*","BQ2","hbq gate"},{0x9c,"Game::Population*","pop","hsp gate"},{0xa0,"Game::Population*","ppop"},{0xa8,"int","atsp"},{0xac,"int","tblt"}});
|
|
// --- StrategyServer (partial; from Write FUN_0079fa70 + spine work) ---
|
|
fill("Game::StrategyServer", 0x320, "Game::StrategyServer (IStreamable@0 = serializer this, primary vftable 0x00a26034 @+4). Offsets from Write FUN_0079fa70 and the turn-spine functions; PARTIAL.", new Object[][]{
|
|
{0,"ptr","vptr_IStreamable","0x00a26084"},{4,"ptr","vptr","0x00a26034"},{8,"int","ModCount"},{0xc,"int","Frame","turn number; ++ in BeginProcessTurn"},{0x10,"int","GOTurn"},{0x14,"int","GameID"},{0x28,"std::string","GameName"},
|
|
{0x44,"std::vector","Systems","vector<ServerSystem*> (NumSys/SysID/Sys)"},{0x54,"std::vector","Players","vector<ServerPlayer*> (NumPlrs/PlayerID/Player)"},{0x64,"std::vector","Fleets","vector<StarFleet*> (NumFlts/FltID/Flt)"},{0x74,"std::vector","Acts","vector<obj*> (NumActs/Act handle ids)"},
|
|
{0x8c,"std::vector","NodeMapLines","stride 0x14; NMSz = count"},{0x9c,"int","NMLc"},{0xbc,"int","Map"},{0xc0,"float","IncMod"},{0xc4,"float","ResMod"},{0xc8,"bool","EnAl"},{0xc9,"bool","EnTm"},{0xfc,"float[7]","ISsu","per-species (ISsp name, ISsu value)"},{0x134,"std::string","KeyPath"},
|
|
{0x154,"Game::ServerNodeGraph*","NdGr2"},{0x158,"Game::ServerTradeManager*","trdmgr"},{0x15c,"Game::IServerSpyManager*","spymgr"},{0x164,"Game::AttribMap*","Attrib"},{0x170,"pfn","OnEventCallback","(*cb)(playerNetId, int eventType, StrategyEvent** ev); set in ctor FUN_007d78d0 from ctor arg; NULL -> 'OnEvent() called, but no callback function specified'"},
|
|
{0x1a0,"float","RandEncAdj"},{0x1b0,"ptr","listener","optional observer; vft+0x10(code,&args) called on player/fleet removal"},{0x1b4,"Game::SVScriptObject*","SvSctOb"},{0x1b8,"int","NPCm"},{0x1bc,"int","NPCo"},{0x1c0,"int","NPCi"},{0x1c4,"int","NPCv"},{0x1c8,"int","NPCa"},{0x1cc,"float","szadj"},{0x1d0,"float","rsadj"},{0x1d4,"float","suadj"},{0x1f8,"int","cmbtid"},
|
|
{0x2b8,"std::list","StrategyEvents","list<StrategyEvent*> pending events (node: next,prev,vptr@8,..,targetId@0x10,id2@0x14)"},{0x2ec,"bool","bRecordEvents","gate for the per-player removal lists at 0x2d4/0x2e0"},{0x2f1,"bool","bProcessingTurn","set 1 in BeginProcessTurn"},{0x318,"std::map","zds","zdsc/zdsi/zdst"}});
|
|
Structure sysV = serView("Game::ServerSystem", 8); Structure fltV = serView("Game::StarFleet", 8); Structure shpV = serView("Game::StarShip", 8);
|
|
|
|
log.println("\n==== FUNCTION RENAMES: serializers ====");
|
|
Object[][] ser = {
|
|
{0x0075d4b0L,"Game::ServerSystem::Read"},{0x00749630L,"Game::ServerSystem::Write"},{0x008804d0L,"Game::ServerPlayer::Read"},{0x008563e0L,"Game::ServerPlayer::Write"},
|
|
{0x00702470L,"Game::StarFleet::Read"},{0x00701070L,"Game::StarFleet::Write"},{0x00853fa0L,"Game::StarShip::Read"},{0x008291f0L,"Game::StarShip::Write"},
|
|
{0x007d27a0L,"Game::StrategyServer::Read"},{0x0079fa70L,"Game::StrategyServer::Write"},{0x00727790L,"Game::StarMapNode::Read"},{0x00727820L,"Game::StarMapNode::Write"},
|
|
{0x00752af0L,"Game::StarSystem::PlayerView::Read"},{0x007492d0L,"Game::StarSystem::PlayerView::Write"},{0x007472a0L,"Game::StarSystem::OutputRates::Read"},{0x00745190L,"Game::StarSystem::OutputRates::Write"},
|
|
{0x005390c0L,"Game::Population::Read"},{0x00537ef0L,"Game::Population::Write"},{0x00536a80L,"Game::PopulationGroup::Read"},{0x00536af0L,"Game::PopulationGroup::Write"},
|
|
{0x00748df0L,"Game::IndependenceInfo::Read"},{0x00748ee0L,"Game::IndependenceInfo::Write"},{0x00744dd0L,"Game::Morale::Read"},{0x00744ea0L,"Game::Morale::Write"},{0x007490b0L,"Game::MoraleEvent::Read"},{0x007491b0L,"Game::MoraleEvent::Write"},
|
|
{0x00813770L,"Game::ShipBuildOrder::Read"},{0x00813800L,"Game::ShipBuildOrder::Write"},{0x00813250L,"Game::PlayerNotes::Read"},{0x008132b0L,"Game::PlayerNotes::Write"},{0x008843d0L,"Game::SpyReport::Read"},{0x00828ec0L,"Game::SpyReport::Write"},
|
|
{0x008200a0L,"Game::PlayerReport::Read"},{0x00817480L,"Game::PlayerReport::Write"},{0x00818cb0L,"Game::DiplomacyStats::Write"},{0x00704c70L,"Game::FlightPlan::Read"},{0x00700f60L,"Game::FlightPlan::Write"},
|
|
{0x00701860L,"Game::FlightPlan::Waypoint::Read"},{0x00700ed0L,"Game::FlightPlan::Waypoint::Write"},{0x006e2260L,"Game::NodeRoute::Read"},{0x006e22e0L,"Game::NodeRoute::Write"},{0x0056eb00L,"Game::PrisonerHold::Read"},{0x0056ec00L,"Game::PrisonerHold::Write"},
|
|
{0x00825cc0L,"Game::EventStorage::Write"},{0x006d2e10L,"Game::PlayerAlliances::Write"},{0x00813e50L,"Game::ShipHealth::Write"},{0x0053c080L,"Game::PlayerColorID::Write"},{0x008a60d0L,"Mars::Vector3::Write"},{0x005890a0L,"Game::TechTree::Write"},{0x008176a0L,"Game::ShipRecords::Write"},{0x0082c740L,"Game::CivilianRatios::Write"}};
|
|
for (Object[] r : ser) fn((Long) r[0], (String) r[1], "IStreamable serializer (vftable slot [1]=Read, [2]=Write). Member table: " + DOC);
|
|
log.println("\n==== FUNCTION RENAMES: stream primitives ====");
|
|
String P = "Stream primitive wrapper. Stream vftable slots: +0x10 ReadIntRef(name,&v)->found, +0x14 ReadNested(name,helper|NULL=skip), +0x18 String, +0x1c Bool, +0x20 Float, +0x24 Int(name,v,default=-1), +0x28 Nested(name,StreamableHelper*), +0x30 RawBytes(name,ptr,n). " + DOC;
|
|
Object[][] prims = {{0x008b9d70L,"Mars::Stream::WriteString","(Stream*, name, std::string*) -> vft+0x18; does the _Myres>=16 heap/SSO select"},{0x008b9c20L,"Mars::Stream::WriteBool","(Stream*, name, bool*) -> vft+0x1c"},{0x008b9be0L,"Mars::Stream::WriteFloat","(Stream*, name, float*) -> vft+0x20"},
|
|
{0x008b9d50L,"Mars::Stream::WriteInt","(Stream*, name, int*) -> vft+0x24"},{0x008b9d00L,"Mars::Stream::WriteInt16AsInt","(Stream*, name, int16*) widened -> vft+0x24"},{0x008b9cb0L,"Mars::Stream::WriteInt8AsInt","(Stream*, name, int8*) -> vft+0x24"},{0x008b9c60L,"Mars::Stream::WriteInt64","(Stream*, name, int64*) -> vft+0x30 raw 8 bytes"},
|
|
{0x00816490L,"Mars::Stream::WriteHandleId","(Stream*, name, NetworkObject*) writes obj ? obj->id(+4) : 0"},{0x008b9bc0L,"Mars::Stream::ReadFloat",""},{0x008b9d20L,"Mars::Stream::ReadInt",""},{0x008b9c00L,"Mars::Stream::ReadBool",""},{0x008b9d90L,"Mars::Stream::ReadString","vft+4 (name, buf, 0x400) then assigns std::string"},
|
|
{0x008b9c40L,"Mars::Stream::ReadInt64",""},{0x008b9cd0L,"Mars::Stream::ReadInt16",""},{0x008164d0L,"Mars::Stream::ReadHandle","(Stream*, name) -> handle id -> object* lookup"}};
|
|
for (Object[] r : prims) fn((Long) r[0], (String) r[1], ((String) r[2]) + "\n" + P);
|
|
|
|
log.println("\n==== FUNCTION RENAMES: affinity / app / main loop ====");
|
|
fn(0x0089ee70L, "Process_PinAffinity", "Pins the whole process to ONE logical CPU: SetProcessAffinityMask(GetCurrentProcess(), 1 << coreIndex(ESI)). Logs 'Limiting process affinity to CPU-%i'. Called from Mars::Application::Initialize when config CPU/ForceSingleCore > 0. Only affinity/topology API in the binary. Source: findings/objects/ghidra-recon.md");
|
|
fn(0x008a0e50L, "Mars::Application::Initialize", "AppStartup_ReadConfig: reads CPU/ForceSingleCore (-> Process_PinAffinity), startup config via Mars::AppStartup parser, display.cfg, audio.cfg, COM, Direct3DCreate9, window (CreateAppWindow), DrawDevice, timers, spawns the streaming-sound update thread (SoundStreamingThreadProc, THREAD_PRIORITY_TIME_CRITICAL), GlobalConsts/textures/effects/sprites/keymap/font, SimplePainter, PanelManager, then IApplication::OnStartup() (vft+0x10 = Game::DemoApp::OnStartup). this = g_pApplication (DemoApp). Source: findings/objects/ghidra-recon.md + " + DOC2);
|
|
lbl(0x008a0e50L, "AppStartup_ReadConfig", null);
|
|
fn(0x0089dd30L, "WinMain", "WinMain(hInst, hPrev, lpCmdLine, nShow) called from ___tmainCRTStartup. Tokenises the command line, creates mutex Kerberos_SwordOfTheStars_Mutex (single instance unless /concurrent), new Game::DemoApp (0x1b8 bytes) -> g_pDemoApp, Mars::Application::Initialize, then Mars::Application::Run (main loop). " + DOC2);
|
|
fn(0x0089c950L, "Game::DemoApp::DemoApp", "DemoApp ctor (IApplication impl, vftable 0x00a36004). Calls Mars::Application ctor which sets g_pApplication.");
|
|
fn(0x008a0170L, "Mars::Application::Application", "Mars::Application ctor: reads app config, sets g_pApplication (DAT_00b2d540) = this.");
|
|
fn(0x0089f5b0L, "Mars::Application::Run", "MAIN LOOP. do { FrameTimer::Update; PanelManager->vft+0x60 (UI update); app->vft+0x18 OnUpdate (DemoApp::OnUpdate: network pump + game update); if (!focused && app->vft+0xc) Sleep(20); _controlfp; periodic timer-callback flush (TimerList::Dispatch) every _DAT_00a36860 s; ok = app->vft+0x1c OnTick (DemoApp::OnTick: sound/console/panel/state machine; returns 0 when quitting); if (ok && DrawDevice) { dev->BeginFrame; if (!dev->IsLost) { dev->+8; app->vft+0x20 OnRender; dev->+0xc; dev->Present } } } while (PumpMessages() && ok). " + DOC2);
|
|
fn(0x0089f1c0L, "Mars::Application::PumpMessages", "PeekMessage/TranslateMessage/DispatchMessage loop; returns 0 on WM_QUIT (0x12). Keyboard-translation gate: PanelManager(+0x78)->vft+0x58.");
|
|
fn(0x0090c700L, "Mars::FrameTimer::Update", "QueryPerformanceCounter-based frame timer: dt(+0x14), total(+0x18 double), fps(+0xc) every +0 ticks.");
|
|
fn(0x008e5ac0L, "Mars::TimerList::Dispatch", "walks a std::map of timers calling the +0x20 callback; used by the main loop at a fixed period.");
|
|
fn(0x0089fe70L, "Mars::Application::CreateAppWindow", "RegisterClass/CreateWindow/ShowWindow for Kerberos_SwordOfTheStars_WndCls.");
|
|
fn(0x0089f4d0L, "Mars::AppStartup::OnConfigToken", "AppStartup vftable[1]: startup-config token handler (key 'conlevel' -> console level FUN_008ba280; other keys -> app->+4->vft+0x24).");
|
|
fn(0x008cd820L, "Mars::ConfigParser::ParseFile", "generic tokenising config-file parser with callback object (used for the startup config with Mars::AppStartup).");
|
|
fn(0x0089d610L, "Game::DemoApp::OnStartup", "IApplication vft+0x10: version banner 'Sword of the Stars%s (%s %s)', profiles, Mars::Network::Startup (creates network watchdog thread), ParticleSystemManager, Mesh, Model, StringTable (Locale/<loc>/Strings.csv), species, GUIResources, SoundSystem, UI sounds, SpeechEvents, GUIAppearances, PlayerColor/Badge/Avatar dictionaries, PlanetPainter, then main menu / '/join'. " + DOC2);
|
|
fn(0x0089dfb0L, "Game::DemoApp::OnShutdown", "IApplication vft+0x14: destroys all game/UI subsystems.");
|
|
fn(0x00898800L, "Game::DemoApp::OnUpdate", "IApplication vft+0x18 (called first each main-loop iteration): Mars::Network update (FUN_00902ad0), then current game object(+0x158)->vft+4 Update, then FUN_007879c0 if a strategy game (+0x150) exists.");
|
|
fn(0x0089a640L, "Game::DemoApp::OnTick", "IApplication vft+0x1c: sound/console/UI tick and the top-level game-state machine (loads/unloads the strategy game at +0x150 / combat at +0x158); returns 0 when quitting (+0x1b2).");
|
|
fn(0x00899210L, "Game::DemoApp::OnRender", "IApplication vft+0x20: PanelManager->vft+0x64 draw, or the movie/splash player at +0x108.");
|
|
fn(0x008986a0L, "Game::DemoApp::ShouldSleepWhenInactive", "IApplication vft+0xc");
|
|
|
|
log.println("\n==== FUNCTION RENAMES: threads ====");
|
|
fn(0x00902470L, "Mars::Network::Startup", "called from DemoApp::OnStartup; -> NetworkManager::Create");
|
|
fn(0x00902350L, "Mars::NetworkManager::Create", "THREAD SITE 1 (0x0090242d): InitializeCriticalSection(g_netCS 0x00b2e5f8), new NetworkManager(0x150), CreateThread(NetworkWatchdogThreadProc). Globals: 0x00b2e61c manager, 0x00b2e620 thread handle, 0x00b2e618 running flag. " + DOC2);
|
|
fn(0x00901f40L, "Mars::NetworkManager::WatchdogThreadProc", "THREAD 1 body: loop { Sleep(10); Enter(g_netCS); pump host link(+0x20)/listener(+0x98); if host link timed out -> log 'Network(%f): No response from host %s' and drop; for each client link (+0x70 vector, stride 0xc) -> 'No response from client %s', collect & drop; Leave } until manager NULL. Pure timeout watchdog, not the game sim. " + DOC2);
|
|
fn(0x008fe730L, "Mars::NetworkManager::NetworkManager", "ctor (0x150 bytes)");
|
|
fn(0x008ef1d0L, "Mars::SoundSystem::StreamingUpdateThreadProc", "THREAD 2 body (created in Mars::Application::Initialize at 0x008a14ef, priority 15): WaitForMultipleObjects(app+0x84 wake event, app+0x88 quit event); under g_musicCS (0x00b2e4a8): if current music stream (0x00b2e4c4) finished/faded (+0x388 float <= 0) -> stop it and open the next queued music file (MusicPlayer::OpenMusicFile from the 0x00b2e4d0 list); else StreamingSound::FillBuffer; then FUN_008b65d0 (sound system update). Streams music/DirectSound buffers only. " + DOC2);
|
|
fn(0x0091b5a0L, "Mars::StreamingSound::FillBuffer", "DirectSound streaming buffer refill (GetCurrentPosition / Lock / decode / Unlock); 'Couldn't restore buffer'.");
|
|
fn(0x008ef040L, "Mars::MusicPlayer::OpenMusicFile", "opens a music file (FUN_008b66e0) as the current stream 0x00b2e4c4; '[%s] cannot open music file for playback'.");
|
|
fn(0x00736e30L, "Game::BackgroundWorker::Start", "THREAD SITE 3 (0x00736e84): embedded struct {CRITICAL_SECTION cs@0; HANDLE thread@0x18; ...; job* @0x54; flags @0x58..0x5c (0x5b = quit request, 0x5c = exited)}; InitializeCriticalSection + CreateThread(BackgroundWorker::ThreadProc, this). Owner: StarMapPanelBase (strategic star-map renderer). " + DOC2);
|
|
fn(0x00735bb0L, "Game::BackgroundWorker::ThreadProc", "THREAD 3 body: loop { Sleep(10); Enter(cs); if quit(+0x5b) {set exited(+0x5c); Leave; break}; Leave; Enter; job = (+0x54 && !+0x59) ? +0x54 : NULL; Leave; if (job) { StarMapBlobs::BuildBlobMesh_Job(job+0x3c, job+4); Enter; +0x58=0,+0x59=1 (done); Leave } }. Background political-map 'blob' (territory overlay) mesh builder for the star map; NOT on the battle path. " + DOC2);
|
|
fn(0x00732ab0L, "Game::StarMapBlobs::BuildBlobMesh_Job", "worker job: sums point cloud (job+0x28 vector<Vector3>), centroid/spread (sqrt), computes a blob radius (job+0x14) then FUN_008fc160 (implicit-surface / metaball polygoniser with callbacks FUN_00722010/FUN_0071ea60) and FUN_008fa5b0 (mesh build) into the output vertex list (param_1). Used by the political map overlay (Render/StarMapBlobs_*.fx).");
|
|
fn(0x00741cd0L, "Game::StarMapPanelBase::StarMapPanelBase", "ctor of the strategic star-map view base class (Render/StarMapBlobs_Solid.fx, StarMapBlobs_Glow.fx, POLMAP_* colours, Skysphere); first statement starts the BackgroundWorker thread (this embedded at +0). Derived: Game::PoliticalMapPanel (ctor FUN_007424b0), created by the strategy-map screen ctor FUN_005e9780.");
|
|
|
|
log.println("\n==== FUNCTION RENAMES: game creation / strategy server ====");
|
|
fn(0x00898b00L, "Game::DemoApp::CreateStrategyGame", "-> StrategyApp::CreateGame");
|
|
fn(0x00888e80L, "Game::StrategyApp::CreateGame", "reads GameOptions (AIProcessMinTime, DefaultAutoRefuel), Data/Strategy/starcolors.txt, TurnCommands_v5 stream tag, builds the StrategyServer (ctor FUN_007d78d0) + StrategyServer::InitGame, loads (FUN_007dd530 'loaded from file') and the star-map UI (FUN_00778f40 -> ... -> StarMapPanelBase).");
|
|
fn(0x007d78d0L, "Game::StrategyServer::StrategyServer", "ctor (vftables 0x00a26084 IStreamable @0, 0x00a26034 @4). Stores the OnEvent callback at +0x170 from a ctor argument (EBX).");
|
|
fn(0x007c8d90L, "Game::StrategyServer::InitGame", "raises SEInitGame / SEAddDesign for every player, then SynchronizePlayer for each.");
|
|
fn(0x007c6220L, "Game::StrategyServer::SynchronizePlayer", "(playerNetId, bool full, bool, byte): pushes the server-side view (systems/fleets/designs/notes/...) to one player's client via the OnEvent callback (SE* events). Logs 'Can't synchronize player %d(id)'. Called by SyncLocalClients, InitGame, GenerateTurnEvents.");
|
|
fn(0x00815fd0L, "Game::StrategyApp::SyncLocalClients", "for each local client (+0xc vector): StrategyServer::SynchronizePlayer(client->+0x148 (player id), arg, 0, 0). 'SyncLocalClients() failed, no StrategyServer exists'.");
|
|
fn(0x007dd530L, "Game::StrategyServer::LoadGame", "'loaded from file'");
|
|
fn(0x008d2290L, "Mars::NetMessageRegistry::Register", "(this=registry entry {name@0,id@4,factory@8}, name, id, factory); g_NetMsgRegistryById[id] = entry (0x00b2ddf8, 256 slots). Called from static initialisers in .text 0x009be0cc..0x009c138c (unanalysed code). Entry(id) = 0x00b2bc94 + 12*id for the SNM* family.");
|
|
lbl(0x00b2ddf8L, "g_NetMsgRegistryById", "NetMessageRegistry entry* [256], indexed by message id");
|
|
lbl(0x00b2d540L, "g_pApplication", "Mars::Application* (the DemoApp)");
|
|
lbl(0x00b2d0bcL, "g_pDemoApp", "Game::DemoApp* created in WinMain");
|
|
lbl(0x00b2e61cL, "g_pNetworkManager", null); lbl(0x00b2e620L, "g_hNetworkWatchdogThread", null); lbl(0x00b2e5f8L, "g_netCS", "CRITICAL_SECTION guarding the network manager");
|
|
lbl(0x00b2e4a8L, "g_musicCS", "CRITICAL_SECTION guarding the streaming music player"); lbl(0x00b2e4c4L, "g_pCurrentMusicStream", null);
|
|
|
|
log.println("\n==== FUNCTION RENAMES: turn spine ====");
|
|
fn(0x00784640L, "Game::StrategyNetworkClient::OnMessage", "NETWORK MESSAGE DISPATCH (client AND host side; host checks +0x54 = StrategyServer*). Compares msg->GetType()->id with the registry entries: 0x3f SNMEndTurn -> host: StrategyServer::OnPlayerEndTurn + StorePlayerTurnCommands; 0x29 SNMUpdate (all players' TurnCommands) -> BeginProcessTurn, ApplyTurnCommands, ProcessTurn (deterministic local sim on EVERY machine), SyncLocalClients(1), state=5; 0x3c SNMDoEncounterQuery -> state=4; 0x2f SNMAllCombatDone -> ApplyEncounterResults (-> SETurnResults), SyncLocalClients(0), GenerateTurnEvents (-> SETurnEvents), autosave, state=6 'New turn begins'; 0x43 SNMResumePlaying -> StrategyServer::ResumePlaying (SEResumePlaying), reply SNMResumePlayingReceived, state=1; 0x3d SNMRunAI -> StrategyApp::RunAI; 0x32 SNMSetPlayerStatus -> player->Status(+0x164). " + DOC2);
|
|
fn(0x00783be0L, "Game::StrategyClient::EndTurn", "CLIENT End Turn: marks +0x57/+0x132, records QPC time, EndTurnDelay, raises SETurnEndPending (event type 0x21) via RaiseEvent, then SendEndTurn (SNMEndTurn). Callers: UI (FUN_005e4f80, FUN_00579310).");
|
|
fn(0x00783d30L, "Game::StrategyClient::EndTurnForced", "variant used by StrategyClient::Update (turn timer) and FUN_00783ee0; raises SETurnEndPending + SNMEndTurn.");
|
|
fn(0x00783980L, "Game::StrategyClient::SendEndTurn", "builds SNMEndTurn (TurnCommands + AIEncounterFlags) and sends it to the host.");
|
|
fn(0x007856f0L, "Game::StrategyClient::CancelEndTurn", "raises SETurnEndCancelled.");
|
|
fn(0x007842b0L, "Game::StrategyClient::Update", "per-frame client update: strategy turn timer -> SETurnTimeExpired / EndTurnForced / SNMQueryEndTurnDone; combat join/launch bookkeeping ('All(%d) clients connected to combat server', 'Launching combat for encounter %d').");
|
|
fn(0x00783ee0L, "Game::StrategyClient::RaiseEvent", "(int type, StrategyEvent** ev) -> client-side event sink (UI).");
|
|
fn(0x0088c7b0L, "Game::SNMEndTurn::Create", "registry factory for message id 0x3f (object 0x1cc bytes: TurnCommands @+4, AIEncounterFlags @+0x1b8).");
|
|
fn(0x007d9af0L, "Game::StrategyServer::OnPlayerEndTurn", "host: on SNMEndTurn from a player; if >1 human still playing and exactly one not done -> raise SELastPlaying (0x27) to that player via OnEventCallback.");
|
|
fn(0x007893c0L, "Game::StrategyServer::StorePlayerTurnCommands", "host: keeps the player's TurnCommands until SNMUpdate is broadcast.");
|
|
fn(0x00789710L, "Game::StrategyServer::BroadcastEvent", "(int eventType, StrategyEvent** ev): for every player in Players -> OnEventCallback(player->netId, type, ev). 'OnEvent() called, but no callback function specified' if NULL.");
|
|
fn(0x007d98e0L, "Game::StrategyServer::BeginProcessTurn", "Frame(+0xc)++, bProcessingTurn(+0x2f1)=1, log 'Begin processing turn %d', clears per-system/per-fleet transient state (fleets: +0x114=+0x110; +0x110=0), BroadcastEvent(0x24 = SEProcessTurn). " + DOC2);
|
|
fn(0x007b18b0L, "Game::StrategyServer::ApplyTurnCommands", "applies every player's TurnCommands from SNMUpdate ('set for turn processing'): fleet orders, builds, research, alliances (EVENT_ALLIANCE_*), diplomacy.");
|
|
fn(0x007dc6c0L, "Game::StrategyServer::ProcessTurn", "TURN PROCESSING (arg = 1.0f time step). Phases in order: [1] per-system pre-turn (SESystemAbandoned / morale events); [2] FUN_0086b300 + FUN_007adc80 (alliance/diplomacy upkeep); [3] per-player pre-pass; [4] fleet snapshot (FUN_00794ad0/FUN_007b9b90), node-space travel (ProcessNodeSpaceTravel: EVENT_LOSTINNODESPACE_*), MOVEMENT ProcessFleetMovement -> MoveFleet -> SEFleetArrived; [5] per-fleet per-ship FUN_00814ea0 (ship upkeep); [6] per-system ServerSystem::ProcessTurn (pop/morale, BUILD queue -> BuildQueue::ProcessTurn -> SEBuildCompleted, plague, rebellion, slaves); [7] FUN_0078a7c0; [8] per-player ServerPlayer::ProcessTurn (income/savings, RESEARCH: TechTree::ProcessResearch, lab accidents, EVENT_NO_RESEARCH); [9] ProcessMissions, ProcessStations (EVENT_STATIONS_SCUTTLED), ProcessDefenceSats (EVENT_DEFSATS_SCUTTLED); [10] per-ship flag pass (FUN_00814da0 4 / 0x400000); [11] encounter detection (FUN_00794ad0 second snapshot -> local_98 != 0 means encounters pending); if NO encounters: ProcessAid (EVENT_GIVE_*), ProcessSpecialProjects (EVENT_SPRJTECHOFFER_STARTED), ProcessSurrenders (EVENT_PLAYER_SURRENDERED_/EVENT_SYSTEM_SURRENDERED), per-player FUN_00818530, FUN_0086a8d0, FUN_0078ab30, FUN_00799380, FUN_0078aa70, per-system FUN_00743ec0, FUN_007b4c00, FUN_007d7f70 (end-of-turn bookkeeping over the per-player 0x74-byte records). Turn RESULTS/EVENTS are raised later from the SNMAllCombatDone handler (ApplyEncounterResults -> SETurnResults; GenerateTurnEvents -> SETurnEvents). " + DOC2);
|
|
fn(0x007da9a0L, "Game::StrategyServer::ProcessFleetMovement", "iterates fleets with flight plans, calls MoveFleet(fleet, dt) (several passes: normal, in-transit, arrival), then OnFleetArrived (EVENT_FLEET_ARRIVED).");
|
|
fn(0x007d9ee0L, "Game::StrategyServer::MoveFleet", "(StarFleet*, float dt): advances a fleet along its FlightPlan; on arrival raises SEFleetArrived; 'Destination of fleet doesn't exist. Stopping fleet.'; cancels ship actions on departure.");
|
|
fn(0x007ccb10L, "Game::StrategyServer::OnFleetArrived", "EVENT_FLEET_ARRIVED bookkeeping");
|
|
fn(0x007a0e20L, "Game::StrategyServer::ProcessNodeSpaceTravel", "EVENT_FLEET_MULTIPOINT_NONODE / EVENT_LOSTINNODESPACE_NOBORE / _ENGINES");
|
|
fn(0x007598e0L, "Game::ServerSystem::ProcessTurn", "per-system turn: population/infrastructure growth, morale (FUN_00752a10), ProcessPlague, ProcessBuildQueue, terraforming/resources, slaves (ProcessSlaves, EVENT_SLAVES_DEAD), rebellion (ProcessRebellion, EVENT_SYSTEM_REBELLION_CONTINUES).");
|
|
fn(0x00752500L, "Game::ServerSystem::ProcessBuildQueue", "-> BuildQueue::ProcessTurn");
|
|
fn(0x00890d50L, "Game::BuildQueue::ProcessTurn", "advances ShipBuildOrders; completed orders -> SEBuildCompleted (with ShipBuildOrderDef). Callers: ServerSystem::ProcessBuildQueue, FUN_00789500 (ship-borne build queues).");
|
|
fn(0x00756a90L, "Game::ServerSystem::ProcessPlague", "EVENT_PLAGUE_OUTBREAK / EVENT_COLONY_DESTROYEDBYPLAGUE / EVENT_PLAGUE_CURED");
|
|
fn(0x007583b0L, "Game::ServerSystem::ProcessRebellion", "EVENT_SYSTEM_REBELLION_CONTINUES");
|
|
fn(0x007537b0L, "Game::ServerSystem::ProcessSlaves", "EVENT_SLAVES_DEAD");
|
|
fn(0x00891340L, "Game::ServerPlayer::ProcessTurn", "per-player turn: savings/income (FUN_00840fe0), research: if ResT set -> RollResearchAccident then TechTree::ProcessResearch (EVENT_RESEARCH_OVERBUDGET / EVENT_TECHS_UNLOCKED) else EVENT_NO_RESEARCH; special projects (FUN_00863cf0).");
|
|
fn(0x005876c0L, "Game::TechTree::ProcessResearch", "EVENT_RESEARCH_OVERBUDGET / EVENT_TECHS_UNLOCKED");
|
|
fn(0x00889dc0L, "Game::ServerPlayer::RollResearchAccident", "'ACCIDENT!!' / 'All okay.' EVENT_LABACCIDENT_SMALL/MEDIUM/LARGE");
|
|
fn(0x007ad100L, "Game::StrategyServer::ProcessAid", "EVENT_GIVE_SAVINGS / EVENT_GIVE_RESEARCH");
|
|
fn(0x007a3310L, "Game::StrategyServer::ProcessSpecialProjects", "EVENT_SPRJTECHOFFER_STARTED");
|
|
fn(0x007d0d10L, "Game::StrategyServer::ProcessSurrenders", "EVENT_PLAYER_SURRENDERED_ / EVENT_SYSTEM_SURRENDERED");
|
|
fn(0x007af0b0L, "Game::StrategyServer::ProcessDefenceSats", "EVENT_DEFSATS_SCUTTLED");
|
|
fn(0x007ae480L, "Game::StrategyServer::ProcessStations", "EVENT_STATIONS_SCUTTLED");
|
|
fn(0x007999a0L, "Game::StrategyServer::ProcessMissions", "'mission'");
|
|
fn(0x007cbe80L, "Game::StrategyNetworkServer::RunCombatRound", "host combat round: for each pending encounter -> SendEncounterQuery (SNMDoEncounterQuery) / 'Notifying %s to host encounter %d' (SNMHostCombat) ...; when all encounters resolved -> SNMAllCombatDone 'All combat complete, waiting for clients to process results'.");
|
|
fn(0x007cda40L, "Game::StrategyNetworkServer::Update", "host per-frame: lobby/slot messages, combat round driving (RunCombatRound), SNMTurnInfo, SNMLaunchCombat, SNMResumePlaying (SendResumePlaying).");
|
|
fn(0x007bfe60L, "Game::StrategyNetworkServer::SendEncounterQuery", "builds SNMDoEncounterQuery");
|
|
fn(0x00794770L, "Game::StrategyNetworkServer::SendResumePlaying", "builds SNMResumePlaying");
|
|
fn(0x007d4400L, "Game::StrategyServer::ApplyEncounterResults", "after SNMAllCombatDone: applies combat outcomes (EVENT_STATION_ENABLED/DISABLED ...) then DispatchTurnResults -> SETurnResults per player.");
|
|
fn(0x007cd2a0L, "Game::StrategyServer::DispatchTurnResults", "-> SendTurnResultsToPlayers");
|
|
fn(0x007c5850L, "Game::StrategyServer::SendTurnResultsToPlayers", "(perPlayerResults[0x11c stride], n): per player: SETurnResults::Create, fill (FUN_007c24d0), dispatch (FUN_0079ac10).");
|
|
fn(0x007a7ae0L, "Game::SETurnResults::Create", "");
|
|
fn(0x007dc640L, "Game::StrategyServer::GenerateTurnEvents", "-> BuildTurnEvents (SETurnEvents per player) + FUN_00792a20/FUN_007c5610.");
|
|
fn(0x007db780L, "Game::StrategyServer::BuildTurnEvents", "builds the per-player SETurnEvents (EventStorage::TurnEvents) from the turn's event log; also SEResetMap/SEAddPlayer/SEInitTrade/SESyncDesign; calls SynchronizePlayer.");
|
|
fn(0x007ddc90L, "Game::StrategyServer::ResumePlaying", "on SNMResumePlaying: for every player with Status(+0x164)==0 -> OnEventCallback(netId, 0x26 = SEResumePlaying).");
|
|
fn(0x008706f0L, "Game::StrategyApp::RunAI", "on SNMRunAI: 'RunAI: No StrategyServer created' guard; runs the AI turn (SEAIPrepareTurn via FUN_00815f20) for AI players.");
|
|
fn(0x00815f20L, "Game::StrategyApp::RaiseAIPrepareTurn", "raises SEAIPrepareTurn");
|
|
setEOLComment(toAddr(0x0090242dL), "CreateThread -> Mars::NetworkManager::WatchdogThreadProc (thread 1: network timeout watchdog)");
|
|
setEOLComment(toAddr(0x008a14efL), "CreateThread -> Mars::SoundSystem::StreamingUpdateThreadProc (thread 2: music/DirectSound streaming, priority 15)");
|
|
setEOLComment(toAddr(0x00736e84L), "CreateThread -> Game::BackgroundWorker::ThreadProc (thread 3: star-map political-blob mesh builder)");
|
|
|
|
log.println("\n==== NET MESSAGE REGISTRY LABELS ====");
|
|
Address lo = toAddr(0x009b0000L), hi = toAddr(0x009c8000L); byte[] buf = new byte[(int)(hi.subtract(lo))]; mem.getBytes(lo, buf); int n = 0;
|
|
for (int i = 0; i + 10 < buf.length; i++) {
|
|
if ((buf[i]&0xff) != 0xB9 || (buf[i+5]&0xff) != 0xE8) continue;
|
|
long rel = (buf[i+6]&0xffL) | ((buf[i+7]&0xffL)<<8) | ((buf[i+8]&0xffL)<<16) | ((buf[i+9]&0xffL)<<24);
|
|
if (lo.getOffset() + i + 10 + (int)rel != 0x008d2290L) continue;
|
|
long entry = (buf[i+1]&0xffL) | ((buf[i+2]&0xffL)<<8) | ((buf[i+3]&0xffL)<<16) | ((buf[i+4]&0xffL)<<24);
|
|
int p = i - 5; if ((buf[p]&0xff) != 0x68) continue;
|
|
long name = (buf[p+1]&0xffL) | ((buf[p+2]&0xffL)<<8) | ((buf[p+3]&0xffL)<<16) | ((buf[p+4]&0xffL)<<24);
|
|
long id; int q; if ((buf[p-2]&0xff) == 0x6a) { id = buf[p-1]&0xff; q = p-2; } else if ((buf[p-5]&0xff) == 0x68) { id = (buf[p-4]&0xffL) | ((buf[p-3]&0xffL)<<8) | ((buf[p-2]&0xffL)<<16) | ((buf[p-1]&0xffL)<<24); q = p-5; } else continue;
|
|
long factory = (buf[q-5]&0xff) == 0x68 ? ((buf[q-4]&0xffL) | ((buf[q-3]&0xffL)<<8) | ((buf[q-2]&0xffL)<<16) | ((buf[q-1]&0xffL)<<24)) : 0;
|
|
String nm = cstr(toAddr(name), 64); if (nm == null) continue;
|
|
try { createLabel(toAddr(entry), "NetMsgReg_" + nm, true, SourceType.USER_DEFINED); setEOLComment(toAddr(entry), String.format("NetMessageRegistry entry: name=%s id=0x%02x factory=0x%08x (registered at 0x%08x)", nm, id, factory, lo.getOffset() + i)); } catch (Exception e) {}
|
|
Function ff = getFunctionAt(toAddr(factory));
|
|
if (ff != null && ff.getName().startsWith("FUN_")) { try { ff.setParentNamespace(ns("Game::" + nm)); ff.setName("Create", SourceType.USER_DEFINED); ff.setComment(String.format("NetMessage factory for %s (id 0x%02x); registered via Mars::NetMessageRegistry::Register", nm, id)); } catch (Exception e) { log.println(" !! factory " + nm + ": " + e.getMessage()); } }
|
|
n++;
|
|
}
|
|
log.println(" labelled " + n + " registry entries");
|
|
|
|
log.println("\n==== VERIFY ====");
|
|
decomp = new DecompInterface(); decomp.openProgram(currentProgram);
|
|
retypeThis(0x00749630L, new PointerDataType(sysV)); retypeThis(0x0075d4b0L, new PointerDataType(sysV));
|
|
retypeThis(0x00701070L, new PointerDataType(fltV)); retypeThis(0x00702470L, new PointerDataType(fltV));
|
|
retypeThis(0x008291f0L, new PointerDataType(shpV)); retypeThis(0x00853fa0L, new PointerDataType(shpV));
|
|
retypeThis(0x0079fa70L, new PointerDataType(srv)); retypeThis(0x007d27a0L, new PointerDataType(srv));
|
|
retypeThis(0x007d98e0L, new PointerDataType(srv)); retypeThis(0x007dc6c0L, new PointerDataType(srv)); retypeThis(0x00789710L, new PointerDataType(srv)); retypeThis(0x007d9af0L, new PointerDataType(srv)); retypeThis(0x007598e0L, new PointerDataType(sys)); retypeThis(0x00891340L, new PointerDataType(ply));
|
|
verify(0x00749630L, "verify_ServerSystem_Write.c"); verify(0x0079fa70L, "verify_StrategyServer_Write.c"); verify(0x00701070L, "verify_StarFleet_Write.c"); verify(0x007d98e0L, "verify_BeginProcessTurn.c"); verify(0x007dc6c0L, "verify_ProcessTurn.c");
|
|
decomp.dispose(); log.close(); println("writeback done");
|
|
}
|
|
}
|