Skip to content

ElCapor/il2cpp_rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

About

Unity Resolve but in rust

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages