Skip to content

Architecture

How the Launcher Works

The PVZRH Launcher injects BepInEx into the game process at runtime using Pine (a Java method hooking framework). No native bootstrap library is required — all hooks are installed from Kotlin/Java.

Injection Flow

  1. Package ContextcreatePackageContext() for PVZRH obtains the game's class loader and DEX access.
  2. Pine Hooks — Installs hooks for ClassLoader (bidirectional), Instrumentation, PackageManager, native library loading, and UnityPlayer.
  3. Activity RedirectionInstrumentation.execStartActivity hook redirects the game activity to a manifest-registered StubActivity.
  4. Activity RestorationInstrumentation.newActivity restores the real game activity class and original Intent.
  5. Context WrappingActivity.attachBaseContext hook wraps the Context with a three-way CustomContextWrapper:
    • Game resources (Assets, Resources, Theme) → PVZRH package context
    • File/storage (getFilesDir, SharedPreferences) → launcher Application
    • Window services (getDisplay, getSystemService) → original Activity base Context
  6. Native Library RedirectionClassLoader.findLibrary() hook routes .so loading: game libs from game APK, Fusion libs from launcher, .NET/il2cpp/unity libs from data directory.
  7. UnityPlayer Setup — Constructor hook sets the activity field and shows injection overlay.
  8. Integrity Check BypassUnityPlayer.kill() hook blocks the first call for 5 seconds to survive Unity's integrity checks.

Context Wrapper

The CustomContextWrapper is the core mechanism that allows the game and launcher to coexist in the same process:

MethodRoutes ToPurpose
getAssets(), getResources(), getTheme()Game package contextGame loads its own resources
getFilesDir(), getCacheDir(), getSharedPreferences()Launcher ApplicationLauncher storage isolated from game
getSystemService(), getDisplay()Original Activity contextWindow services work normally
getApplicationContext()Launcher ApplicationApp-level singletons stay in launcher

Native Library Loading

The ClassLoader.findLibrary() hook uses a three-way redirection table:

CategorySourceExample
Fusion librariesLauncher APKlibmain.so, libfusion.so
Data librariesData directorylibil2cpp.so, libunity.so
Game librariesGame APKAll other .so files

ClassLoader Hook

A bidirectional ClassLoader hook enables cross-class-loader references:

  • Game classes can reference BepInEx mod types
  • BepInEx mods can reference game types
  • Falls back to the other loader on ClassNotFoundException

Save Data Access

The launcher uses different methods to access game save data depending on the Android version:

Android VersionAPI LevelMethodNotes
Android 10 and below≤29Direct file accessNo special permissions needed
Android 1130SAF (Storage Access Framework)Requires user to grant directory access via system picker
Android 12+≥31ShizukuRequires Shizuku app installed and authorized

Why Shizuku? Android 12+ restricted SAF's ability to persistently access the Android/data directory. Shizuku provides shell-level (uid 2000) access that works across all Android versions without the restrictions of SAF.

Backup location:

text
/storage/emulated/0/PVZRH_Launcher/<package>/saves_backup/
├── G2L/    # Game → Launcher backup
└── L2G/    # Launcher → Game restore

PVZRH Launcher