Skip to content

Pass patcher related options via config#11

Open
4z0t wants to merge 20 commits intomainfrom
patcher-config
Open

Pass patcher related options via config#11
4z0t wants to merge 20 commits intomainfrom
patcher-config

Conversation

@4z0t
Copy link
Member

@4z0t 4z0t commented Jan 26, 2026

Summary by CodeRabbit

  • New Features

    • JSON-configurable build/patch settings with centralized paths, toolchain options, and symbol mappings.
    • Example configuration file included.
  • Refactor

    • Core patch flow rewritten to be configuration-driven and use template-based command construction; command execution now validates template input.
  • Bug Fixes

    • Command-line argument handling corrected.
    • Missing-section lookup now raises explicit errors.
  • Chores

    • CI workflow and Python/tooling version updates.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Warning

Rate limit exceeded

@4z0t has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 17 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Introduces a new Config dataclass and example JSON; refactors the patch flow to load configuration and build Template-based commands; run_system now accepts a Template; PEData.find_sect raises on missing sections; main forwards argv[1:]; CI/workflow and docs updated for the config-driven flow.

Changes

Cohort / File(s) Summary
Configuration Infrastructure
patcher/Config.py, config_example.json
Add Config dataclass with load_from_json(), __post_init__ path normalization/validation, input_path/output_path properties, and defaults for compilers/flags/functions; include example JSON.
Patcher Refactor
patcher/Patcher.py
Replace previous argument interface with patch(config_path) that loads Config; build shell commands via string.Template; run_system now accepts a Template; all paths derived from Config (input, target, build, output).
PE Data Error Handling
patcher/PEData.py
find_sect() now raises an exception when a requested section is missing instead of returning None.
CLI Entry
main.py
main now passes only sys.argv[1:] to the patcher entry (skips script name).
CI / Docs
.github/workflows/test.yml, README.md, SETUP.md
Workflow checkout/path and Python version updated; build and invocation paths adjusted to use config_example.json; documentation updated to reflect newer tool/Python versions and minimum Python requirement.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Main as "main.py"
    participant Config as "patcher/Config.py"
    participant Patcher as "patcher/Patcher.py"
    participant FS as "Filesystem"
    participant Tool as "Compiler/Linker"
    rect rgba(200,200,255,0.5)
    User->>Main: run script with config path
    Main->>Patcher: call patch(config_path)
    end
    rect rgba(200,255,200,0.5)
    Patcher->>Config: Config.load_from_json(path)
    Config-->>Patcher: Config(instance with paths, flags, functions)
    end
    rect rgba(255,230,200,0.5)
    Patcher->>FS: read input binary, hooks, headers
    Patcher->>FS: write build artifacts (section.ld, patch.pe, defines)
    FS-->>Patcher: artifact paths
    end
    rect rgba(255,200,200,0.5)
    Patcher->>Tool: run_system(template) — compile/link via Template
    Tool-->>Patcher: exit status, outputs
    end
    rect rgba(230,255,255,0.5)
    Patcher->>FS: write final output to config.output_path
    Patcher-->>User: completion / errors
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 A little config hop, neat and spry,
Paths in a row, flags aiming high,
Templates whisper to compilers near,
Artifacts tumble, the goal is clear,
Patch-ready carrots — nibbles of joy!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Pass patcher related options via config' directly and clearly summarizes the main change: transitioning from hardcoded parameters to config-driven configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch patcher-config

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@patcher/Config.py`:
- Around line 24-33: In load_from_json (the Config classmethod) resolve the
incoming Path before using it to derive defaults so relative config paths won't
produce a relative target_folder_path that fails __post_init__; i.e.,
convert/resolve the path to an absolute Path at the start of load_from_json,
then open that resolved path and use resolved_path.parent as the default for
target_folder_path while keeping the rest of the keys the same.

In `@patcher/Patcher.py`:
- Around line 331-335: The shell command passed to run_system is building an
unquoted string using config.clang_path, the joined config.clang_flags,
include_folder_path, section_folder_path / "main.cxx", and build_folder_path /
"clangfile.o" which will break on paths containing spaces; update the run_system
invocations (the one shown and the other occurrences noted around the file) to
safely quote or shell-escape each path and each flag before joining (e.g., wrap
config.clang_path, each element from config.clang_flags, include_folder_path,
the section_folder_path/"main.cxx" token, and the
build_folder_path/"clangfile.o" token in quotes or use a function to
shell-escape them) so the command string passed to run_system treats each
path/flag as a single argument.
- Around line 467-468: The format spec in the exception message uses a stray
space in the hex formatter; replace the format expression f"0x{exxt_sect.f_size:
x}" with a proper hex specifier (e.g. f"0x{exxt_sect.f_size:x}") in the raise
Exception call so the output reads like 0x80000 (reference: exxt_sect.f_size in
the raise Exception block).
🧹 Nitpick comments (2)
patcher/Config.py (1)

18-20: Flag type hints don’t match JSON shape.

tuple[str] is a fixed‑length 1‑tuple, while JSON loads lists. Consider tuple[str, ...] or Sequence[str], and normalize to tuples in __post_init__.

♻️ Proposed refactor
-from typing import Self
+from typing import Self, Sequence
 ...
-    clang_flags: tuple[str] = ()
-    gcc_flags: tuple[str] = ()
-    asm_flags: tuple[str] = ()
+    clang_flags: Sequence[str] = ()
+    gcc_flags: Sequence[str] = ()
+    asm_flags: Sequence[str] = ()
 ...
     def __post_init__(self):
         self.target_folder_path = Path(self.target_folder_path)
         self.build_folder_path = Path(self.build_folder_path)\
             if self.build_folder_path \
             else self.target_folder_path / "build"
 
         self.clang_path = Path(self.clang_path)
         self.gcc_path = Path(self.gcc_path)
         self.linker_path = Path(self.linker_path)
+
+        self.clang_flags = tuple(self.clang_flags)
+        self.gcc_flags = tuple(self.gcc_flags)
+        self.asm_flags = tuple(self.asm_flags)

Also applies to: 42-50

patcher/PEData.py (1)

75-79: Align find_sect's contract with its new behavior.

The return type Optional[PESect] is misleading—the method always raises Exception and never returns None. Call sites in Patcher.py (lines 450, 456) don't check for None and directly use the result, relying on exception handling.

Consider a dedicated exception (e.g., SectionNotFoundError) and a non-optional return type (PESect) to clarify the actual contract.

♻️ Proposed refactor
+class SectionNotFoundError(LookupError):
+    pass
+
 ...
-def find_sect(self, name: str) -> Optional[PESect]:
+def find_sect(self, name: str) -> PESect:
     for sect in self.sects:
         if sect.name == name:
             return sect
-    raise Exception(f"Couldn't find section {name}")
+    raise SectionNotFoundError(f"Couldn't find section {name}")

@4z0t
Copy link
Member Author

4z0t commented Jan 26, 2026

Jesus this language again shows me how well designed it is... I have to call str everywhere when path is passed into a shell... bruh f-strings suck nvm Path doesn't even support that natively xd

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@patcher/Config.py`:
- Around line 52-62: The build_folder_path value is converted to a Path but not
resolved to an absolute path like target_folder_path, causing the is_absolute()
check to fail for relative inputs; update the Config initialization so after
converting self.build_folder_path to a Path, if it is not absolute then join it
with self.target_folder_path (e.g., self.target_folder_path /
self.build_folder_path) and resolve it to an absolute Path before running the
existing validation; reference the build_folder_path and target_folder_path
attributes in Config to locate where to perform this normalization.

