Skip to content

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 updated (build paths and Python version).

✏️ Tip: You can customize this high-level summary in your review settings.

@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 3 minutes and 50 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

Adds a Config dataclass and example JSON; refactors patcher to load configuration and build Template-based commands; run_system now accepts a Template; PEData.find_sect now raises when missing; main forwards argv[1:]; CI workflow updated to run the config-driven flow.

Changes

Cohort / File(s) Summary
Configuration Infrastructure
patcher/Config.py, config_example.json
New Config dataclass with load_from_json(), __post_init__ path normalization, input_path/output_path properties, compiler/linker/flag defaults, and an example JSON mapping functions to addresses.
Patcher Refactor
patcher/Patcher.py
Replaced prior argument interface with patch(config_path) that loads Config; command construction moved to string.Template; run_system now accepts a Template; file paths derived from Config (input, target, build, output).
PE Data Error Handling
patcher/PEData.py
find_sect() now raises an exception when a section is missing instead of returning None.
CLI Entry
main.py
main now forwards only sys.argv[1:] to the patcher entry.
CI Workflow
.github/workflows/test.yml
Workflow checkout/path and Python version updated; base executable and build/invocation paths adjusted to use the repository layout and config_example.json.

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

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 A neat config hopped into view,
Paths and flags all tidy and true,
Templates hum and compilers play,
Artifacts tumble out to stay,
Hooray — the patcher leaps anew!

🚥 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 accurately summarizes the main change: migrating patcher options from command-line arguments to a JSON configuration file.

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


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.

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