架构
RenMori 由一套构建期工具与一个原生运行时核心组成。它以已构建完成的 Ren'Py 8.x(Python 3)发行版构建为输入,输出受保护的发行版构建,无需改动任何游戏源码。
原生启动器只是四个层级之一,其余三个层级(资源、脚本、引擎)才是保护的主体所在。
全局首要原则:性能优先
任何与运行时性能冲突的保护手段都会被舍弃或削弱,绝不反过来牺牲性能。具体而言:
- 默认不使用重型加壳或虚拟化保护(VMProtect、Themida 等):它们会拖慢启动,在 Proton/Wine(Steam Deck 与 Linux)下无法正常工作,容易触发杀毒软件误报,并干扰 macOS 公证
- 热路径上不进行任何反调试或自校验:完整性校验仅在启动时执行一次
- 只使用对称、硬件加速的加密算法(AES、AES-NI):解密以原生代码(Rust 或 C)实现,绝不使用 Python 循环
- 由多态承载保护:多态是一种零运行时开销的构建期变换,整体策略是“无法批量破解”,而非“在运行时设防”
- 验收线:受保护的构建在帧时间、场景切换以及 CG 与视频播放上与未受保护的构建无从区分;若某项手段无法满足这一要求,舍弃的是该手段,而非性能
四个层级
1. 资源(包括字体在内的每一个文件)
game/下的所有文件(图像、音频、视频、字体、.rpyc、tl/、JSON 等)均被封入一个自定义加密容器,完全取代 RPA 封包格式;每个文件拥有各自的密钥与完整性标签- 运行时由一个原生模块接管
renpy.loader的文件打开与索引入口,返回已解密的流式文件对象;字体(FreeType)、视频(ffmpeg)与音频均经由加载器读取,因此单一入口即可覆盖全部资源- 实现任务:审计每种资源类型的读取路径,确认其均经由
renpy.loader,并为任何绕过它的路径补充钩子
- 实现任务:审计每种资源类型的读取路径,确认其均经由
- 按偏移量进行流式随机访问解密,视频与音频从不整体解密到内存中;为此容器格式必须支持按偏移寻址
- 解密结果复用 Ren'Py 既有的缓存(纹理、音频),同一图像不会被重复解密
2. 脚本(源码混淆)
- 输出的不是标准
.rpyc,而是采用自定义 AST 序列化,其类名映射、字段顺序与结构布局逐构建随机化,使依赖伪造renpy模块、基于 pickle 的反编译器没有任何稳定的绑定目标 - Ren'Py 脚本中内嵌的 Python(
init python、$)在构建期编译并加密,仅在运行时为执行而解密,调试元数据(源文件名、行号)可选择剥除 - 对白与字符串以加密形式存储,仅在使用时解密
3. 引擎
renpy/引擎代码与 Python 标准库一并封包加密,并在启动时进行防篡改的完整性校验- 白名单加载:仅加载构建期签名的脚本与模块,放入游戏目录的游离
.rpy或.rpyc文件一律拒绝,从而阻断un.rpyc式的注入反编译器 - 发行版构建禁用开发者控制台、开发者模式以及导出数据的 CLI 子命令(compile、lint、dialogue export 等)
4. 原生启动器(外壳)
- 以分平台的原生启动器取代 Ren'Py 自带的启动器:它引导内嵌的 Python,从加密容器加载引擎,并在运行时派生密钥,磁盘上不留任何明文密钥
- 以 Rust 或 C 编写,同时编译为启动器与 CPython 扩展模块,使核心逻辑位于原生代码而非 Python 之中
逐构建多态(核心价值)
每次构建都会随机化容器格式参数、AST 编码映射与密钥派生路径,使通用解包器无从存在。这是一项构建期变换,不增加任何运行时开销。
开放核心拆分
划分准则:任何公开的部分,即使被完全理解,也不得使任何人能够写出一个对所有 RenMori 游戏通用的解包器。
开放部分(本仓库)
- CLI 骨架、配置解析、CI 集成以及 GitHub Actions 示例
renpy.loader钩子框架与运行时 SDK:即接管加载器并将解密后的数据流交还给引擎的管道;将其开源本身就是一项特性,开发者可借此确认其中不存在后门或回传行为,并自行调试兼容性问题- 基线容器格式 v1 规范:一种固定、公开、中等强度的格式,见 容器格式
- 标准加密原语的使用方式:AES 是公开标准,公开其用法不会带来任何损失
- 文档、示例与测试
开放部分可以单独使用,此时处于基线强度(一种固定、可预测的格式)。
闭源部分(预编译二进制插件,已确定)
- 多态 / 变异引擎:决定每次构建如何随机化容器布局、字段顺序与 AST 类映射;它仅在构建期运行,绝不分发给玩家,一旦泄露便会暴露整个变换空间,使攻击者能够编写通用求解器,因此必须保持闭源
- 密钥派生混淆:密钥最终仍可在客户端派生,但派生与隐藏密钥的方式正是抬高逆向成本的关键
- 原生反篡改与密钥恢复运行时:以不附源码的预编译二进制形式交付;它随游戏一同分发,因此最终仍可被逆向,闭源只是抬高门槛
交付方式: 开放的 CLI 在构建期调用闭源的预编译二进制。这一方式适合 CI、可离线运行,并契合免费分发。二者通过一套稳定且有文档的进程接口通信(基于 stdin/stdout 的 CLI 调用或本地 IPC:输入为构建产物路径与配置,输出为受保护的产物与构建清单),接口公开而实现不公开,每个平台对应一个二进制,详见 插件接口。
此外,运行时可选择只接受由官方闭源二进制签名的构建指纹,使自行编写的弱多态层无法冒充官方实现。
抗逆向:如实的模型
前提:一个分发给玩家、且必须在本地解密的二进制,不可能做到无法破解,这就是白盒问题。目标是抬高破解成本、使破解成果无法迁移,并通过轮换方案使旧的破解随时间失效。成功的标准是不存在通用解包器,且每款游戏都需要专门的专家级投入。完整论证见 威胁模型。
两件闭源产物面对不同的威胁模型:
- 构建期二进制(多态与密钥派生):仅在开发机或 CI 机器上运行,绝不分发给玩家,因此更易于保护
- 运行时核心(随每款发行游戏分发的解密与反篡改模块):运行在攻击者的机器上,且必须能够解密全部内容,因此完全处于白盒环境
运行时核心的加固措施,按收益排序(并受性能优先原则约束):
- 全部原生实现,绝不使用 Python:关键逻辑的任何
.py或.pyc都会直接暴露实现 - 多态使破解无法迁移(最重要):对一款游戏的完整逆向只能得到仅适用于该游戏、该版本的工具
- 密钥绝不是静态常量:密钥在运行时由多个来源(代码段自哈希、分散的常量、计算所得的值)派生,单次内存转储无法直接得到可用密钥;真正的白盒密码学难度很高,学术构造也屡有被攻破的先例,因此此处不作过度承诺
- 发行版构建剥除符号且不含调试信息
- 原生混淆:通过 obfuscator-LLVM(跨平台)实现控制流平坦化、不透明谓词与指令替换,仅在编译期进行,不引入任何运行时性能开销
- 自完整性与解密绑定:若二进制或引擎被修补,密钥派生会得到无效结果而非明确的错误,使补丁更难排查(仅在启动时进行)
- 反调试:最小化且默认关闭,因为它是一场军备竞赛,误报会触发杀毒软件,且会消耗性能
- 惰性分块解密,用后立即释放:内存中绝不完整保留一张 CG 或一段视频的明文,这会抬高内存提取的成本,但无法阻止内存提取
兼容性与性能结论(已由性能优先原则确定):原生代码、轻量的 obfuscator-LLVM、符号剥除与多态。重型商业加壳仅作为 Windows 专属、默认关闭的选项,且只有在实测确认对性能无可感知影响时才会启用。
平台集成
Steam
- 允许 Steam 叠加层(Overlay)注入:叠加层会注入游戏进程,激进的反篡改或反注入措施会将其拦截并破坏商店集成;性能优先原则已使反篡改保持最小化,并明确放行该注入
- 不要与 Steam DRM 双重包装:Steam DRM 会包装并改写可执行文件,可能与启动时自校验冲突或触发自校验,因此应进行兼容性测试,或建议开发者不使用 Steam DRM,其本身防护也较弱
- 成就与云存档经由 Ren'Py 的 Python API 实现,该 API 未被改动;
libsteam_api与steam_appid.txt仍可正常加载;加密容器为静态文件,因此 Steam 的文件校验与更新均正常工作
发布流程见 Steam 发行指南。
macOS 签名与公证(假定开发者不进行公证)
- 设计约束:只解密数据,绝不生成或执行解密后的代码。因此无需 hardened runtime 权限,保护措施也不会迫使开发者购买付费签名;未签名的发行版构建与任何未签名的 Ren'Py macOS 游戏行为一致(通过 Gatekeeper 右键打开)
- Apple Silicon 至少需要 ad-hoc 签名才能运行(
codesign -s -,免费且无需开发者账号):构建工具默认对其产出的启动器与 dylib 进行 ad-hoc 签名 - 若开发者持有 Developer ID 并希望进行公证,这些二进制仍可重新签名且兼容 hardened runtime,并提供文档化的流程
许可
- 全部内容(框架与工具、闭源插件,以及随游戏分发的运行时)均采用 RenMori License(LICENSE,中文版 LICENSE.zh-CN.md):可免费用于保护任何 Ren'Py 游戏,包括商业作品;运行时可以内嵌于发行游戏的形式免费再分发,开发者因此可以合法销售受保护的游戏
- 不允许的行为:销售 RenMori 本身或其修改版,或以其运营付费加密服务;该许可为源码可得(source-available)许可而非 OSI 开源许可,因为禁止转售的条款无法以宽松许可表达
- 贡献代码须签署 CLA,使贡献的代码能够在统一的许可下同时用于框架与闭源插件
- 第三方声明见 NOTICE:ffmpeg(LGPL/GPL)是主要的合规风险点,此外还涉及 AES 库与 Ren'Py 自身的许可
- 安全免责声明:声明任何保护都并非不可破解,并限定游戏被破解时的责任范围,见 免责声明
- 出口与加密声明:含强加密的软件可能受出口管制约束
- 隐私声明:不收集任何遥测数据(或如实列出所收集的内容),以消除对加载器钩子回传数据的疑虑
里程碑
- M0:本设计文档
- M1:资源层概念验证(Windows):加密容器与加载器接管;图像、音频、视频与字体均可加载;
unrpa、rpatool、RenPack与RenpyEx全部失效;在 CG 与视频密集的样例上,受保护构建与未受保护构建的性能基准对比通过,见 M1 成果 - M2:脚本层与引擎层:自定义 AST 格式、签名白名单加载、发行版锁定以及拒绝注入的脚本,见 脚本层与引擎锁定
- M3:单条命令的 CLI 与 CI 集成:输入标准 Ren'Py 构建产物,支持配置文件、退出码、构建清单与 GitHub Actions 示例,见 CI 集成
- M4:逐构建多态;macOS(自动 ad-hoc 签名、可选公证)与 Linux/Steam Deck 启动器;验证 Steam 叠加层、Steamworks 与 Proton/Steam Deck 兼容性,见 M4
验证标准
- 功能:受保护的样例游戏能在全部三个平台上运行并完整通关,所有资源与全部剧情内容均正确
- 保护:
unrpa、rpatool、RenPack、RenpyEx与unrpyc(包括--try-harder与un.rpyc注入)全部失效,且任何时刻磁盘上都不存在明文文件,字体同样如此 - 性能:CG 与视频密集的样例相对于未受保护的构建达到验收线
- 可用性与 CI:在干净的 CI runner 上一条命令即可完成,退出码正确,示例工作流运行通过