cleat 0.1.0

Android IL2CPP game modding toolkit — safe Rust bindings for IL2CPP field access, method calls, and inline hooks
Documentation
//! # cleat — Android IL2CPP game modding toolkit
//!
//! Safe Rust bindings over IL2CPP. Read fields, call methods, install
//! inline hooks — the unsafe stays inside the library.
//!
//! ## Quick example
//!
//! ```ignore
//! use cleat::prelude::*;
//!
//! #[cleat::main]
//! fn my_mod() -> cleat::Result<()> {
//!     cleat::set_app_data("/data/data/com.example.game/files");
//!
//!     let player = Il2CppClass::find("Player")?;
//!     let max_hp: i32 = player.static_field_value("maxHealth")?;
//!
//!     Ok(())
//! }
//!
//! #[cleat::hook("Assembly-CSharp", "Player", "TakeDamage")]
//! fn god_mode(this: &Il2CppObject, damage: i32) -> cleat::Result<()> {
//!     // Nullify incoming damage.
//!     god_mode::original(this, 0);
//!     Ok(())
//! }
//! ```
//!
//! ## Features
//!
//! * Field access — read and write static/instance fields.
//! * Method calls — parameterised and generic methods supported.
//! * Inline hooks — `#[cleat::hook]` wraps ShadowHook, no manual trampoline code.
//! * Custom value types — `#[cleat::value_type]` derives FFI-safe read/write for
//!   your `#[repr(C)]` structs.
//!
//! ## Setup
//!
//! ```toml
//! [lib]
//! crate-type = ["cdylib"]
//!
//! [dependencies]
//! cleat = "0.1"
//! ```
//!
//! ```bash
//! cargo ndk -t arm64-v8a -o ./target/jniLibs build --release
//! ```
//!
//! ## Requirements
//!
//! * Rust nightly (edition 2024)
//! * Android NDK r26+
//! * cargo-ndk ≥ 3.0
//! * libshadowhook.so (runtime, bundled in the APK)

// Re-export proc macros so users only need `use cleat::prelude::*`.
pub use cleat_macros::*;

use il2cpp_bridge_rs as bridge;
use std::ffi::{CString, c_void};

// Internal modules.
mod class;
mod error;
mod init;
mod math;
mod method;
mod object;
mod types;
mod value_type;

#[cfg(target_os = "android")]
mod hook;

// Public re-exports.
pub mod prelude;

pub use class::Il2CppClass;
pub use error::{Error, Result};
pub use init::{app_data, init, set_app_data};
pub use math::{Quaternion, Vector2, Vector3, Vector4};
pub use method::MethodInfo;
pub use object::Il2CppObject;
pub use types::{Il2CppArray, Il2CppList, Il2CppString};
pub use value_type::{Args, Il2CppValueType};

/// Resolve an IL2CPP internal call (iCall) by its fully qualified name.
///
/// iCall names follow the format `"Assembly.Class::Method(args)"`.
/// Use when managed method lookup fails for `[NativeHeader]` / `extern`
/// methods (e.g. Unity AssetBundle loading APIs).
///
/// Returns the raw native function pointer.
pub fn resolve_icall(name: &str) -> Result<*mut std::ffi::c_void> {
    let c_name =
        CString::new(name).map_err(|_| Error::Bridge("iCall name contains null byte".into()))?;
    let ptr = unsafe { bridge::api::resolve_icall(c_name.as_ptr()) };
    if ptr.is_null() {
        return Err(Error::Bridge(format!("iCall not found: {name}")));
    }
    Ok(ptr)
}

macro_rules! icall_arity {
    (0, $name:ident,) => {
        /// # Safety
        ///
        /// `fn_ptr` must be a valid function pointer obtained from
        /// [`resolve_icall`] with a matching signature.
        pub unsafe fn $name(fn_ptr: *mut c_void) -> *mut c_void {
            let f: extern "C" fn() -> *mut c_void = unsafe { std::mem::transmute(fn_ptr) };
            f()
        }
    };
}

macro_rules! icall_arity_n {
    ($name:ident, $($pn:ident : $pt:ty),+) => {
        /// # Safety
        ///
        /// `fn_ptr` must be a valid function pointer obtained from
        /// [`resolve_icall`] with a matching signature.
        pub unsafe fn $name(fn_ptr: *mut c_void, $($pn: $pt),+) -> *mut c_void {
            let f: extern "C" fn($($pt),+) -> *mut c_void =
                unsafe { std::mem::transmute(fn_ptr) };
            f($($pn),+)
        }
    };
}

icall_arity!(0, invoke_icall_0,);
icall_arity_n!(invoke_icall_1, a: *mut c_void);
icall_arity_n!(invoke_icall_2, a: *mut c_void, b: *mut c_void);
icall_arity_n!(invoke_icall_3, a: *mut c_void, b: *mut c_void, c: *mut c_void);

#[cfg(target_os = "android")]
pub use hook::HookGuard;

/// Desktop stub — allows `cargo check` on non-Android platforms.
/// Hooks are no-ops and will fail gracefully at runtime.
#[cfg(not(target_os = "android"))]
pub struct HookGuard;

#[cfg(not(target_os = "android"))]
impl HookGuard {
    pub fn install(
        _target: *const std::ffi::c_void,
        _replace: *const std::ffi::c_void,
    ) -> crate::Result<Self> {
        log::warn!("HookGuard::install called on non-Android platform — skipping");
        Ok(Self)
    }
    pub fn trampoline(&self) -> *const std::ffi::c_void {
        std::ptr::null()
    }
    pub fn uninstall(self) -> crate::Result<()> {
        Ok(())
    }
}

#[cfg(not(target_os = "android"))]
unsafe impl Send for HookGuard {}
#[cfg(not(target_os = "android"))]
unsafe impl Sync for HookGuard {}