In `@patcher/Patcher.py`:
- Around line 290-306: The run_system function currently only quotes Path values
with spaces and concatenates parts unsafely, so replace the ad-hoc quoting with
proper shell escaping: import shlex and, inside run_system (function name) when
iterating template parts (loop over part and using part.value), call
shlex.quote(str(value)) for non-string parts instead of the current Path/space
check or manual f"\"{value}\"" quoting; also build the final command by joining
the command list with a space (e.g., " ".join(command)) and keep the existing
newline replacement before calling os.system(command.replace("\n", " ")).
- Line 12: The README's Versions section is out of date: the code imports
string.templatelib (which requires Python 3.14/PEP 750), so update the README
Versions entry that currently says "python: 3.12.4" to "python: 3.14" to match
the CI and the import usage; search for the README Versions block and replace
the Python version string accordingly so documentation aligns with the use of
string.templatelib in Patcher.py.
🧹 Nitpick comments (4)
main.py (1)

7-7: Argument unpacking mismatch with patch() signature.

patch() expects a single config_path argument, but *sys.argv[1:] unpacks all CLI arguments. If the user provides zero or more than one argument, this raises a TypeError with a confusing message. Consider passing the argument directly and validating input.

♻️ Suggested improvement
 if __name__ == "__main__":
+    if len(sys.argv) != 2:
+        print(f"Usage: {sys.argv[0]} <config_path>")
+        sys.exit(1)
     start = time.time()
