-
-
Notifications
You must be signed in to change notification settings - Fork 5
Add payload for YARPE AUTO LOAD using Persistent file. #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add payload for YARPE AUTO LOAD using Persistent file. #8
Conversation
WalkthroughAdds a new payload module that injects a serializable trigger into Ren'Py's persistent object, reserializes and compresses it, overwrites the on-disk persistent save, and attempts to terminate the running process after installation. The trigger executes embedded bootstrap SCRIPT when the save is later loaded. Changes
Sequence DiagramsequenceDiagram
participant Tool as payloads/force_persistent.py
participant Persistent as Ren'Py persistent (in-memory)
participant Disk as /saves/persistent (on-disk)
participant Game as Ren'Py game process
rect rgb(220, 235, 250)
Note over Tool,Persistent: Injection & prepare
Tool->>Persistent: Attach `Yummy()` trigger (p.yarpe_trigger)
Tool->>Persistent: Mark persistent changed
end
rect rgb(245, 235, 220)
Note over Tool: Serialize & compress
Tool->>Tool: Pickle persistent (protocol 2)
Tool->>Tool: zlib.compress(serialized)
end
rect rgb(220, 245, 230)
Note over Tool,Disk: Write to disk
Tool->>Disk: Overwrite `/saves/persistent` with compressed data
end
rect rgb(235, 220, 245)
Note over Disk,Game: Trigger on load (future)
Game->>Disk: Read `/saves/persistent`
Game->>Game: Decompress & unpickle
Game->>Game: Invoke `Yummy.__reduce__()` → execute embedded `SCRIPT`
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. 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
🧹 Nitpick comments (3)
payloads/force_persistent.py (3)
12-59: Label callback override may clobber any existing game callbackThe bootstrap script unconditionally assigns
renpy.config.label_callback = yarpe_label_callback, which will discard any label callback the host game (or other mods) may have installed. That’s fine if you don’t care about compatibility, but if you want to be less invasive you could capture and chain the previous callback, e.g. insideSCRIPT:old_cb = renpy.config.label_callback def yarpe_label_callback(label, abnormal): ylog("LABEL: %s (abnormal=%r)" % (label, abnormal)) if label == "splashscreen" and not ys.game_initialized: ys.game_initialized = True ylog("YARPE: Entered _call__enter_game_menu_0 → autoloading slot '1-1'.") try: renpy.load("1-1") except Exception as e: ylog("YARPE ERROR: renpy.load('1-1') failed: %r" % e) if old_cb is not None: try: old_cb(label, abnormal) except Exception as e: ylog("YARPE: original label_callback raised: %r" % e)This keeps your behavior while preserving any existing callback.
66-94: Unify syscall error typing and consider wiring guarantees forgetpid/killTwo minor points here:
getpidfailure raises a bareExceptionwhilekillraisesSocketError. For consistency (and to satisfy linters complaining about generic exceptions), consider using the same domain-specific exception in both branches, e.g.:- if pid < 0: - raise Exception( + if pid < 0: + raise SocketError( "getpid failed with return value %d, error %d\n%s" % ( pid, sc.syscalls.getpid.errno, sc.syscalls.getpid.get_error_string(), ) )
kill_gameassumessc.syscalls.getpidandsc.syscalls.killhave already been bound using the updatedSYSCALLentries. It’s worth double‑checking that your initialization path guarantees these call wrappers exist before this payload runs; otherwise, you could get anAttributeErrorhere instead of a clean error.
95-138: BroadExceptioncatches are acceptable here but can be tightened or made more debuggableGiven this is a one‑shot payload, catching
Exceptionaroundpickle.dumps,zlib.compress, and the file write to log and bail is reasonable. If you want to placate stricter linters and improve diagnostics:
- Narrow the exceptions to likely failures, e.g.
pickle.PicklingError,zlib.error, andOSError/IOErrorfor the file operations.- Or, keep
Exceptionbut log a traceback as well so you don’t lose context during debugging.Functionally it’s fine as is; this is more about tooling friendliness and future debugging ease.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
payloads/force_persistent.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
payloads/force_persistent.py (3)
src/utils/rp.py (1)
log(45-52)payloads/updater_for_up_to_2.x.x.py (1)
sc(16-34)renpy/python.py (1)
py_exec(1-2)
🪛 Ruff (0.14.4)
payloads/force_persistent.py
75-82: Create your own exception
(TRY002)
116-116: Do not catch blind exception: Exception
(BLE001)
122-122: Do not catch blind exception: Exception
(BLE001)
136-136: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (2)
payloads/force_persistent.py (2)
1-11: Imports and base constants look tight and purposefulAll imported symbols are used, and the single
PERSISTENT_PATHconstant keeps the file’s external contract minimal. No issues here.
62-64: Pickle-based bootstrap viaYummy.__reduce__is coherentUsing
__reduce__to returnrenpy.python.py_execwith the embeddedSCRIPTensures the payload runs on unpickle as intended, and the tuple shape matches whatpickleexpects. Just make surerenpy.python.py_execexists for all Ren’Py versions/platforms you target.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
payloads/force_persistent.py (1)
148-150: Exit prompt doesn't match immediate termination behavior.The log message on line 149 says
"Press X(or O) to exit the game.{w}", butkill_game()is called immediately on line 150, giving no opportunity for user action. This has already been flagged in a previous review.
🧹 Nitpick comments (3)
payloads/force_persistent.py (3)
68-69: Consider moving syscall definitions into a function.Modifying the global
SYSCALLdictionary at module level creates side effects when the module is imported. If these syscall numbers are already defined elsewhere or if multiple payloads define them, this could cause conflicts.Consider moving these definitions into
main()or checking if they already exist before adding them.
74-84: Consider creating custom exceptions for syscall errors.Both
getpidandkillsyscall errors raise genericExceptioninstances. Per the static analysis hint (Ruff TRY002), creating domain-specific exception classes would improve error handling and allow callers to catch specific failure modes.Based on coding guidelines
119-143: Broad exception catching is acceptable here but could be more specific.The try/except blocks catch broad
Exceptiontypes (flagged by Ruff BLE001 on lines 121, 127, 141). While this provides robust error handling for a payload module, consider catching more specific exceptions:
pickle.PicklingErrorfor line 121zlib.errorfor line 127IOErrororOSErrorfor line 141This would allow unexpected exceptions to propagate while still handling known failure modes.
Based on coding guidelines
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
payloads/force_persistent.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
payloads/force_persistent.py (3)
src/utils/rp.py (1)
log(45-52)payloads/updater_for_up_to_2.x.x.py (1)
sc(16-34)renpy/python.py (1)
py_exec(1-2)
🪛 Ruff (0.14.4)
payloads/force_persistent.py
77-84: Create your own exception
(TRY002)
121-121: Do not catch blind exception: Exception
(BLE001)
127-127: Do not catch blind exception: Exception
(BLE001)
141-141: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (1)
payloads/force_persistent.py (1)
64-66: LGTM - Pickle exploit implementation is correct.The
__reduce__method correctly returns a tuple that will execute the embeddedSCRIPTviarenpy.python.py_execwhen the persistent save is deserialized.
| SCRIPT = """ | ||
| import os | ||
| import sys | ||
| import traceback | ||
| # Add YARPE folder to Python path | ||
| sys.path.insert(0, "/saves/yarpe") | ||
| class YarpeState(object): | ||
| def __init__(self): | ||
| self.logs = [] | ||
| self.game_initialized = False | ||
| renpy.store.yarpe_state = YarpeState() | ||
| ys = renpy.store.yarpe_state | ||
| # --------------------------------------------------------- | ||
| # Logging | ||
| # --------------------------------------------------------- | ||
| def ylog(msg): | ||
| ys.logs.append(msg) | ||
| if len(ys.logs) > 400: | ||
| del ys.logs[0:100] | ||
| ylog("YARPE: Initialized from persistent bootstrap.") | ||
| # --------------------------------------------------------- | ||
| # Label Callback Logic | ||
| # --------------------------------------------------------- | ||
| def yarpe_label_callback(label, abnormal): | ||
| ylog("LABEL: %s (abnormal=%r)" % (label, abnormal)) | ||
| if label == "splashscreen" and not ys.game_initialized: | ||
| ys.game_initialized = True | ||
| ylog("YARPE: Entered _call__enter_game_menu_0 → autoloading slot '1-1'.") | ||
| try: | ||
| renpy.load("1-1") | ||
| except Exception as e: | ||
| ylog("YARPE ERROR: renpy.load('1-1') failed: %r" % e) | ||
| # Install callback | ||
| renpy.config.label_callback = yarpe_label_callback | ||
| ylog("YARPE: label_callback replaced with yarpe_label_callback.") | ||
| """ |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review hardcoded paths and save slot assumptions.
The SCRIPT contains two potential issues:
- Line 20: The absolute path
"/saves/yarpe"has the same root filesystem issue asPERSISTENT_PATH. - Line 53: The hardcoded save slot
"1-1"assumes this slot exists. If the player hasn't saved in that slot,renpy.load()will fail (though it's caught by the try/except).
Consider documenting these assumptions or making the save slot configurable.
🤖 Prompt for AI Agents
In payloads/force_persistent.py around lines 14 to 61, the script uses a
hardcoded absolute path "/saves/yarpe" and a fixed save slot "1-1"; change both
to be configurable and safe: replace the absolute path with a path derived from
Ren'Py's save directory or a configurable variable (e.g., read from renpy.config
or an environment variable) and ensure the folder is created if missing; make
the target save slot configurable (e.g., a module-level constant or config
value) and before calling renpy.load verify the slot exists or the save id is
valid (fall back to a safe default or skip autoload if no save present), and
keep the try/except but log the chosen path and slot for easier debugging.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mpereiraesaa maybe add a good little note to the user that they have to save the exploit to slot 1-1?
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| ret = u64_to_i64(sc.syscalls.kill(pid, SIGKILL)) | ||
| if ret < 0: | ||
| raise SocketError( | ||
| "kill failed with return value %d, error %d\n%s" | ||
| % ( | ||
| ret, | ||
| sc.syscalls.kill.errno, | ||
| sc.syscalls.kill.get_error_string(), | ||
| ) | ||
| ) |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect exception type for kill syscall error.
Raising SocketError on line 88 for a kill syscall failure is semantically incorrect. The kill syscall has nothing to do with sockets.
Create a generic syscall exception or reuse an existing one:
- ret = u64_to_i64(sc.syscalls.kill(pid, SIGKILL))
- if ret < 0:
- raise SocketError(
- "kill failed with return value %d, error %d\n%s"
- % (
- ret,
- sc.syscalls.kill.errno,
- sc.syscalls.kill.get_error_string(),
- )
- )
+ ret = u64_to_i64(sc.syscalls.kill(pid, SIGKILL))
+ if ret < 0:
+ raise Exception(
+ "kill failed with return value %d, error %d\n%s"
+ % (
+ ret,
+ sc.syscalls.kill.errno,
+ sc.syscalls.kill.get_error_string(),
+ )
+ )🤖 Prompt for AI Agents
In payloads/force_persistent.py around lines 86 to 95, the code raises
SocketError for a failed kill syscall which is semantically wrong; replace it
with a generic syscall-related exception (e.g., SyscallError) or reuse an
existing syscall exception type in the project, update imports so SyscallError
is available in this module (or define it here if it doesn't exist), and raise
that exception with the same formatted message and error details currently being
used.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix this, i have to fix this as well 😅
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Helloyunho Thanks for confirming! 👍 Looking forward to seeing the fix. Let me know if you need any help with it!
Summary by CodeRabbit
Note: These are internal technical changes that may affect save-file behavior or cause the app to exit briefly during update.