Deterministic PyGhidra extraction, triage, and local LLM compiler-backend pipeline for C reimplementation.
Thank you for downloading my Ghidra + LLM pipeline.
This is my first README. ASL (American Sign Language) is my first language, so my English is not perfect. I did my best.
I love an old PC simulation I have played since childhood, and I want to improve it under the hood.
Looking at open-source games like OpenRA and OpenTTD taught me this: a hobbyist reimplementation is not about getting the original source code. It is about building a new open-source version of the game that can load the assets of the original. Then anyone can expand it or contribute.
To do the work I use two kinds of AI together:
Every function gets one label. This saves a lot of time, because I do not waste effort on the big pool of OS and runtime junk.
|-----------|------------------------------------------------------------------------------------------------------------------
| Label | Meaning |
|---|---|
| KEEP | The game's own logic (for example the flight model, the damage model, TrackIR) |
| WRAP | Java-facing glue that calls OS or input APIs: keep the signature, write a new body. |
| (IL-2 1946 is an interesting engine: C++ and Java together.) | |
| REPLACE | Old C runtime, allocator, thunks: the compiler or OS provides them, so a new 64-bit version does not need them |
| UNKNOWN | Not clear: look at it by hand |
| ----------- | ------------------------------------------------------------------------------------------------------------------ |
In my test program, about 44% of the functions were REPLACE, so they never needed to be converted.
At the start I trusted the C pseudocode to have all the answers. That was naive. It is Ghidra's interpretation of the assembly, and it guesses (variable types, lost arguments).
The assembly is the raw truth. It has no variable types, but it holds the exact behavior of each function. So the scripts read the assembly, and turn it into a blueprint in C-like form. Ghidra's C is kept only as a hint.
My early tests used the local LLMs (Qwen 2.5 Coder, DeepSeek Coder V2 Lite, Mellum 2) to read the assembly directly. It never worked reliably. They hallucinated, because they are made to write code, not to read raw machine code.
After many failed experiments I split the work:
NOTE: How Python checks the answer
Text checks: the function name, every call, global and string from the blueprint, the loops, the step sizes, the compared constants, the return value, and that no register names or invented names are left.
gcc compile check: the code is compiled with gcc (syntax only, nothing is run). This helped a lot. In an early run the text checks passed Mellum 2's answers, but none of them compiled, because of a header comment written outside a comment. gcc catches what text checks cannot see: undeclared or redeclared variables, wrong types, and broken braces or semicolons. A function counts as PASS only when the text checks and the compile are both OK. gcc does not prove the logic is right, so I still read the code against the blueprint.
If a check fails, the LLM gets the list of what failed and tries once more.
Python fixes small slips by itself, without the LLM: it declares each variable once, wraps loose header lines in a comment, makes the last return return the right result, and adds the includes and declarations.
The checklist came after I saw the LLMs fail on very long functions. It works like the checklist a pilot uses before take-off and landing. Python writes it from the function, and checks the answer against it. In my tests the long functions started to pass, and the runs got faster.
|-------------------|---------------------------------------|---------------------------------------------
| Folder | What it does | Read |
|---|---|---|
ghidra_pipeline | Dump, grouping, triage, roles | ghidra_pipeline/INSTRUCTION.txt |
llm_pipeline | Blueprints and the LLM runner (v3) | llm_pipeline/INSTRUCTION_BLUEPRINT_LLM.txt |
| ------------------- | --------------------------------------- | --------------------------------------------- |
Start with ghidra_pipeline, then go to llm_pipeline when you are ready to use your local LLMs.
First, analyze your program in PyGhidra and run PyGhidra Scripts/decompile_full_dump.py.
The two INSTRUCTION files explain the folder setup. Then:
First, analyze your program in PyGhidra and run PyGhidra Scripts/decompile_full_dump.py.
The two INSTRUCTION files explain the folder setup. Then:
python .\make_full_lite_v2.py --in . --out .\OUT
python .\triage_evidence_v2.py --lite .\OUT\LITE_AC
python .\build_roles_v2.py --triage .\OUT\TRIAGE
python .\blueprint_v3.py --group ".\SOURCE\LITE_AC\<GroupName>" --out ".\G_<GroupName>\BLUEPRINT_v3"
python .\run_blueprint_llm_v3.py --bp ".\G_<GroupName>\BLUEPRINT_v3" --out ".\G_<GroupName>\RUN1_v3_<model>" --model <model name>
You need Ghidra with PyGhidra, Python 3, LM Studio (one model loaded), and gcc (recommended, used as a compile check).