Framework for Sacred 2 Gold, game log mirror, borderless fullscreen

Ported from sacredK with module-relative addresses, hooks by exported
name and a SHA-256 check of the six game modules (Community Patch
2.65.1). Borderless windowed at monitor size replaces exclusive
fullscreen, whose focus loss during loading made the game quit and
crash on exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-31 01:39:44 +01:00
commit 8a7bdfd99f
44 files changed
+2505

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
// Decompile functions of one program of the persistent Ghidra project.
//
// Headless usage (see tools/decompile.ps1):
// analyzeHeadless <projectDir> sacred2 -process <module> -noanalysis
// -scriptPath tools/ghidra -postScript Decompile.java <outFile> <addr|symbol> [...]
//
// An argument that is not a number is looked up as a symbol name (e.g. a
// decorated export). Addresses without a function (e.g. methods only
// reachable via a vtable) get one created first; run without -readOnly to
// keep those in the project.
// @category sacred2K
import java.io.File;
import java.io.PrintWriter;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileOptions;
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.symbol.SymbolIterator;
public class Decompile extends GhidraScript {
// A hex address (0x-prefixed or 8 digits), or the address of a symbol with
// that name.
private Address resolve(String text) {
if (text.matches("0x[0-9a-fA-F]+|[0-9a-fA-F]{8}")) {
return toAddr(text);
}
SymbolIterator symbols = currentProgram.getSymbolTable().getSymbols(text);
return symbols.hasNext() ? symbols.next().getAddress() : null;
}
@Override
protected void run() throws Exception {
String[] args = getScriptArgs();
if (args.length < 2) {
printerr("usage: Decompile.java <outFile> <address> [address...]");
return;
}
DecompInterface decompiler = new DecompInterface();
decompiler.setOptions(new DecompileOptions());
decompiler.openProgram(currentProgram);
try (PrintWriter out = new PrintWriter(new File(args[0]), "UTF-8")) {
for (int i = 1; i < args.length; i++) {
Address address = resolve(args[i]);
if (address == null) {
out.println("// ==== " + args[i] + " ====");
out.println("// no such address or symbol");
continue;
}
Function function = getFunctionAt(address);
if (function == null) {
function = getFunctionContaining(address);
}
if (function == null) {
new DisassembleCommand(address, null, true).applyTo(currentProgram, monitor);
function = createFunction(address, null);
}
out.println("// ==== " + args[i] + " ====");
if (function == null) {
out.println("// could not create a function here");
continue;
}
out.println("// " + function.getName(true) + " @ " + function.getEntryPoint()
+ " (" + function.getCallingConventionName() + ")");
DecompileResults result = decompiler.decompileFunction(function, 120, monitor);
if (result.decompileCompleted()) {
out.println(result.getDecompiledFunction().getC());
} else {
out.println("// decompile failed: " + result.getErrorMessage());
}
}
} finally {
decompiler.dispose();
}
}
}
+86
View File
@@ -0,0 +1,86 @@
// Apply function names from tools/names.txt to the persistent Ghidra project
// (run on every program; each line names its module).
//
// Headless usage (see tools/rename.ps1):
// analyzeHeadless <projectDir> sacred2 -process -noanalysis
// -scriptPath tools/ghidra -postScript Rename.java <namesFile>
//
// names.txt: one "module 0xADDRESS Name" per line (module as the program
// name, e.g. s2logic.dll; the address at its preferred base), "Class::Method" puts the function
// into a class namespace; "#" starts a comment. Functions are created where
// none starts at the address yet. Names starting with "g_" label data
// (globals, tables) instead of functions.
// @category sacred2K
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.SymbolTable;
public class Rename extends GhidraScript {
@Override
protected void run() throws Exception {
String[] args = getScriptArgs();
if (args.length != 1) {
printerr("usage: Rename.java <namesFile>");
return;
}
SymbolTable symbols = currentProgram.getSymbolTable();
int renamed = 0;
int labelled = 0;
for (String raw : Files.readAllLines(Paths.get(args[0]), StandardCharsets.UTF_8)) {
String line = raw.replaceAll("#.*", "").trim();
if (line.isEmpty()) {
continue;
}
String[] parts = line.split("\\s+");
if (parts.length != 3) {
printerr("bad line: " + raw);
continue;
}
if (!parts[0].equalsIgnoreCase(currentProgram.getName())) {
continue;
}
parts = new String[] {parts[1], parts[2]};
Address address = toAddr(parts[0]);
if (parts[1].startsWith("g_")) {
createLabel(address, parts[1], true, SourceType.USER_DEFINED);
labelled++;
continue;
}
Function function = getFunctionAt(address);
if (function == null) {
new DisassembleCommand(address, null, true).applyTo(currentProgram, monitor);
function = createFunction(address, null);
}
if (function == null) {
printerr("no function at " + parts[0]);
continue;
}
String name = parts[1];
Namespace namespace = currentProgram.getGlobalNamespace();
int separator = name.lastIndexOf("::");
if (separator >= 0) {
String className = name.substring(0, separator);
name = name.substring(separator + 2);
namespace = symbols.getNamespace(className, currentProgram.getGlobalNamespace());
if (namespace == null) {
namespace = symbols.createClass(currentProgram.getGlobalNamespace(), className,
SourceType.USER_DEFINED);
}
}
function.setParentNamespace(namespace);
function.setName(name, SourceType.USER_DEFINED);
renamed++;
}
println(currentProgram.getName() + ": renamed " + renamed + " functions, labelled " + labelled + " data addresses");
}
}