Skip to content

Latest commit

 

History

History
66 lines (43 loc) · 4.84 KB

File metadata and controls

66 lines (43 loc) · 4.84 KB

IL2CPP support

A large share of modern Unity games ship IL2CPP rather than Mono — Genshin, Hollow Knight: Silksong, Among Us, most post-2020 commercial Unity titles. UnityScope must cover both for "premier agentic pipeline for modders" to mean anything.

This is a planning doc. No IL2CPP code is shipped yet.

What changes vs Mono

Concern Mono IL2CPP
Loader BepInEx 5 (Mono) or BepInEx 6 BepInEx 6 (IL2CPP)
Plugin base class BaseUnityPlugin from BepInEx.dll BasePlugin from BepInEx.IL2CPP.dll
Game types Standard .NET reflection works Must go through Il2CppInterop proxy assemblies
Component lookup gameObject.GetComponent<Foo>() Same API — but Foo is the proxy, not the C++ type
Field/property reads Direct via reflection Through Il2CppInterop's generated wrappers
MonoBehaviour subclassing Native Requires [RegisterTypeInIl2Cpp] and Il2Cpp object lifecycle
Coroutines Native Wrapped via Il2CppSystem.Collections.IEnumerator
Sockets / threading / file I/O Host CLR — works Host CLR — works (transport layer untouched)

The transport layer (HTTP, named pipe, discovery file, auth token, request router, main-thread dispatcher, JSON writer) runs in the host CLR — none of it changes for IL2CPP. That's most of the code.

What does change: anything that touches game objects via reflection. Today that's SceneEndpoint and (when implemented) HierarchyWalker, ComponentSerializer, TypeHandlerRegistry, find selectors, invoke.

Architecture for dual support

Introduce an IInspector seam under UnityScope.Runtime/Inspection/. Two implementations:

  • MonoInspectorSystem.Reflection against game types directly.
  • Il2CppInspectorIl2CppInterop.Runtime calls; uses Il2CppType.Of<T>(), navigates proxy assemblies generated by Il2CppAssemblyUnhollower/Il2CppInterop.

Build outputs:

  • UnityScope.Runtime.Mono.dll — references BepInEx.dll, MonoInspector only.
  • UnityScope.Runtime.IL2CPP.dll — references BepInEx.IL2CPP.dll + Il2CppInterop.Runtime.dll, Il2CppInspector only.

Shared code (Transport/, Server/, Endpoints/, Json/) compiles into both via shared csproj <Compile Include> globs. One solution, two plugin DLLs, one wire format. Mod authors install whichever matches their target game's BepInEx variant.

Type-handler registry, IL2CPP-flavored

Today's plan is for games to ship adapter packages registering custom extractors (the way blackout-access knows about BlackoutText/BlackoutButton/BlackoutDropdown). For IL2CPP games, those adapters reference the game's proxy assemblies (the unhollowed DLLs in BepInEx/interop/), not the original. Same registration API, different reference targets. Adapter authors don't write IL2CPP-specific glue — they just [hint path] the unhollowed assembly instead of the original.

What we lose / what's harder

  • Method invocation (POST /invoke) — IL2CPP makes calling generic methods, methods with ref/out parameters, or methods on inner generic types painful. Spike will start with no-arg / simple-arg methods only.
  • Field setting on value types — IL2CPP value-type semantics through proxies are fiddly; skip until requested.
  • PerformanceIl2CppInterop calls have marshal overhead. /tree walks of huge hierarchies will be slower than Mono. Pagination already in the design absorbs most of this.
  • First-call latency — proxy assemblies are JIT'd / loaded on demand. A second snapshot is much faster than the first; document this so agents don't think the plugin is broken.

Phasing

Don't build IL2CPP until Mono spike works and proves the agent-loop is genuinely useful. Then:

  1. Extract IInspector interface; refactor MonoInspector out of SceneEndpoint etc. (Should be a small change because the codebase is small.)
  2. Add UnityScope.Runtime.IL2CPP.csproj referencing BepInEx 6 IL2CPP + Il2CppInterop.
  3. Implement Il2CppInspector for /scene and /tree first. Validate against a free IL2CPP game (e.g. Among Us on Steam, or a clean Unity demo build to IL2CPP).
  4. Iterate per endpoint.

Distribution

Ship both DLLs in one release, plus a one-paragraph "which one do I install" decision tree in the README. Detection heuristic (for the install script later): if <GameDir>\GameAssembly.dll exists → IL2CPP; if <GameDir>\<Name>_Data\Managed\Assembly-CSharp.dll exists → Mono.

Open questions to resolve before committing IL2CPP code

  1. BepInEx 6 IL2CPP is still in beta as of late 2025 — do we pin a specific bleeding-edge version, or wait for a stable tag?
  2. Do we want the same plugin GUID across Mono and IL2CPP builds (simpler discovery) or separate (clearer install)?
  3. Should the discovery file include runtime: "mono" | "il2cpp" so MCP tools can warn agents about feature differences (e.g. /invoke limitations on IL2CPP)?