Full-data encryption
Scripts, sprites, CGs, audio, video, fonts, translations and configuration are packed into an encrypted container and decrypted only inside the running engine, leaving no plaintext assets on disk
Released by MewBaka, protecting your visual novel without getting in the way
Scripts, sprites, CGs, audio, video, fonts, translations and configuration are packed into an encrypted container and decrypted only inside the running engine, leaving no plaintext assets on disk
Works directly on the distribution build or .zip archive produced by the Ren'Py launcher, with no changes to the project source or existing workflow
Every build is protected differently, so reverse-engineering results for one title or version do not carry over to other builds
Works out of the box; an optional renmori.toml controls chunk size, exclusion lists, locking, protection strength and key source
Emits machine-readable JSON results with explicit exit codes, and renmori verify serves as a pipeline gate that packs and verifies every distribution build automatically
Protecting any Ren'Py game is free, commercial titles included, and the runtime may be redistributed with the game at no cost
All figures below are taken from the milestone verification records and the container format specification in the RenMori repository, with the measurement or derivation conditions stated for each.
The baseline Python reference reader measured about 74 MB/s of in-engine sequential decryption on multi-megabyte assets, far above the few MB/s that real-time video and audio decoding require. The shipped native decryptor removes the per-chunk Python call overhead; its throughput has not been measured separately.
Schematic, not to scale
offset(i) = data_offset + i × (chunk_size + 16)Each chunk is encrypted independently with a 16-byte GCM authentication tag, giving a size overhead of 16 / 65,536 ≈ 0.024%, plus a 64-byte header and the encrypted index. The fixed chunk stride allows any position to be reached by seeking directly, and large files are decrypted as a stream without being loaded into memory in full.
game/archive.rpaPlaintextgame/images.rpaPlaintextgame/script.rpycPlaintextgame/script.rpyPlaintextgame/renmori.rmcEncrypted containergame/renmori.rpeRuntime loaderAfter protection, no plaintext archive, .rpyc or .rpy remains on disk; every asset exists only in encrypted form inside the container, and the runtime adds only a cache/ directory.
| Tool | Measured result |
|---|---|
unrpa | Rejects the container: version auto-detection fails because the file is not an RPA archive |
unrpyc | Nothing to decompile: no .rpyc or .rpy exists on disk |
PNGNot foundRENPY RPC2Not foundOggSNot foundRIFFNot foundOTTONot foundzipNot foundSignature carving over a gigabyte-scale container found no multi-byte file signatures, only the three-byte coincidental matches expected by chance, none of them recoverable; the container is indistinguishable from random data.
| Asset type | Delivery path | Result |
|---|---|---|
| Images: sprites, CGs (PNG/JPG) | Buffered | Byte-identical |
| Audio (Opus/OGG) | Buffered | Byte-identical |
| Scripts (.rpyc) | Buffered | Byte-identical |
| Translations (tl/<language>/) | Buffered | Byte-identical |
| Fonts, small (TTF) | Buffered | Byte-identical |
| Fonts, large multi-MB (TTF) | Streamed | Byte-identical |
| Video, large (WebM/OGV/AVI) | Streamed | Byte-identical |
Every asset type was decrypted through the real in-engine renpy.loader and compared against the SHA-256 of the original file.