renmori protect
renmori protect converts a Ren'Py distribution build into a protected distribution build: the assets and compiled scripts under game/ are sealed into the encrypted container game/renmori.rmc, compiled scripts additionally undergo the script-layer transformation, the plaintext originals are deleted, and the loader extension game/renmori.rpe and the build manifest renmori-manifest.json are written. When the game starts, Ren'Py loads this extension automatically, and the extension decrypts and supplies resources inside the engine.
Syntax
Input
Build directory
The input directory must directly contain a game/ subdirectory; otherwise the command fails with exit code 2. If renmori.rmc already exists in game/, the build is already protected, and the command likewise fails with exit code 2 without protecting it a second time. Always use a fresh distribution build, or the backup created before protection, as the input.
Release archive (.zip)
When the input is a valid .zip file, protect first extracts it and then protects the extracted result:
- Determine the output directory: the directory given by
-oif specified; otherwise a directory next to the archive, named after the archive without its extension, for exampleMyGame-1.0-pc/forMyGame-1.0-pc.zip. This directory must not exist - Create a temporary directory in the parent of the output directory and extract into it. Before extraction, every archive member path is checked; any member that would be extracted outside the target directory (zip-slip) causes the command to fail
- Locate the build root: if the extraction root itself contains
game/, it is used directly; otherwise the command looks among the top-level subdirectories for exactly one directory that containsgame/, which corresponds to the common case of the launcher wrapping the build in a folder of the same name. If none or more than one is found, the command fails - Move the build root to the output directory, then process that directory in the same way as in-place protection, and clean up the temporary directory
The original archive always remains unchanged and serves as the original, so no _bak backup is created when the input is an archive.
Output modes
In-place protection deletes the plaintext originals under game/. If <dir>_bak already exists, the command fails with exit code 2 before making any change; delete the old backup, use -o instead, or use --no-backup once you have confirmed that no backup is needed. The backup directory contains all plaintext resources; never upload it together with the release.
Execution flow
The command runs in the following order, and every foreseeable conflict is checked before any file is modified.
- Resolve input: a directory is used directly; a
.zipis first extracted and the build root located - Preconditions: confirm that
game/exists and is not yet protected - Merge configuration: read
renmori.toml(if present) and merge it with the command-line options; see Configuration File renmori.toml - Determine protection strength: when no strength is specified, full protection is used and the closed plugin is required; when
baselineis specified, the open baseline format is used and the plugin is not downloaded; whenautois specified, full protection is used if the plugin is available, and baseline otherwise. For how the plugin is obtained, see Protection Strength and the Closed Plugin - Prepare keys (baseline only): read the container key and script key from the environment variables named in
[keys]; when none is named or the variable is not set, a random key is generated for this build - Pre-flight the output location: check whether the
-odirectory or<dir>_bakalready exists, and count the files and scripts to be packed; fail if there are no files to pack - Print summary and confirm: print the input, mode, file count, script count, lockdown state and key sources to stderr; in an interactive terminal, ask for confirmation
[y/N]; any answer other thanyoryescancels, in which case{"status": "aborted"}is printed and the command exits with code 2 - Copy or back up: in
-omode, copy the entire build to the output directory; in in-place mode, first copy to<dir>_bak(skipped with--no-backup) - Pack and encrypt: write the files to be packed one by one into
game/renmori.rmc;.rpaarchives are unpacked and their entries written individually, and compiled scripts are written after the script-layer transformation - Delete plaintext: delete the packed originals and the stripped
.rpysources, and remove subdirectories that become empty as a result (skipped with--keep-plaintext) - Write the loader extension: generate
game/renmori.rpe - Write the build manifest: generate
renmori-manifest.jsonin the build root, and print the result JSON to stdout
When the closed plugin is used, step 5 is not performed and the plugin derives the keys itself; steps 8 to 11 are performed by the plugin, and the build manifest is still written by renmori. In in-place mode, renmori first moves the original build to the backup directory (with --no-backup, to a temporary directory that is deleted on success), and the plugin then reads from that directory and writes an entirely new build; if the plugin fails, the original build is moved back into place, and in -o mode any incomplete output is deleted.
File handling rules
protect walks all files under game/ and classifies them according to the following rules. The table describes the baseline protection flow; under full protection the plugin performs the packing, and its file selection likewise follows the exclude and keep_on_disk settings.
Entry names in the container use Ren'Py's game-relative path form: forward-slash separated, with no leading slash, for example images/bg room.png. At runtime the container has the highest priority in the loader chain, so even if a plaintext file of the same name remains on disk, the engine always reads the version in the container.
Do not use exclude to exclude script files such as .rpy, .rpyc, .rpym and .rpymc. The runtime loader refuses to open script-type files located on disk in game/, in order to block decompiler injection, so excluded scripts cannot be loaded. exclude should only be used for files read by programs other than the engine.
Options
Output and backup
-o, --output DIR
Writes the protected build to DIR, leaving the original build unchanged. DIR must not exist; otherwise the command fails with exit code 2. If its parent directory does not exist, it is created automatically. This mode does not create a backup. When the input is a .zip, DIR is the target directory for extraction and protection.
--no-backup
During in-place protection, does not create the <dir>_bak backup and rewrites the original build directly. This operation is irreversible and is recommended only for disposable build directories in CI. This option has no effect when -o is specified or the input is a .zip.
-y, --yes
Skips the confirmation prompt before protection, as well as the confirmation prompt when the closed plugin is downloaded for the first time. The prompts are likewise skipped when stdin is not a terminal or --quiet is specified.
Configuration
-c, --config FILE
Reads the configuration from FILE. If the file does not exist, the command fails with exit code 2. When not specified, <game-dir>/renmori.toml and renmori.toml in the current working directory are searched in that order, and the first one found is used; if neither exists, the default configuration is used. For all configuration keys, see Configuration File renmori.toml.
Protection strength and plugin
--strength S
Sets the protection strength; corresponds to the configuration key strength.
For the difference in security between the two, see Protection Strength and the Closed Plugin.
--plugin PATH
Uses the closed plugin at PATH and no longer searches the cache or downloads automatically. Equivalent to setting RENMORI_PLUGIN; the command-line option takes precedence. If the path does not exist, a notice is printed and the option is ignored, and the search continues with the cache or automatic download. PATH may be an executable or a .py script run by the current Python interpreter.
--platforms LIST
Applies to the plugin only. A comma-separated list of target platforms, for example win-x64,linux-x64,mac-universal; corresponds to the configuration key platforms. When not specified, the plugin detects them automatically from the lib/py3-* directories in the build.
--launcher
Applies to the plugin only. Binds the keys to the native launcher and ships it with the build, so that the keys are reconstructed only when the game is started through that launcher. Corresponds to the configuration key launcher.
Encryption parameters
--chunk-size N
The container chunk size in bytes; corresponds to the configuration key chunk_size, default 65536. Each chunk is encrypted independently and carries a 16-byte authentication tag. Larger chunks favor large CGs and videos, while smaller chunks reduce the overhead for many small files. This option determines the chunk size of the baseline container; under full protection the chunking parameters are determined by the plugin per build.
--exclude GLOB
Keeps files matching GLOB on disk, unencrypted; may be specified multiple times. Patterns given on the command line are appended after exclude and keep_on_disk from the configuration file and do not replace them.
Matching rules:
- Patterns are matched against game-relative paths separated by forward slashes, for example
thirdparty/*matches files undergame/thirdparty/ - Python
fnmatchsemantics are used:*matches any characters and can cross directory separators,?matches a single character, and[seq]matches a character set - Matching is case-insensitive on Windows and case-sensitive on macOS and Linux
- Quote each pattern to prevent premature expansion by the shell, for example
--exclude "thirdparty/*"
Runtime behavior
--no-lockdown
Keeps developer features in the protected build. Lockdown, which is on by default, does the following at runtime:
- Turns off
config.developerandconfig.console, making developer mode and the developer console unavailable - Refuses to run the Ren'Py command-line subcommands that can export scripts and text data:
compile,dialogue,translate,add_from,merge_strings
Corresponds to the configuration key lockdown; release builds should keep lockdown on.
--keep-plaintext
Does not delete the plaintext originals after packing, nor the stripped .rpy sources; for troubleshooting only. Corresponds to the configuration key keep_plaintext. This option applies to the baseline protection flow.
A build produced with --keep-plaintext provides no protection whatsoever, and renmori verify also reports errors because of the plaintext leftovers. Never release such a build.
Output control
--no-hints
Does not print the "package and release" guidance after successful protection. By default the guidance covers: verifying the build, compressing the entire directory for itch.io or direct download, uploading the directory contents through SteamPipe, the requirement that renmori.rmc and renmori.rpe be distributed together, and not running a Ren'Py launcher build on the directory again.
Global options
--lang, --no-color and --quiet also apply; see CLI Overview.
Generated files
At runtime, Ren'Py also creates a cache/ directory under game/; this is normal engine behavior.
Examples
Protect a copy, keeping the original build:
Protect in place, with the original backed up to ./dist/MyGame-1.0-pc_bak:
Process the release archive produced by the launcher directly, writing the result to ./dist/MyGame-1.0-pc/:
Run non-interactively in CI, printing only JSON:
Exclude files read directly by a third-party program, and explicitly use the open baseline:
Use a locally available plugin and specify the target platforms:
The protected directory is the release itself. Do not run the Ren'Py launcher's Build Distributions on it again; the launcher rebuilds from the source project and discards all protection.