decomp-file
A systematic tool for decompiling entire source files function-by-function to match assembly.
Install
mkdir -p .claude/skills/decomp-file && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/12305" && unzip -o skill.zip -d .claude/skills/decomp-file && rm skill.zipInstalls to .claude/skills/decomp-file
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Decompile a whole file (segment) in this Paperboy N64 project from top to bottom. Use when asked to decompile a .cpp/.c file end-to-end, "do the whole file", convert all of <file> from INCLUDE_ASM to C/C++, or migrate a segment. Complements the single-function `decomp` skill. Triggers on "decompile <file>", "do all of", "go through the file", "convert the file", "match the file", or when the task scope is the whole TU rather than a single function.Key capabilities
- →Convert `INCLUDE_ASM` lines to C/C++
- →Build and diff after every single function
- →Maintain source order from top to bottom
- →Match C/C++ to asm filenames
- →Park functions in `NON_MATCHING` blocks
- →Generate C/C++ drafts using `m2c`
How it works
The skill processes a .cpp segment file function by function, converting `INCLUDE_ASM` to C/C++ code. It builds and diffs after each function to ensure accuracy and maintains strict source order.
Inputs & outputs
When to use decomp-file
- →Converting legacy asm to C
- →Matching full files in game projects
- →Refactoring N64 codebases
- →Migrating code segments
About this skill
Decompiling a Whole File
The goal: hand this skill a .cpp segment file full of INCLUDE_ASM lines, and walk away. By the time you finish, every function is either matched in C/C++ or parked in a #ifdef NON_MATCHING / #else INCLUDE_ASM / #endif block, the file builds cleanly into a .o, and the user comes back to a reviewable result.
The work is dominated by one inner loop. Most of this skill is about how to run that loop. Setup is brief; afterwards is "done".
Three invariants
These hold for every function you touch. Internalize them before starting.
1. The unit of work is one function. No batching, ever. Read its asm, write its body, build, diff, iterate or park, then move to the next. Build and diff after every single function — the loop is the unit of progress, not the file. Concretely:
- Never write two function bodies before building.
- Never run
asm-differacross multiple symbols in one command (nofor sym in a b c; do uv run asm-differ -o $sym; done-style loops, no scripts that diff a set). One symbol at a time, every time. - Never queue up changes across functions to verify in bulk.
Batching hides which change caused which diff and turns a tractable iteration into a guessing game.
2. Source order is fixed; walk the file top to bottom. asm-differ aligns functions by their position in the original map file. If you write a function out of order, every diff downstream shifts and lies. The first INCLUDE_ASM you replace is the topmost one, the second is the next one down, always.
3. The asm filename dictates the symbol your C/C++ must compile to. The asm filenames are fixed. asm-differ anchors on the symbol name, and expected/ was built against the current names. Two cases:
- The
.sfile isfunc_NNNN.s→ writeextern "C" ReturnType func_NNNN(...) { ... }. Theextern "C"suppresses C++ mangling so the compiled symbol staysfunc_NNNN. - The
.sfile is cfront-mangled (open__10JamArchive...s,__10JamArchive.s,_._10JamArchive.s) → write a normal C++ member / ctor / dtor. The compiler mangles it back to the matching symbol.
Do not edit symbol_addrs.txt or run configure.py --clean to rename anything during this skill — not even for an obvious ctor/dtor. Reconfiguring regenerates asm filenames in the current tree while expected/ keeps the old names, so asm-differ has nothing to anchor to and the diff breaks. cfront-style mangling is also fiddly enough that the name you'd pick is unlikely to match what cfront actually emits without iteration. Renaming func_NNNN to a mangled symbol is a separate later phase, not part of this skill. The asm filenames are whatever they are; you write C/C++ to match them.
Scope
In: writing C/C++ for each function in this one .cpp segment, getting the .o to match asm-differ.
Out: cross-TU work, full ROM links, defining vtable data symbols (leave vtables as extern), preserving rodata symbol names from .s (inline the values as C literals — see loop step 3), renaming externally-visible symbols of other TUs, propagating types into include/structs.h, editing symbol_addrs.txt, running configure.py.
All types and struct declarations stay local to this .cpp. External functions and variables get extern (or extern "C") declarations at the top of this file.
Setup
A short read of the asm to understand the class shape, then one build to confirm the skeleton compiles.
1. Inventory the asm directory
ls asm/nonmatchings/<segment>/. The list, in alphanumeric order, is the file's full set of functions (alphanumeric = address order). This is your work queue.
The filenames tell you what symbol each function compiles to:
__<N><Class>.s— ctor ofClass(write as C++ ctor)_._<N><Class>.s— dtor (write as C++ dtor)<method>__<N><Class>...s— method (write as C++ member function)func_NNNN.s— write asextern "C" func_NNNN(...), regardless of whether it's conceptually a free helper or a method that just hasn't been mangled. The filename is the constraint.
2. Identify the class shape
You need data-member offsets/types and the vtable layout so the C++ class declaration is right. Read asm to find them:
- The ctor (whatever its filename): a function that loads a rodata address via
lui/addiuand stores it tothis->+N(the vptr slot). It usually stores immediate constants to otherthis->+Moffsets first. Those stores reveal the data members:sb= 1-byte field,sw= 4-byte field, the immediate value = the field's initial value. The vptr store comes last. - The vtable: follow the ctor's
lui/addiuinto rodata. Each entry is{s16 this_offset, s16 pad, void* fn}= 8 bytes. Slot 0 is the cfront pad; real virtuals start at slot 1, in declaration order. - The dtor: called from
deletepatterns; takesthisas its onlya0. Its slot in the vtable tells you where it sits in the declared order.
End of this step you should have: the class name (or a working name), data members with offsets and types, and the vtable in declaration order.
How much of this you express in C++ depends on the asm filenames you have to match:
- All methods cfront-mangled → write a full C++ class with
virtualkeywords matching the vtable, ctor/dtor as C++ ctor/dtor. - Some methods still
func_NNNN.s→ write a plain struct (data members only). Define each function asextern "C" ReturnType func_NNNN(Class* this, ...)with the receiver as an explicit first arg. Don't try to express the vtable as C++ virtuals; the mixed-mangling case doesn't map cleanly, and renamingfunc_NNNNto a mangled symbol is out of scope here (see invariant #3).
3. (Optional) reference lookup
If you know of a similar class decompiled elsewhere (another game, another project), peek at it for structural hints: field names, virtual signatures. Use as a hint about shape, not as source for code — different compilers emit different code.
4. Write the skeleton
In the .cpp:
- Top:
extern "C"declarations for every external function/variable the asm references (jal targets, data referenced vialui/addiu). For rodata symbols and vtables,externis enough — you're not defining them. - Then: local struct/class declarations. Data members in offset order. Virtuals declared with the C++
virtualkeyword, in vtable order. Mangled methods declared as plain C++ members. (For virtuals whose asm filename is stillfunc_NNNN, you'll define them asextern "C"outside the class and the vtable entry's referenced symbol matches — see invariant #3.) - Then: one
INCLUDE_ASM("asm/nonmatchings/<seg>", <symbol>);for every function in the asm directory, in source order.<symbol>is the asm filename without extension.
5. Confirm the skeleton
ninja build/ntsc/src/<file>.o
Spot-check one function with uv run asm-differ -o <symbol>. Every body is still asm, so every diff should be clean. If a diff shows up against an INCLUDE_ASM line, your class layout is wrong — fix the declaration before entering the loop.
The loop
You're at the top of the file, looking at the first INCLUDE_ASM line. Walking top to bottom, for each INCLUDE_ASM:
read asm → draft body → replace INCLUDE_ASM → build → diff
├─ matches? → next function
├─ retry (≤5) → adjust C, build, diff again
└─ budget out → park → next function
Per-function steps
-
Read the function's full asm. Every instruction.
-
(Optional) m2c draft for control-flow scaffolding:
uv run m2c asm/nonmatchings/<seg>/<func>.sPass the rodata file too if the function has a jump table or float consts. m2c gives you a starting point, not a finished translation.
-
Replace the
INCLUDE_ASMline with the body. Apply invariant #3's mangling rule:.sisfunc_NNNN.s→extern "C" ReturnType func_NNNN(...) { ... }.sis mangled → matching C++ member function / ctor / dtor (noextern "C")
Use C++ forms:
new/new[],delete/delete[], member-function calls. Locally-declared types and casts stay inside this.cpp.No
goto/labels in the C source. If you find yourself wanting to addgoto check;or similar to mimic the compiler's emitted control flow shape, stop and park the function instead. Readable non-matching C beats goto-spaghetti that matches.Rodata referenced from the function body — inline the literal, don't preserve the symbol. If the asm references
D_NNNNfor a string, float constant, or similar value, look up its content (in the function's own.svia.section .rdata, or in a separateasm/data/*.rodata.sfile) and write the literal in C. Example: an asm reference toD_80000CE0whose rodata is"."becomesstrcat(dst, ".")in C, notstrcat(dst, D_80000CE0). The compiler re-emits the literal into rodata at the right address. SkipINCLUDE_RODATAentirely — it's not needed for this workflow. -
Build:
ninja build/ntsc/src/<file>.oCompile errors block; fix them. (Single-file build produces only the
.o, so no link errors at this stage.) -
Diff:
uv run asm-differ -o <symbol><symbol>is the asm filename without extension.</>markers — real code differences. Adjust the C, retry.rmarkers — same instructions, different register allocation. Sometimes a sign that variable ordering or a type is slightly off; sometimes the compiler's register choices have no straightforward C-source lever. If obvious variants don't move it within budget, park.imarkers — symbol-name substitution (e.g.D_80004898vs_vt.8JamArchive). Usually fine.
Read the markers, not the score. asm-differ's numeric score also counts label
Content truncated.
When not to use it
- →When performing cross-TU work
- →When defining vtable data symbols
- →When editing `symbol_addrs.txt` or running `configure.py`
Limitations
- →Does not handle cross-TU work
- →Does not define vtable data symbols
- →Does not preserve rodata symbol names from .s files
How it compares
This skill enforces a disciplined, iterative process of decompilation with continuous verification, unlike a bulk conversion that might introduce multiple errors simultaneously.
Compared to similar skills
decomp-file side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| decomp-file (this skill) | 0 | 2mo | Review | Advanced |
| add-uint-support | 18 | 9mo | No flags | Intermediate |
| cpp-pro | 18 | 4mo | No flags | Advanced |
| unreal-engine-cpp-pro | 43 | 4mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
add-uint-support
pytorch
Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.
cpp-pro
sickn33
Write idiomatic C++ code with modern features, RAII, smart pointers, and STL algorithms. Handles templates, move semantics, and performance optimization. Use PROACTIVELY for C++ refactoring, memory safety, or complex C++ patterns.
unreal-engine-cpp-pro
sickn33
Expert guide for Unreal Engine 5.x C++ development, covering UObject hygiene, performance patterns, and best practices.
at-dispatch-v2
pytorch
Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.
defi-protocol-templates
wshobson
Implement DeFi protocols with production-ready templates for staking, AMMs, governance, and lending systems. Use when building decentralized finance applications or smart contract protocols.
llvm-tooling
gmh5225
Expertise in LLVM tooling development including Clang plugins, LLDB debugger extensions, Clangd/LSP, and LibTooling. Use this skill when building source code analysis tools, refactoring tools, debugger extensions, or IDE integrations.