Repository navigation
cpp_permuter: a source permuter for matching C++ functions (VC6 / gta2_re) - #1
Merged
Merged
Conversation
A C++17 tool in the spirit of decomp-permuter for MSVC 6 matching projects such as gta2_re. It locates one function in a .cpp, parses its body with a tolerant statement/expression parser (no preprocessing, so the rest of the file stays byte-identical), applies mutation passes, compiles each candidate with a user command and scores the function's disassembly (llvm-objdump) against a target object. Passes: reorder_saves (every dependency-respecting order of runs of local saves), move_stmt, inline_local, sink_decl, hoist_decl, merge_decl, swap_operands, flip_compare, invert_if, ternary, compound_assign. Exhaustive and random search, parallel compiles, dry runs, and a VC6-under-wine compile script for gta2_re. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Passes can now be combined: -p a+b tries each pass's variants and every in-order chain of them (exhaustive), or one mutation of each (random). -p can be given more than once. Integration tests (ctest 'integration', clang -O0 i686-msvc) check that the permuter turns a wrong base.cpp into code that matches the target, and that the matched source is exactly target.cpp. Negative controls run the wrong passes and must not match. Cases: var_reorder, combo (needs reorder_saves+invert_if; single passes fail), inline_local, ops. The 'vc6' suite does the same with real MSVC 6 under wine on gta2_re's PedGroup::PromoteMemberToLeader_4C9680: scrambled saves (and an inverted if) are permuted back to the original source. Skipped without GTA2_RE. inline_local no longer wraps a substituted initializer in parentheses when the use is a whole operand on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…check New passes, from gta2_re's matching_quirks.md: bool_return, ternary_arg, switch_if, early_return, branch_dup, cond_split, explicit_compare, negate_const, loop_form, reassociate. --inline-callees / --also [FILE:]NAME permute the inline helpers a function calls (found through its #include "..." headers, overloads filtered by argument count) along with the function. Candidates are now compiled in a per-worker mirror of the source tree (symlinks plus edited copies) so an edited header is the one every #include sees; the user's files are never written. Paths keep symlinks (resolving them sent a symlinked source tree back to its origin). --check-parse parses every function in the given files and runs every pass on each. On gta2_re all 4235 functions parse, 2 of 53552 expressions are not understood, and every candidate parses back. The expression parser now handles new/delete, operator new/delete and type arguments to macros such as va_arg; definitions are no longer confused with macro invocations before them. Tests: unit tests for every new pass (both directions), definition listing and the workspace/mirror; clang integration cases for seven new passes and for an inline header helper, each with a control run of every other pass; gta2_parse over all gta2_re sources; and VC6 cases recovering RemovePed (ternary_arg), IsTrainOrBoxcar (bool_return), UpdateShortAnim (switch_if) and edits to Fix16::Max/Abs inlined into PedGroup, plus a VC6 compile-rate check of random candidates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…IT license - PERM_GENERAL/LINESWAP/INT/ONCE/VAR/RANDOMIZE/FORCE_SAMELINE/IGNORE/ PRETEND in the target function, as in decomp-permuter: expanded at random or exhaustively; the passes only run on top with PERM_RANDOMIZE or an explicit -p. -p none turns the passes off. - --show-timings: permuting/writing/compiling/scoring. Measured on VC6: 0.5 ms to permute, 601 ms to compile per candidate. - examples/gta2/compile.sh uses VC6 precompiled headers (/YX, /Zm500) per worker: ~2x faster compiles, identical code for every function in PedGroup.cpp; VC6 rebuilds the .pch when a header changes. The mirror only rewrites files whose content changed so the .pch stays valid. - --weight NAME=N, --keep-prob, --keep-ties, --only-if-below, --base-only, --list-definitions. - README credits decomp-permuter; MIT license like decomp-permuter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New passes: inequalities, chain_assign, scope_block, local_type, locals_to_array, reorder_cases, split_case_labels, use_getter (inline getters found in the included headers), temp_for_expr (types from the declarations) and remove_stmt. IDO-only decomp-permuter passes that VC6 folds away are left out. Tests: unit tests for each pass and for the PERM macro expander; clang integration cases for the passes that change -O0 code and for PERM macros; VC6 cases for use_getter, inequalities and PERM_LINESWAP. Fixes: precompiled headers are turned off (PERMUTER_NO_PCH) while headers are permuted, since VC6 checks them by timestamp and missed edits made in the same second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- runner: a Windows implementation (CreateProcess through cmd.exe, a job object to kill the process tree on timeout, extra environment) and cmd-style quoting. The mirror falls back to hard links or copies where symlinks aren't allowed. Checked with a MinGW build under wine: unit tests pass and VC6 compiles through examples/gta2/compile.cmd. - examples/gta2/compile.cmd: the VC6 compile script for cmd.exe. - CI on Linux (GCC) and Windows (MSVC): unit and integration tests. - docs/passes.md: an example of every pass, generated by --pass-examples from src/examples.cpp; a unit test keeps it in sync. - README: Windows, PERM macros, the new options, measured timings and why other-function stripping isn't offered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A C++17 tool, heavily inspired by decomp-permuter, that permutes the source of one C++ function until its compiled code matches a target object. It is built for gta2_re and MSVC 6. MIT licensed, like decomp-permuter.
What it does
.cppand parses only its body. There is no preprocessing, so the rest of the file stays byte-identical.llvm-objdump) against the target. A score of 0 means a byte-for-byte match.docs/matching_quirks.mdor from decomp-permuter. Examples: every order of local saves, inline/sink/hoist/merge locals, ternary andif/elseforms, one-caseswitch, inline getters, inequalities, case order and labels, local types and arrays, temporaries.-p a+b), with-prepeatable;PERM_*macros;--inline-callees/--alsopermute helpers in headers that VC6 inlines. Candidates compile in a per-worker mirror of the source tree, so the checkout is never written to.examples/gta2/compile.shand.cmduse VC6 precompiled headers, which halves compile time with identical code. They are turned off while headers are permuted, because VC6 only checks them by timestamp.cmd.exe, job objects, and a hard-link or copy fallback for the mirror).Testing
base/and must matchtarget/, giving back the exact target source where expected. Controls without the pass under test must not match.PromoteMemberToLeader, including a combo case;RemovePed(ternary_arg);IsTrainOrBoxcar(bool_return);sub_4C9150(inequalities); and edits toFix16::Max/Absinlined intoPedGroup.UpdateShortAnim(switch_if) andIsLeaderCloseToTargetCar(use_getter). The latter has a second matching source: it reads the car's x throughCar_BC::get_x_41E430()instead of expanding the original getter.mainis a single empty initial commit, so this PR contains the whole tool. After merging, setmainas the default branch in the repo settings.🤖 Generated with Claude Code
Generated by Claude Code