il2cpp_rs

Crates.ioil2cpp_rs
lib.rsil2cpp_rs
version0.1.3
created_at2025-11-08 22:05:18.734869+00
updated_at2025-11-11 18:50:13.893766+00
descriptionA library for interacting with il2cpp on Windows
homepagehttps://github.com/ElCapor/il2cpp_rs
repositoryhttps://github.com/ElCapor/il2cpp_rs
max_upload_size
id1923329
size137,321
ElCapor (ElCapor)

documentation

README

Crates.io Version

Note

This repo is currently in development and is not ready for production use.

Windows ONLY

I wrote it in a single day with 15 hours of work.

il2cpp_rs

A lightweight Rust library for discovering and navigating IL2CPP metadata at runtime. It provides a safe-ish Rust façade over the IL2CPP C API, and builds a cache of assemblies, classes, fields, and methods with a modern ownership model for convenient querying and printing.

Note: This repo targets Windows for now and requires using an injected DLL entry for example (DllMain) to attach to a running IL2CPP process (e.g., a Unity game).


Features

  • IL2CPP API bindings (il2cpp::il2cpp_sys) and ergonomic wrappers (il2cpp module)
  • Discovery of:
    • Assemblies → Classes → Fields → Methods
    • Method signatures: name, flags, parameters, return type
    • Field metadata: name, offset, static-ness, type
  • Modern ownership model for metadata graph:
    • Arc shared handles for nodes (Assembly, Class, Field, Method, Type)
    • Weak back-references (e.g., Field/MethodClass) to avoid cycles
    • Thread-safe collections via RwLock<Vec<...>> in Class
  • Minimal profiling utilities to time code paths (profile_scope!, profile_call!)

Architecture Overview

  • src/il2cpp/il2cpp_sys: raw FFI to IL2CPP exports (pointers, C-strings). All low-level Il2Cpp* are represented as *mut u8 handles.
  • src/il2cpp/mod.rs: safe-ish wrappers around the FFI that return Rust types (e.g., String, Vec<...>), and helper functions like:
    • get_domain, thread_attach/thread_detach
    • domain_get_assemblies, assembly_get_image
    • image_get_class_count, image_get_class
    • class_get_name/namespace/parent
    • class_get_fields, field_get_name/offset/type
    • class_get_methods, method_get_name/flags/params/return_type
  • src/il2cpp/classes: high-level Rust model types used in the cache
    • Class = Arc<ClassInner>
      • fields: RwLock<Vec<Field>>
      • methods: RwLock<Vec<Method>>
    • Field = Arc<FieldInner>
      • class: Weak<ClassInner> backref
    • Method = Arc<MethodInner>
      • class: Weak<ClassInner> backref, return_type: Type
    • Type = Arc<TypeInner> (cacheable handle with address, name, size)
  • src/il2cpp_cache.rs: metadata discovery and hydration into the high-level types
    • Cache::parse_assemblies(domain)
    • Cache::parse_class(&mut Assembly, image)
    • Cache::parse_fields(&Class) (populates fields)
    • Cache::parse_methods(&Class) (populates methods)

Ownership Model

  • Strong edges (Arc):
    • AssemblyVec<Class>
    • ClassRwLock<Vec<Field>>, RwLock<Vec<Method>>
  • Weak edges (Weak):
    • Field.class, Method.classWeak<ClassInner>
  • Benefits:
    • Avoid cycles (Class ↔ Field/Method)
    • Safe cloning of handles (cheap Arc clones)
    • Thread-safe reads and targeted writes (RwLock)

Profiling

The crate exposes a tiny profiling module for quick ad-hoc timing in dev builds.

  • Scope-based timing:
profile_scope!("Cache::new");
// code to profile...
  • Single expression timing with result preserved:
let cache = profile_call!("Cache::new", Cache::new(domain));

Printed output example:

Cache::new took 1.23ms

Implementation: see src/prof.rs (ScopeTimer) and macros exported at crate root.


Building

  • Install and build:
rustup toolchain install stable
cargo build
  • Standard dev build:
cargo build

Running / Example

Example usage of the library in a dll at https://github.com/ElCapor/il2cpp_rs-example-dll

Example: Discover and print classes

use il2cpp_rs::il2cpp;
use il2cpp_rs::il2cpp_cache::Cache;

fn entry_point() -> Result<(), String> {
    il2cpp::init("GameAssembly.dll")?;
    let domain = il2cpp::get_domain()?;
    il2cpp::thread_attach(domain)?;

    // the cache is the structure that contains all the assemblies, classes, fields, and methods
    let cache = Cache::new(domain)?;
    // Debug printing is available via Debug impls
    // println!("{:?}", cache);
    Ok(())
}

Safety Notes

  • The FFI layer manipulates raw pointers (*mut u8) from IL2CPP. Access patterns assume the underlying engine keeps these pointers valid while attached to the domain.
  • Do not send handles across threads unless you’ve attached those threads to the IL2CPP domain (thread_attach).
  • Avoid storing borrowed C-string pointers; convert to Rust String immediately (already handled by wrappers).
  • All Arc/Weak handles are Send/Sync only insofar as the contained data is. The raw pointer addresses are opaque and not dereferenced in safe code.

Contributing

  • Run cargo fmt and cargo clippy on changes
  • Keep FFI wrappers minimal and well-commented
  • Preserve the Arc/Weak ownership model and avoid re-introducing cycles

License

GNU General Public License v3.0. See LICENSE.MD for details.

Commit count: 0

cargo fmt