mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
bd5e21b366
- RE'd the full file-loader/overlay subsystem in Ghidra via MCP: CdReadRequest -> CdReadStateMachine (hand-rolled CdControl SeekL/ReadN reader, NOT PsyQ CdRead); LIST.CD RAM cache cdFileLocTable + ResourceGetCdLoc/resourceIdMap lookup; LZSS staging buffer 0x80079A70; loadDestPtrTable; ~50 symbols named (T1-T5,T8) - T6b MILESTONE: resident blob @0x800CEDF8 + location overlay @0x80128158 proven byte-identical (sha1-equal contiguous prefixes) vs live PCSX-Redux RAM; blobs imported into Ghidra (ImportOverlay.java); overlay-map table -> memory-map.md §4.3 - PCSX-Redux runtime oracle: Windows-native build, WSL reads RAM via the web API at 172.17.208.1:8081; ledger #4 resolved - live-verified player/time state: gold(/10) HP BP day hour tiredness boss-HP; save-header template (Q#5) + debug-menu dispatch (Q#9: DEBUG.BIN path is dead) - overlay map: 13 area-states across SC01/02/03, 2 duplicate pairs, location x chapter keying, free-roam vs menu-hub granularity - resolves Open Q#1/#3/#4/#5/#6/#8/#9/#12; corrects SLUS-90029 (FF8 demo, not BFM) - 2 US prototypes acquired + CRC-verified (Aug-31 1369DE07, Sep-8 5C24728E) - rules R9 (verify persistence), R10 (>=3-datapoint live verification), R11 (runtime oracle Windows-native, bridged to WSL) - bumps project version 1.2.0 -> 1.3.0
81 lines
3.6 KiB
Java
81 lines
3.6 KiB
Java
// BfmMcpServer.java — headless GhidrAssistMCP server with clean save-on-shutdown.
|
|
//
|
|
// PERSISTENCE MODEL (verified 2026-06-13):
|
|
// * GhidrAssistMCP write tools apply changes immediately, but under headless
|
|
// (-Djava.awt.headless=true) they leave the change in an OPEN transaction:
|
|
// program.isChanged() reads false and program.save() throws
|
|
// "Unable to lock due to active transaction" — so there is NO mid-session save.
|
|
// * On a CLEAN shutdown, analyzeHeadless's normal end-of-run save COMMITS that
|
|
// pending transaction and writes the program DB (verified: a rename survived a
|
|
// full stop + reopen, db.3.gbf -> db.4.gbf, "Save succeeded for processed file").
|
|
//
|
|
// => To persist MCP work: request a clean stop (touch the stopreq sentinel, e.g.
|
|
// via tools/ghidra_mcp_stop.sh). This script stops the MCP server and RETURNS,
|
|
// letting analyzeHeadless save+close the project. Resume by restarting (the
|
|
// saved db reopens). Checkpoint == stop + restart at task boundaries.
|
|
//
|
|
// Run as a -preScript (no package decl). Imports the extension server class, which
|
|
// resolves because GhidrAssistMCP.jar is on the headless JVM classpath.
|
|
//
|
|
// Args (key=value): host= port= stopreq=
|
|
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.util.Msg;
|
|
import ghidrassistmcp.GhidrAssistMCPHeadlessServer;
|
|
import java.io.File;
|
|
|
|
public class BfmMcpServer extends GhidraScript {
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
if (currentProgram == null) {
|
|
Msg.warn(this, "BfmMcpServer: no program loaded — aborting");
|
|
return;
|
|
}
|
|
|
|
String host = "127.0.0.1";
|
|
int port = 8080;
|
|
String stopReqPath = System.getProperty("user.home") + "/bfm-decomp/.run/mcp-stop.req"; // project-local default (overridden by stopreq= arg)
|
|
String[] scriptArgs = getScriptArgs();
|
|
if (scriptArgs != null) {
|
|
for (String a : scriptArgs) {
|
|
if (a.startsWith("host=")) host = a.substring(5);
|
|
else if (a.startsWith("port=")) port = parseIntOr(a.substring(5), port);
|
|
else if (a.startsWith("stopreq=")) stopReqPath = a.substring(8);
|
|
}
|
|
}
|
|
|
|
File stopReq = new File(stopReqPath);
|
|
stopReq.delete(); // clear any stale sentinel so we don't stop immediately
|
|
|
|
GhidrAssistMCPHeadlessServer srv = GhidrAssistMCPHeadlessServer.getInstance();
|
|
if (srv.isRunning()) {
|
|
srv.setProgram(currentProgram);
|
|
Msg.info(this, "BfmMcpServer: server already running; program reference updated");
|
|
} else {
|
|
srv.start(currentProgram, host, port);
|
|
Msg.info(this, "BfmMcpServer: MCP server started on " + host + ":" + port);
|
|
}
|
|
|
|
Msg.info(this, "BfmMcpServer: serving (stopreq=" + stopReqPath + "). "
|
|
+ "MCP writes persist on CLEAN SHUTDOWN only — analyzeHeadless commits+saves on close; "
|
|
+ "no mid-session save (GhidrAssist holds an open transaction while serving).");
|
|
|
|
while (!monitor.isCancelled() && srv.isRunning()) {
|
|
if (stopReq.exists()) {
|
|
stopReq.delete();
|
|
Msg.info(this, "BfmMcpServer: stop requested via sentinel");
|
|
break;
|
|
}
|
|
Thread.sleep(1000);
|
|
}
|
|
|
|
srv.stop();
|
|
Msg.info(this, "BfmMcpServer: server stopped; returning so analyzeHeadless saves+closes the project.");
|
|
}
|
|
|
|
private static int parseIntOr(String s, int dflt) {
|
|
try { return Integer.parseInt(s.trim()); } catch (Exception e) { return dflt; }
|
|
}
|
|
}
|