-    patcher.patch(*sys.argv[1:])
+    patcher.patch(sys.argv[1])
     end = time.time()
     print(f"Patched in {end-start:.2f}s")
config_example.json (1)

45-57: Inconsistent hex address formatting.

Some addresses have leading zeros ("0x00A89950", "0x00A848E8") while others don't ("0xA8211E"). This inconsistency doesn't affect functionality but could indicate copy-paste errors. Consider normalizing the format.

♻️ Suggested normalization
-        "__CxxThrowException@8": "0x00A89950",
+        "__CxxThrowException@8": "0xA89950",
         "_memset": "0xA89110",
-        "__invoke_watson": "0x00A848E8"
+        "__invoke_watson": "0xA848E8"
patcher/Config.py (1)

19-21: Type annotations don't match actual values from JSON.

The annotations specify tuple[str] but json.load() returns lists, and there's no conversion. This works at runtime but may confuse type checkers.

♻️ Consider using `list[str]` or converting to tuples

Either change the type hints:

-    clang_flags: tuple[str] = ()
-    gcc_flags: tuple[str] = ()
-    asm_flags: tuple[str] = ()
+    clang_flags: list[str] = field(default_factory=list)
+    gcc_flags: list[str] = field(default_factory=list)
+    asm_flags: list[str] = field(default_factory=list)

Or convert in load_from_json:

-            clang_flags=config.get("clang_flags", Config.clang_flags),
+            clang_flags=tuple(config.get("clang_flags", Config.clang_flags)),
patcher/Patcher.py (1)

352-355: Unusual indentation on raise statements.

The raise Exception(...) statements are indented more deeply than the surrounding code. While syntactically valid, this harms readability and looks like a formatting error.

♻️ Consistent indentation
     if run_system(
         t""" cd {build_folder_path} &
             {config.gcc_path} {" ".join(config.gcc_flags)}             -I {include_folder_path}             -Wl,-T,section.ld,--image-base,{image_base},-s,-Map,sectmap.txt,-o,section.pe             {section_folder_path / "main.cpp"}"""):
-                raise Exception("Errors occurred during building of patch files")
+        raise Exception("Errors occurred during building of patch files")

