Skip to content

架构

启动器工作原理

PVZRH 启动器使用 Pine(Java 方法 Hook 框架)在运行时将 BepInEx 注入游戏进程。无需原生引导库——所有 Hook 均通过 Kotlin/Java 安装。

注入流程

  1. 包上下文 — 通过 createPackageContext() 获取 PVZRH 的 ClassLoader 和 DEX 访问权限。
  2. Pine Hook — 安装双向 ClassLoader、Instrumentation、PackageManager、原生库加载和 UnityPlayer 的 Hook。
  3. Activity 重定向Instrumentation.execStartActivity Hook 将游戏 Activity 重定向到清单中注册的 StubActivity
  4. Activity 恢复Instrumentation.newActivity 恢复真实的游戏 Activity 类和原始 Intent。
  5. Context 包装Activity.attachBaseContext Hook 用三路 CustomContextWrapper 包装 Context:
    • 游戏资源(Assets、Resources、Theme)→ PVZRH 包上下文
    • 文件/存储(getFilesDir、SharedPreferences)→ 启动器 Application
    • 窗口服务(getDisplay、getSystemService)→ 原始 Activity 基础 Context
  6. 原生库重定向ClassLoader.findLibrary() Hook 路由 .so 加载:游戏库来自游戏 APK,Fusion 库来自启动器,.NET/il2cpp/unity 库来自数据目录。
  7. UnityPlayer 设置 — 构造函数 Hook 设置 activity 字段并显示注入覆盖层。
  8. 完整性检查绕过UnityPlayer.kill() Hook 阻止首次调用 5 秒以通过 Unity 的完整性检查。

Context 包装器

CustomContextWrapper 是允许游戏和启动器在同一进程中并存的核心机制:

方法路由至用途
getAssets()getResources()getTheme()游戏包上下文游戏加载自己的资源
getFilesDir()getCacheDir()getSharedPreferences()启动器 Application启动器存储与游戏隔离
getSystemService()getDisplay()原始 Activity 上下文窗口服务正常工作
getApplicationContext()启动器 Application应用级单例留在启动器中

原生库加载

ClassLoader.findLibrary() Hook 使用三路重定向表:

类别来源示例
Fusion 库启动器 APKlibmain.solibfusion.so
数据库数据目录libil2cpp.solibunity.so
游戏库游戏 APK所有其他 .so 文件

ClassLoader Hook

双向 ClassLoader Hook 实现跨类加载器引用:

  • 游戏类可以引用 BepInEx 模组类型
  • BepInEx 模组可以引用游戏类型
  • 遇到 ClassNotFoundException 时回退到另一个加载器

存档数据访问

启动器根据 Android 版本使用不同方式访问游戏存档数据:

Android 版本API 等级方式备注
Android 10 及以下≤29直接文件访问无需特殊权限
Android 1130SAF(存储访问框架)需要用户通过系统选择器授予目录访问权限
Android 12+≥31Shizuku需要安装并授权 Shizuku 应用

为什么需要 Shizuku? Android 12+ 限制了 SAF 持久访问 Android/data 目录的能力。Shizuku 提供 shell 级别(uid 2000)的访问权限,可在所有 Android 版本上可靠工作,不受 SAF 限制。

备份位置:

text
/storage/emulated/0/PVZRH_Launcher/<package>/saves_backup/
├── G2L/    # 游戏 → 启动器备份
└── L2G/    # 启动器 → 游戏恢复

PVZRH Launcher