Ghidra Re
OrbitCurve/firmware-reverse-engineering
Expert-level Ghidra reverse engineering for firmware binaries with emphasis on stripped binary analysis, automated function discovery, cryptographic routine identification, authentication logic…
A skill your agent uses when a Granblue Fantasy Relink game patch breaks the GBFR Logs hook — signatures no longer match, "Could not find match for pattern" / "Could not find <offset" warnings…
$ npx skills add villith/relink-logs --skill reverse-engineering-signatures -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install villith/relink-logs reverse-engineering-signatures --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/villith/relink-logs.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/reverse-engineering-signatures .claude/skills/reverse-engineering-signatures && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "reverse-engineering-signatures" agent skill from https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signatures into .claude/skills/reverse-engineering-signatures/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reverse-engineering-signatures", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signaturesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add villith/relink-logs --skill reverse-engineering-signatures -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install villith/relink-logs reverse-engineering-signatures --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/villith/relink-logs.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/reverse-engineering-signatures .agents/skills/reverse-engineering-signatures && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "reverse-engineering-signatures" agent skill from https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signatures into .agents/skills/reverse-engineering-signatures/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reverse-engineering-signatures", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add villith/relink-logs --skill reverse-engineering-signatures -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install villith/relink-logs reverse-engineering-signatures --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/villith/relink-logs.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/reverse-engineering-signatures .cursor/skills/reverse-engineering-signatures && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "reverse-engineering-signatures" agent skill from https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signatures into .cursor/skills/reverse-engineering-signatures/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reverse-engineering-signatures", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/villith/relink-logs.git --path .claude/skills/reverse-engineering-signatures--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add villith/relink-logs --skill reverse-engineering-signatures -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install villith/relink-logs reverse-engineering-signatures --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/villith/relink-logs.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/reverse-engineering-signatures .gemini/skills/reverse-engineering-signatures && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "reverse-engineering-signatures" agent skill from https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signatures into .gemini/skills/reverse-engineering-signatures/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reverse-engineering-signatures", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install villith/relink-logs reverse-engineering-signaturesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add villith/relink-logs --skill reverse-engineering-signatures -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/villith/relink-logs.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/reverse-engineering-signatures .github/skills/reverse-engineering-signatures && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "reverse-engineering-signatures" agent skill from https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signatures into .github/skills/reverse-engineering-signatures/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reverse-engineering-signatures", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add villith/relink-logs --skill reverse-engineering-signatures -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install villith/relink-logs reverse-engineering-signatures --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/villith/relink-logs.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/reverse-engineering-signatures .opencode/skills/reverse-engineering-signatures && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "reverse-engineering-signatures" agent skill from https://github.com/villith/relink-logs/tree/dev/.claude/skills/reverse-engineering-signatures into .opencode/skills/reverse-engineering-signatures/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reverse-engineering-signatures", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
reverse-engineering-signaturesA skill your agent uses when a Granblue Fantasy Relink game patch breaks the GBFR Logs hook — signatures no longer match, "Could not find match for pattern" / "Could not find <offset" warnings…
Reverse Engineering Signatures is an agent skill from villith/relink-logs. Use when a Granblue Fantasy Relink game patch breaks the GBFR Logs hook — signatures no longer match, "Could not find match for pattern" / "Could not find <offset" warnings, hooks logging FAIL at startup, an offset reads a wrong/garbage value, or a hook crashes the game. Also use when tracing where a value in the game binary comes from — an unexplained id or constant surfaced by the hook, "which ability/system set this field", or before concluding a value is data-driven, runtime-only, or absent from the code.
Its SKILL.md is about 7.5k tokens, which your agent loads only when the skill is triggered. The skill folder holds 21 other files.
It sits in Security, covering Reverse engineering and malware. It works with Ghidra. The repository describes itself as: Relink Logs lets you track damage statistics with a nice overlay DPS meter for Granblue Fantasy: Relink. The licence is MIT.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 8251151. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships script files (Java, from the files we listed), which the agent can run.
Shell commands in SKILL.md call:
cargonodeFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Reverse Engineering Signatures loads about 7.5k tokens when it runs. Until then it costs about 137 tokens; SKILL.md has 3,771 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from villith/relink-logs at commit 8251151, republished under its MIT licence (© villith). 3,771 words, ~7,477 tokens.
.claude/skills/reverse-engineering-signatures/SKILL.md (or your agent's skills folder). This skill also uses 20 other files; get the full folder from GitHub.The hook (src-hook/) reads game memory via reverse-engineered byte-signatures (AOB scans) and struct field offsets. A game patch that recompiles granblue_fantasy_relink.exe shifts both, silently. This skill re-derives them.
Two tools, two jobs:
sigscan (Rust, in-repo) — scans the on-disk exe. Fast. Confirms a pattern's match count, reads offset operands, follows calls, dumps bytes. Your primary tool.sigscan can follow a call, but the call target may be a callee mid-logic, not a clean entry — hooking that crashes the game. Ghidra gives the real function boundary.Core rule: a signature is only trustworthy when sigscan reports exactly 1 match. A hook target is only safe when Ghidra confirms it's a function ENTRY with the right argument count.
2.0.x in the exe. Launch the lean import + fast analysis of the new exe immediately (background, ~3h); everything below runs against the OLD DB meanwhile.node docs/superpowers/patch-tools/sweep.mjs — signature status. A previously-dead pattern that suddenly matches once is a coincidence until proven otherwise (2.0.6: the death sig landed in an allocator).RelocSites.java on the old fast DB + relocate.mjs — every *_RVA const (grep -rn "RVA: usize").rtti.mjs — summon vtables + the cap oracle's TypeDescriptors.src-hook/Cargo.toml, cargo test -p hook, build with --features eject, copy to src-tauri/hook.dll, live-verify via the hook log.%APPDATA%\gbfr-logs\gbfr-logs.txt) shows Could not find match for pattern or [hook FAIL] <name>.Skill <bignum>, damage cap reads 0) — an inner struct field shifted.Two failure classes, two approaches:
| Symptom | It's a... | Tool |
|---|---|---|
| pattern 0 matches / offset warning | broken AOB signature | sigscan (re-anchor) |
| wrong value read, no crash | shifted struct field offset | sigscan dumprva + live diag |
| game crashes on hook | wrong function target/arity | Ghidra (find true entry) |
| every sig still matches, but a feature silently reads nothing | shifted hardcoded RVA | see "Relocating hardcoded RVAs" |
A patch can move every hardcoded RVA while leaving every AOB signature matching. That was the whole of 2.0.3: .data moved uniformly, the hook log looked healthy ([hook ok] across the board), and the only symptom was features quietly reading nothing. Whenever a patch breaks behaviour but not signatures, audit the const *_RVA blocks first — grep -rn "RVA: usize" src-hook/ src-tauri/.
# from repo root; GBFR_EXE overrides the default Steam path
cargo run -p hook --bin sigscan --release -- "<pelite pattern>" [mode] [--all]Modes: slice_u32 (default, read a u32 operand at cursor), slice_u8, addr (follow a call, report target RVA), dumprva <hexrva> [len] (raw bytes at an RVA).
--all widens the scan from the code section to the whole image. Hook signatures are all code, so the default is right for them — but data lives outside it, and without --all a pattern in .rdata/.data silently reports 0 matches. Needed for the RTTI vtable walk below.
Every match prints pre: (24 bytes before) + at: (48 bytes from the match) — essential for re-anchoring. Want exactly 1 match. See the file header in src-hook/src/bin/sigscan.rs for details.
Pattern syntax (pelite): ? = wildcard byte, ' = store-cursor marker (its RVA → addrs[1], what search_slice/search_address return), e8 $ { ' } = follow the call and capture its target. search_address returns base + cursor.
Old patterns embed magic type-hash constants (e.g. 0x887ae0b0 = LE b0 e0 7a 88) that survive recompiles. Anchor on the surviving constant:
sigscan "<old pattern>" → confirm it now gives 0 matches (the break).dumprva).' at the operand you need.sigscan "<new pattern>" until it gives 1 match with the right value.Compiler drift to expect: mov eax,K→mov r8d,K (b8→41 b8); base register rsi→r14 (48 8d 8e→49 8d 8e); AVX sequences re-ordered.
A global is reached by some instruction's RIP-relative displacement, so recover it from the code that uses it:
XrefsTo.java on the old analyzed DB → the code sites referencing the stale RVA.DumpBytes.java on the old DB → the bytes at that site.target = cursor_rva + 4 + disp (plus any bytes trailing the disp32, e.g. 4 more for a trailing imm32 as in cmp dword [rip+x], imm32).Fast path (2.0.6, ~10 min for 13 globals): RelocSites.java NAME:0xOLD ... on the old analyzed DB does steps 1–2 in one JVM run and emits a sigscan pattern per referencing function with every rel32/RIP disp32 in the window wildcarded (the component-type sites only become unique once the neighbouring displacements are wildcarded too) and the cursor on the target disp; docs/superpowers/patch-tools/relocate.mjs <that output> scans the new exe and tallies the votes per global. Args use NAME:RVA, never NAME=RVA — analyzeHeadless.bat splits on =. Sites in generic helpers come back with hundreds of matches; ignore them, the distinctive sites give 3–6 agreeing votes.
Take the disp32's address from sigscan's reported cursor_rva — never by counting bytes in a hex dump by eye. Miscounting is silent: the value still lands in the same BSS region, still looks like a plausible global, and simply reads nothing at runtime. A 2.0.3 pass mis-sited three displacements by 8 this way, shipped, and cost a full live-test round.
The strongest check: byte-diff the whole enclosing function, old vs new. These functions are typically identical apart from their displacement bytes, so the diff pins every displacement position exactly — and running the same arithmetic on the old function must reproduce the old, known-good value. That reproduction is the proof.
Cross-check two ways: derive the same global from two independent call sites, and compare against the section's common delta (2.0.3: every .data global moved -0x3040). Uniformity is a check, not a derivation — but any value that breaks it is far more likely to be your arithmetic than a real anomaly.
Vtables have no RIP-relative xrefs to chase, so walk MSVC RTTI forward from the class name, which survives recompiles. Get the old names with SymbolAt.java on the analyzed DB, then in the new exe (all scans need --all, since none of this is in the code section):
".?AV<Class>@@" string -> TypeDescriptor (the string sits at TD+0x10)
TypeDescriptor -> Complete Object Locator (COL+0x0C holds the TD's RVA)
COL -> vtable (the qword at vtable-8 points at the COL)COL layout: sig(0)=1, offset(4), cdOffset(8), pTypeDescriptor(0xC), pClassDescriptor(0x10), pSelf(0x14).
These classes carry ~20 COLs each (one per base subobject of a deep multiple-inheritance hierarchy), so sig==1 alone is not discriminating. The vtable that lands at *(object) — the one the hook compares — is the subobject at offset 0: filter sig==1 && offset==0 && cdOffset==0 && pSelf==colRva.
Always round-trip: feed each recovered vtable RVA back through the walk and confirm it yields the class you started from. That turns 18 guesses into 18 verified facts.
docs/superpowers/patch-tools/rtti.mjs "<name regex>" does the whole walk over the on-disk exe in Node (no Ghidra): it enumerates every COL, resolves each offset-0 COL's vtable, and prints name td col vtable per class. Anchor the regex (^\.\?AVSo0000@@$) or the AttributeBase@VSo0000... template noise drowns the hits. It also yields the TypeDescriptor RVAs the cap oracle needs.
The AOB sig is fine but a field inside the struct moved. Confirm with a wide live dump: build with the dmgdiag feature (cargo build -p hook --features dmgdiag) — damage.rs then logs every nonzero u32 in a window per real hit. Match the known value (a skill ID, a cap magnitude) to its new offset, update ffi.rs.
Static shortcut (2.0.6): every hardcoded read_*_guarded(obj, 0xNN) in the hook is a struct field the sweep above cannot see. Scan the new exe for the game's own read idiom of that field with the offset wildcarded — the SBA gauge's vmovss xmm0,[rax+0x7c]; vdivss xmm7,xmm0,[rax+0x80] became [rax+0x80] / [rax+0x84], and Decompile.java on the gauge-update entry confirmed the +0x80 store. Live symptom was SBAPOLL resolving every slot by name with gauge=0.0 for 300+ updates — the walk was right, only the leaf offset was stale.
Needed only for function-hook targets (the thing you detour). One-time setup already done (Ghidra 12.1.2 at C:\ghidra\ghidra_12.1.2_PUBLIC, JDK 21 wired via support\launch.properties).
For most fixes, use lean import (-noanalysis) + a targeted Java post-script that disassembles only a local window — fast, no decompiler needed.
When lean isn't enough (the surviving byte-anchor keeps landing in generic FNV-1a hashmap-lookup helpers, so you can't tell the real accessor apart — this happens for player_load, the sigil/weapon/overmastery offsets, and the SBA family), you need the decompiler + xrefs from a full analysis pass.
The analyzed DB already exists: gbfr202fast (project C:\Users\Scott\ghidra-projects\gbfr, ~1.7 GB .rep, persists). For v2.0.2 you do NOT need to rebuild it — just query it (see "Decompile a function" below). Only re-run the analysis after a NEW game patch.
To (re)build it after a patch — do NOT run the default full analysis (multi-hour: the "Decompiler Parameter ID" analyzer alone ~10x's the time). Run a fast analysis with the slow analyzers disabled (still gives function boundaries, xrefs, decompiler, and C++ RTTI class names). Measured: ~2h49m on the 118 MB exe (i5-13600K, NVMe) — run it in the background and wait for the completion notification:
cd C:/ghidra/ghidra_12.1.2_PUBLIC/support
SKILL=C:/Users/Scott/Projects/gbfr-logs/.claude/skills/reverse-engineering-signatures/ghidra
GHIDRA_HEADLESS_MAXMEM=28G ./analyzeHeadless.bat \
C:/Users/Scott/ghidra-projects/gbfr gbfr<version>fast \
-import "<path to granblue_fantasy_relink.exe>" -processor x86:LE:64:default \
-scriptPath "$SKILL" -preScript FastAnalysisOptions.java # run in backgroundFastAnalysisOptions.java disables Decompiler Parameter ID, Decompiler Switch Analysis, Call Convention ID, Aggressive Instruction Finder, Stack, and logs getOptionNames() to prove it applied. VERIFY the run log shows FastAnalysisOptions: DISABLED '<name>' lines. Success markers at the end: Analysis succeeded + Save succeeded + Import succeeded. Gotchas: -preScript needs -scriptPath <dir> -preScript <bare-name.java> (an absolute path arg → ClassNotFound). Use ≤28G heap (heap is hard-capped by -Xmx; won't exceed it). Fresh project name so the lean DB stays usable. The late RTTI phase (CreateRtti4BackgroundCmd, "Unprocessed TypeDescriptor" lines) is CPU-light, single-threaded, and logs sparsely — a quiet log ≠ hung; it also emits non-fatal ERROR No vfTable found / VarnodeContext: out of address spaces and continues. When checking if it's alive, find the RIGHT java PID — Get-CimInstance Win32_Process -Filter "Name='java.exe'" | select ProcessId,CommandLine and pick the one whose command line contains ghidra.GhidraClassLoader (NOT the Cursor/VSCode redhat.java LSP), then sample that PID's TotalProcessorTime over 10s (>0 = working).
Decompile.java prints a function's C. Use it to read what a function actually DOES — arg meaning, which enum values branch where, what struct fields it touches — instead of guessing from asm or chasing byte idioms into generic helpers.
cd C:/ghidra/ghidra_12.1.2_PUBLIC/support
SKILL=C:/Users/Scott/Projects/gbfr-logs/.claude/skills/reverse-engineering-signatures/ghidra
GHIDRA_HEADLESS_MAXMEM=12G ./analyzeHeadless.bat \
C:/Users/Scott/ghidra-projects/gbfr gbfr202fast -process granblue_fantasy_relink.exe \
-noanalysis -scriptPath "$SKILL" -postScript Decompile.java 0x3f1330 0x63ecb0 # 1+ RVAsFilter with grep 'Decompile.java>'. Runs against the analyzed gbfr202fast DB (the decompiler needs analysis). -noanalysis here just means "don't re-analyze on open." Takes the RVA of any byte inside the target function (it resolves the containing function). Example payoff: decompiling the result-screen router FUN_1403f1330 showed if (0x13 < param_2) return; if (*(char*)(param_1 + 0xed4 + param_2*0x40) == 0) return; — i.e. result_type is a per-screen table index, NOT a quest-clear flag, proving that hardcoding result_type == N is fragile. That's the kind of ground truth lean analysis can't give you.
cd C:/ghidra/ghidra_12.1.2_PUBLIC/support
GHIDRA_HEADLESS_MAXMEM=24G ./analyzeHeadless.bat \
C:/Users/Scott/ghidra-projects/gbfr gbfr202lean \
-import "<path to granblue_fantasy_relink.exe>" \
-processor x86:LE:64:default -noanalysisThe saved program DB lives at C:\Users\Scott\ghidra-projects\gbfr\gbfr202lean.rep (~2.3 GB) and persists. Re-import only after a new game patch (use a fresh project name, e.g. gbfr<version>lean). The project's parent dir must exist and Ghidra 12 needs -processor explicit.
Scripts live in ghidra/ next to this skill. Run against the saved DB with -process (no re-import). Ghidra 12 has no Python by default — these are Java GhidraScripts.
cd C:/ghidra/ghidra_12.1.2_PUBLIC/support
SKILL=C:/Users/Scott/Projects/gbfr-logs/.claude/skills/reverse-engineering-signatures/ghidra
# Find the true ENTRY of the function CONTAINING each anchor RVA (a byte inside it):
GHIDRA_HEADLESS_MAXMEM=24G ./analyzeHeadless.bat \
C:/Users/Scott/ghidra-projects/gbfr gbfr202lean -process granblue_fantasy_relink.exe \
-noanalysis -scriptPath "$SKILL/ghidra" -postScript FindEntry.java 0x3f13b5 0x63ecb0
# Inspect a function ENTRY (callers, callees, string refs, arg usage from prologue):
GHIDRA_HEADLESS_MAXMEM=24G ./analyzeHeadless.bat \
C:/Users/Scott/ghidra-projects/gbfr gbfr202lean -process granblue_fantasy_relink.exe \
-noanalysis -scriptPath "$SKILL/ghidra" -postScript InspectFunc.java 0x3f1330Output is verbose; filter with grep 'FindEntry.java>\|InspectFunc.java>'. Run in background (run_in_background) — JVM startup dominates.
The prologue tells you the arity — get this wrong and the detour crashes the game. In the x64 MS ABI, args arrive in rcx, rdx, r8, r9. If the prologue reads rdx/edx/r8/r9 early, the function takes that many args:
cmp edx, 0x13 ; uses arg2 → at least a 2-arg fn
mov rbx, rcx ; arg1Match your retour detour signature and .call(...) to the real arity. A 1-arg detour on a 2-arg function leaves a register garbage → access violation.
Sometimes the question is not "where is this function" but "where does this number come from" — a status cause id, a damage-type constant, a quest flag. Different discipline, and the failure mode is the opposite of a broken sig: you get a confident wrong negative instead of a loud error.
A null result from a scan only means your scan was too narrow. It is never evidence the value is absent, and reporting it as such sends everyone chasing runtime captures for something sitting in the code.
A real 2026-08-02 failure: a sweep of one API's call sites found no 1100, and
that was written up as "this cause is data-driven, static evidence exhausted."
1100 was in fact MOV R8D,0x44c in the very function that applies it — it
just reached the field through a different applier. One extra sweep of the
missed family recovered 18 cause values in a single run.
Before writing "not in the binary", you must have done all three:
If you have not, the honest phrasing is "not found by <method>; <method> cannot
see X" — say what you searched, not what exists.
Wrappers around one implementation take different argument positions, so a sweep written for one of them silently misreads the others.
# 1. Who calls the implementation? These are the wrappers.
-postScript XrefsTo.java <impl_rva>
# 2. Repeat on each wrapper until the set stops growing (transitive closure).
# 3. Only now sweep every member.Stop when a round adds nothing. Values can also reach a call through a field
store (MOV [R14+0x8b4],0x44c … later read and passed), which no call-site
scan can see — those need the decompiler.
| Question | Tool |
|---|---|
| Which constants does each caller pass? | CallSiteArgs.java <target> — all 8 arg slots per site |
| Where is this constant used at all? | ImmSites.java <value> … — whole-listing scan, with containing function |
| Where did this register's value come from? | Decompile.java — read the dataflow; a scan cannot |
CallSiteArgs reports ? (nothing in window) and reg:NAME (came from a
register). Both mean UNKNOWN, not absent — resolve them by decompiling, or
your summary will encode the same wrong negative.
Interpreting ImmSites output: histogram the instruction kinds before
concluding anything. 118 AND + 86 TEST of a value means it is a mask;
CMP means a comparison; only a MOV into an argument slot is a passed value.
But a value absent from this scan may still arrive via a field or a table.
Static call-site attribution and a live hookdiag capture are independent. When
they disagree, the static sweep is usually incomplete (see above). When they
agree, the fact is solid. The hook log is at %APPDATA%\gbfr-logs\gbfr-logs.txt
and reaches gigabytes — always tail -n it, never read it whole.
sigscan to confirm it's unique).FindEntry.java <anchor_rva> → true entry RVA (self-check: a known-good anchor must resolve to its own entry).InspectFunc.java <entry_rva> → confirm it's the right function (callers, behavior) and read its arg count from the prologue.ret+int3 padding then the prologue, cursor at entry — c3 cc cc cc cc ' <prologue bytes>. Verify with sigscan "<pattern>" addr → 1 match, target_rva = the entry.const *_SIG, the type alias, static_detour!, and run()/.call() to the correct arity.cargo build -p hook --release, then copy to src-tauri/hook.dll (see below), restart game, test.The app injects src-tauri/hook.dll from CWD (dev). build.rs refreshes it only on a backend recompile, so after rebuilding just the hook:
cp -f target/release/hook.dll src-tauri/hook.dllThe injected DLL is locked while the game runs — close the game to swap it. Re-injecting a same-named already-loaded DLL is a silent no-op.
cursor_rva, or byte-diff the enclosing function. An off-by-N lands in the same BSS region and reads as a plausible global, so nothing errors — the feature just silently returns nothing..rep until the new one is fully derived and live-verified.call-follow match as a hook target. Unique ≠ clean entry. The followed target can be a callee mid-function. Always confirm the entry with Ghidra before detouring. (This crashed the quest hook on v2.0.2: the old sig followed a call to a 2-arg helper hooked as 1-arg.)-noanalysis + targeted scripts; when you genuinely need the decompiler, use the fast analysis (analyzers disabled via FastAnalysisOptions.java) — and note the analyzed gbfr202fast DB already exists for v2.0.2, so query it, don't rebuild.java.exe (e.g. the editor's redhat.java LSP) shows 0 CPU and looks dead. Match on ghidra.GhidraClassLoader in the command line first.GhidraScripts.sigscan dumprva (correct pelite addressing).XrefsTo on the implementation transitively until the set stops growing.dmgdiag feature block and console-gated prints are kept on purpose for the next patch.src-hook/src/bin/sigscan.rs — the harness (in-repo, committed).ghidra/DumpBytes.java — RVA(s) → raw hex bytes from the program (lean DB). THE query for "what did the OLD exe look like here" once Steam has patched the exe in place and only its Ghidra DB survives. Pair with XrefsTo: xref site → DumpBytes → wildcard the disp32 → scan the NEW exe → read the new displacement.ghidra/SymbolAt.java — RVA(s) → the symbols defined there (needs the analyzed DB). The inverse of ListSymbols. THE query for "what class is this vtable RVA", i.e. the first step in relocating a hardcoded vtable list after a patch.ghidra/FindEntry.java — anchor RVA(s) → containing-function entry RVA + prologue bytes (lean DB).ghidra/InspectFunc.java — entry RVA(s) → callers, callees, string refs, prologue disasm (lean DB).ghidra/FindByBytes.java — byte pattern → containing-function entry for each hit (lean DB).ghidra/FindStringRefs.java — ASCII substring → enclosing C-strings → code xrefs + containing-function entries (needs the analyzed DB for xrefs).ghidra/XrefsTo.java — RVA(s) → every referencing site, deduped by containing function with per-function counts. THE query for "who touches this global/vtable/function" (needs the analyzed DB).ghidra/RelocSites.java — NAME:0xRVA … → per referencing function, a ready-to-scan sigscan pattern (all displacements wildcarded, cursor on the target disp32, k= trailing-immediate count). Feed to docs/superpowers/patch-tools/relocate.mjs. max:N widens the per-global site cap (default 6).docs/superpowers/patch-tools/ (gitignored, keep) — sweep.mjs (extract every pattern literal incl. inline ones, run sigscan, --json), rtti.mjs (RTTI walk), relocate.mjs (scan RelocSites output, tally new RVAs).ghidra/ListSymbols.java — case-insensitive substring search over the symbol table (RTTI class/vtable names) (needs the analyzed DB).ghidra/FindVCallSlot.java — slot displacement (e.g. 0x48) → every indirect CALL qword ptr [reg + disp] site with its containing function, plus the surrounding instructions. THE query for "who calls virtual slot N", which XrefsTo cannot answer: a virtual call references only the vtable, never the callee. Scans the listing, so data bytes that happen to match are never reported. Expect many hits — filter by the caller's code region and by how the out-param is used.ghidra/DisasmCalls.java — target function RVA → every call site with the ~8 instructions preceding it (optionally filtered to given containing functions). Raw disassembly per site; use CallSiteArgs.java instead when you want the arguments already parsed.ghidra/CallSiteArgs.java — target RVA → per call site, the last immediate written to EACH x64 argument slot (RCX/RDX/R8/R9 + [RSP+0x20..0x38]), as CSV. THE query for "which constants does each caller pass". Emits ? (nothing in window) and reg:NAME (register-sourced) — both mean UNKNOWN, resolve with the decompiler, never read as "absent".ghidra/CallSiteArgsMulti.java — same report for MANY targets in one headless run (JVM startup dominates), and tracks 12 slots through [RSP+0x58] — the status-apply family passes its cause as deep as positional arg 10, which the 8-slot scan silently missed (that blind spot hid cause 0xFFFFF).ghidra/FnStrings.java — function-entry RVA(s) → the defined strings referenced inside each function body. THE attribution query for "which character/entity owns this code": per-character code carries tag strings (pl0400, em1200, ab_scourge_vanish). Expect sparse results — most functions reference no strings; fall back to class-binder symbols and address-band inference (character code is contiguous and ordered by character id).ghidra/FindOperandText.java — substring → every instruction whose rendered text contains it, with containing function. THE query for "who touches struct field +0xNNNN": a displacement inside [RSI + 0xNNNN] is not a scalar operand, so ImmSites.java cannot see it. Histogram the hits (reads/writes/CMP bases) before concluding; a field with only zeroing writers is written through computed pointers elsewhere.ghidra/ImmSites.java — value(s) → every instruction in the listing whose scalar operand matches, with containing function. THE query for "where is this constant used at all". Histogram the instruction kinds before concluding: many AND/TEST = a mask, CMP = a comparison, only a MOV into an argument slot is a passed value.ghidra/DumpVtableSlot.java — class-name substring + slot displacement → the function each matching <Class>::vftable holds at that slot, with a tally of distinct targets. Answers "which subclasses OVERRIDE this virtual and which inherit the base", i.e. exactly how many detours a virtual needs. Classes with multiple vftables (multiple inheritance) or short vtables produce misaligned reads — sanity-check a candidate by decompiling it before trusting it.ghidra/Decompile.java — RVA(s) → decompiled C of the containing function (needs the analyzed gbfr202fast DB).ghidra/FastAnalysisOptions.java — pre-script that disables slow analyzers for the fast full-analysis build.Ghidra DBs (all under C:\Users\Scott\ghidra-projects\gbfr, all persist). Two kinds per game version: gbfr<ver>lean (import-only, for fast FindEntry/InspectFunc/FindByBytes lookups) and gbfr<ver>fast (fully analyzed, for Decompile.java + xrefs + SymbolAt/C++ RTTI names). Re-create both only after a new game patch.
Present: gbfr202* (v2.0.2), gbfr203* (v2.0.3), gbfr204* (v2.0.4), gbfr205* (v2.0.5) and gbfr206lean / gbfr206fast (v2.0.6, current). Queries against an older DB in the same project run fine while a new -import analysis is in progress (just slower). The exe lives at G:\SteamLibrary\steamapps\common\Granblue Fantasy Relink\ (sigscan's default), not under C:\Program Files (x86)\Steam. **Keep the previous version's DBs** — the 2.0.3 fix was derived almost entirely by querying gbfr202fast for xrefs and old bytes, which is impossible once Steam has overwritten the old exe.
Detailed, evolving findings for the current patch live in the memory file gbfr-endless-ragnarok-break (verified entries, offsets, and per-hook status).
© villith, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 20 other files in .claude/skills/reverse-engineering-signatures of villith/relink-logs.
Open the folder on GitHubat commit 8251151
Reverse Engineering Signatures next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Reverse Engineering Signatures this skillvillith/relink-logs | 156 | — | ~7.5k | Automated safety check: Pass | MIT | |
| Ghidra ReOrbitCurve/firmware-reverse-engineering | 215 | — | ~4.2k | Automated safety check: Pass | Apache-2.0 | |
| Firmware Security ReportsOrbitCurve/firmware-reverse-engineering | 215 | — | ~4.1k | Automated safety check: Pass | Apache-2.0 | |
| Bench ExperimentDavidClawson/OpenScope-2C53T | 116 | — | ~1k | Automated safety check: Pass | GPL-3.0 | |
| Go Rust Reversezhaoxuya520/reverse-skill | 40k | 2 repos | ~339 | Automated safety check: Pass | MIT | |
| Analyzing Packed Malware With Upx Unpackermukul975/Anthropic-Cybersecurity-Skills | 34k | — | ~3k | Automated safety check: Pass | Apache-2.0 |
OrbitCurve/firmware-reverse-engineering
Expert-level Ghidra reverse engineering for firmware binaries with emphasis on stripped binary analysis, automated function discovery, cryptographic routine identification, authentication logic…
OrbitCurve/firmware-reverse-engineering
Evidence-based security report generation for firmware assessments.
DavidClawson/OpenScope-2C53T
Run and record a hardware experiment on the 2C53T bench using a controlled five-step cycle.
zhaoxuya520/reverse-skill
A skill your agent uses for reverse engineering stripped Go and Rust binaries including runtime recognition, pclntab/moduel data recovery, panic strings, and idiomatic decompilation recovery.
mukul975/Anthropic-Cybersecurity-Skills
Identifies and unpacks UPX-packed malware samples, including binaries with modified UPX magic bytes or headers that block automated decompression, to recover the original executable for static…
mukul975/Anthropic-Cybersecurity-Skills
Reverse engineers malware binaries using NSA's Ghidra disassembler and decompiler to study internal logic, cryptographic routines, C2 protocols, and evasion techniques at the assembly and pseudo-C…
villith/relink-logs
A skill your agent uses when building, compiling, packaging, or producing an installer/MSI for the GBFR Logs Tauri app, or when a build fails with "path matching hook.dll not found" or build.rs…
villith/relink-logs
A skill your agent uses when working on, discussing, reviewing or debugging the Relink Logs analysis view (the log viewer's Analysis mode) — pins, drilling, grouping, the table/timeline/events…
villith/relink-logs
A skill your agent uses when a reverse-engineered table or formula — SBA gauge weights, the damage-cap model, stun values, any per-hit quantity the parser computes — needs verifying against real…
Works with
Categories
A skill your agent uses when a Granblue Fantasy Relink game patch breaks the GBFR Logs hook — signatures no longer match, "Could not find match for pattern" / "Could not find <offset" warnings…. Reverse Engineering Signatures is an agent skill from villith/relink-logs. Use when a Granblue Fantasy Relink game patch breaks the GBFR Logs hook — signatures no longer match, "Could not find match for pattern" / "Could not find <offset" warnings, hooks logging FAIL at startup, an offset reads a wrong/garbage value, or a hook crashes the game.
Reverse Engineering Signatures fits situations like: A Granblue Fantasy Relink game patch breaks the GBFR Logs hook — signatures no longer match; could not find match for pattern / Could not find <offset warnings; hooks logging FAIL at startup; an offset reads a wrong/garbage value.
Run `npx skills add villith/relink-logs --skill reverse-engineering-signatures -a claude-code`. Or copy the skill folder (.claude/skills/reverse-engineering-signatures in villith/relink-logs) into .claude/skills/reverse-engineering-signatures in your project. Claude Code loads it when a task matches its description.
Run `npx skills add villith/relink-logs --skill reverse-engineering-signatures -a codex`. Or copy the skill folder (.claude/skills/reverse-engineering-signatures in villith/relink-logs) into .agents/skills/reverse-engineering-signatures in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add villith/relink-logs --skill reverse-engineering-signatures -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/reverse-engineering-signatures, .gemini/skills/reverse-engineering-signatures, .github/skills/reverse-engineering-signatures and .opencode/skills/reverse-engineering-signatures in your project.
Going by SKILL.md and its folder, Reverse Engineering Signatures needs Java for the scripts in its folder and the command-line tools its instructions call (cargo and node).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Reverse Engineering Signatures is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.5k tokens (SKILL.md is roughly 30k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Reverse Engineering Signatures: Ghidra Re (OrbitCurve/firmware-reverse-engineering, 215 stars), Firmware Security Reports (OrbitCurve/firmware-reverse-engineering, 215 stars), Bench Experiment (DavidClawson/OpenScope-2C53T, 116 stars) and Go Rust Reverse (zhaoxuya520/reverse-skill, 40k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
villith (a GitHub user) maintains it in villith/relink-logs, which has 156 GitHub stars. The repository holds 4 skills in this directory. The repository was last updated on September 25, 2026.
Source: villith/relink-logs on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.