Apply similar fix to lines 384 and 444.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/test.yml:
- Around line 71-72: The workflow fails because the repo is checked out to the
workspace root but the job later runs the command sequence "mkdir
FA-Binary-Patches/build" followed by "cd fa-python-binary-patcher & python
main.py config_example.json" which expects a fa-python-binary-patcher directory;
fix by either updating the checkout step to set path: fa-python-binary-patcher
so that "cd fa-python-binary-patcher" will succeed, or remove the "cd
fa-python-binary-patcher" and run "python main.py config_example.json" from the
workspace root after "mkdir FA-Binary-Patches/build" so the commands run in the
correct location.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@patcher/Patcher.py`:
- Around line 312-314: The command interpolation is joining flag lists into a
single string which becomes one quoted argument; update run_system to detect
list/tuple values and shlex.quote each element individually (joining them with
spaces) so flags are passed as separate arguments, then change the call sites
that currently do " ".join(config.clang_flags) (and other flag joins used around
the build commands in Patcher.py — e.g., the occurrences near the current uses
of config.clang_flags at the compile lines and the other flag interpolations
referenced) to pass the raw list/tuple (config.clang_flags, etc.) instead of a
pre-joined string so run_system can handle proper quoting per-element.
🧹 Nitpick comments (3)
patcher/Patcher.py (3)

6-6: Unused json import.

The json module is imported but not used in this file. The JSON loading appears to be handled within Config.load_from_json.

🧹 Suggested fix
-import json

275-276: Add type hint for config_path parameter.

The function signature lacks a type annotation. Since it's passed to Path(), adding a type hint improves clarity and enables better static analysis.

✨ Suggested fix
-def patch(config_path):
+def patch(config_path: str | Path):

318-321: Excessive whitespace in command template.

The multi-line template string preserves indentation spaces, resulting in commands like gcc -c -I ... in logs. While not functionally broken, it makes debugging harder.

✨ Suggested fix - use line continuation or dedicated formatting
     if run_system(
-        t""" cd {build_folder_path} &
-            {config.gcc_path} {" ".join(config.gcc_flags)}             -I {include_folder_path}             -Wl,-T,section.ld,--image-base,{image_base},-s,-Map,sectmap.txt,-o,section.pe             {section_folder_path / "main.cpp"}"""):
+        t"""cd {build_folder_path} & {config.gcc_path} {config.gcc_flags} -I {include_folder_path} -Wl,-T,section.ld,--image-base,{image_base},-s,-Map,sectmap.txt,-o,section.pe {section_folder_path / "main.cpp"}"""):
                 raise Exception("Errors occurred during building of patch files")

@4z0t
Copy link
Member Author

4z0t commented Jan 28, 2026

Can we remove this code rabbit thing completely? It is annoying and doesn't provide any useful tips...

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@patcher/Patcher.py`:
- Around line 258-273: In run_system, non-Path interpolated template parts
(variable value from `part.value`) may be integers and are appended directly to
`command`, causing `"".join(command)` to raise a TypeError; convert any
non-Path, non-str values to strings before appending. Locate the loop over
`template` in `run_system` (inspect `part`, `value`, and `command`) and ensure
branches: if `isinstance(value, Path)` use shlex.quote(str(value)); elif
`isinstance(value, str)` append as-is; else append `str(value)` so all entries
in `command` are strings before joining.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@patcher/Patcher.py`:
- Around line 322-325: The shell command passed to run_system in Patcher.py uses
the single-ampersand operator which runs the next command unconditionally;
update the command strings used in the run_system calls (the multi-line t"""...
cd {build_folder_path} & {config.gcc_path} ...""" occurrences tied to the build
steps) to use '&&' instead of '&' so that cd failure prevents running the
compiler/linker; apply the same replacement for the other two occurrences of the
same pattern found later in the file (the other run_system invocations around
the build steps).
🧹 Nitpick comments (1)
SETUP.md (1)

4-4: Use descriptive link text instead of "here".

Static analysis (MD059) flags the link text "here" as non-descriptive. Screen readers and scanners benefit from self-explanatory anchor text.

Suggested fix
-Install newest version [here](https://www.python.org/downloads/) (At least 3.14). 
+Install the newest version from the [Python downloads page](https://www.python.org/downloads/) (at least 3.14). 

Comment on lines 322 to +325
if run_system(
f"""cd {build_folder_path} &
{gcc_compiler_path} {GCC_FLAGS}
-I ../include/ {includes}
-Wl,-T,../section.ld,--image-base,{base_pe.imgbase + new_v_offset - 0x1000},-s,-Map,sectmap.txt,-o,section.pe
../section/main.cpp"""):
raise Exception("Errors occurred during building of patch files")
t""" cd {build_folder_path} &
{config.gcc_path} {" ".join(config.gcc_flags)} -I {include_folder_path} -Wl,-T,section.ld,--image-base,{image_base},-s,-Map,sectmap.txt,-o,section.pe {section_folder_path / "main.cpp"}"""):
raise Exception("Errors occurred during building of patch files")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use && instead of & to fail fast if cd fails.

Windows cmd.exe & runs the next command unconditionally. If cd {build_folder_path} fails (directory missing or mistyped), the gcc/linker commands execute in the wrong directory, producing misleading errors about missing files. The same pattern appears at lines 352 and 412.

Suggested fix (apply to all three occurrences)
-        t""" cd {build_folder_path} &
+        t""" cd {build_folder_path} &&
🧰 Tools
🪛 Ruff (0.14.14)

[warning] 325-325: Create your own exception

(TRY002)


[warning] 325-325: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In `@patcher/Patcher.py` around lines 322 - 325, The shell command passed to
run_system in Patcher.py uses the single-ampersand operator which runs the next
command unconditionally; update the command strings used in the run_system calls
(the multi-line t"""... cd {build_folder_path} & {config.gcc_path} ..."""
occurrences tied to the build steps) to use '&&' instead of '&' so that cd
failure prevents running the compiler/linker; apply the same replacement for the
other two occurrences of the same pattern found later in the file (the other
run_system invocations around the build steps).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants