clank-together/ghidra_scripts/ApplySymbols.java
Clank Together 836610da8f Clank Together: coordinator, sandboxed verifier and agent client
Coordinator (accounts with approval, API tokens, tasks with leases, sandboxed MSVC verification against
the original exe, peer review by three agents for non-identical code, human review, serial merge queue),
the `td` contributor CLI with an MCP server for agents, pinned Ghidra integration, deployment (Caddy,
systemd, nftables, Forgejo) and the wowdecomp 15890 migration tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
2026-10-18 01:29:16 +02:00

88 lines
3.4 KiB
Java

// Applies the Clank Together symbol snapshot to the program, so every
// contributor's local Ghidra project shows the same names.
// Input: TSV lines "addr kind id name decorated library" written by `td ghidra build/sync`.
// @category ClankTogether
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.NamespaceUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolUtilities;
public class ApplySymbols extends GhidraScript {
@Override
protected void run() throws Exception {
String[] args = getScriptArgs();
if (args.length < 1) {
printerr("usage: ApplySymbols.java <symbols.tsv>");
return;
}
List<String> lines = Files.readAllLines(Paths.get(args[0]), StandardCharsets.UTF_8);
int applied = 0;
int failed = 0;
for (String line : lines) {
if (monitor.isCancelled()) {
break;
}
String[] c = line.split("\t", -1);
if (c.length < 6) {
continue;
}
try {
Address addr = toAddr(Long.parseLong(c[0], 16));
apply(addr, c[1], c[2], c[3]);
applied++;
} catch (Exception e) {
failed++;
printerr("could not apply " + line + ": " + e.getMessage());
}
}
println("applied " + applied + " symbols, " + failed + " failed");
}
private void apply(Address addr, String kind, String id, String name) throws Exception {
String label = name.isEmpty() ? id : name;
if (kind.equals("function")) {
Function f = getFunctionAt(addr);
if (f == null) {
disassemble(addr);
f = createFunction(addr, null);
}
if (f != null) {
setName(f.getSymbol(), label);
}
} else {
Symbol s = getSymbolAt(addr);
if (s == null || s.getSource() == SourceType.DEFAULT) {
s = createLabel(addr, SymbolUtilities.replaceInvalidChars(id, true), true, SourceType.USER_DEFINED);
}
setName(s, label);
}
currentProgram.getListing().setComment(addr, CodeUnit.PLATE_COMMENT, "TD " + id);
}
/** Uses "A::B::name" as a namespace path when possible, else a flat sanitized name. */
private void setName(Symbol s, String qualified) throws Exception {
int split = qualified.lastIndexOf("::");
if (split > 0 && !qualified.contains("<")) {
try {
Namespace ns = NamespaceUtils.createNamespaceHierarchy(qualified.substring(0, split), null, currentProgram, SourceType.USER_DEFINED);
s.setNameAndNamespace(SymbolUtilities.replaceInvalidChars(qualified.substring(split + 2), true), ns, SourceType.USER_DEFINED);
return;
} catch (Exception e) {
// fall through to a flat name
}
}
s.setName(SymbolUtilities.replaceInvalidChars(qualified, true), SourceType.USER_DEFINED);
}
}