Skip to main content

il2cpp_bridge_rs/
init.rs

1//! Initialization of the IL2CPP runtime and metadata cache.
2//!
3//! [`init`] is the front door to the crate. It resolves IL2CPP exports, loads
4//! the function table, attaches the worker thread to the runtime, initializes
5//! the cache, hydrates metadata, and then runs queued callbacks.
6use crate::api::{self, cache, Thread};
7use crate::memory::symbol::resolve_symbol;
8use std::ffi::c_void;
9use std::sync::{Mutex, OnceLock};
10use std::thread;
11use std::time::Duration;
12
13#[cfg(dev_release)]
14use crate::logger;
15
16/// The target image name, set once during [`init()`](super::init).
17pub(crate) static TARGET_IMAGE_NAME: OnceLock<String> = OnceLock::new();
18
19type Callback = Box<dyn FnOnce() + Send + 'static>;
20
21enum State {
22    /// No initialization has started yet.
23    Idle,
24    /// Background thread is running; callbacks queued here fire when it finishes.
25    Running(Vec<Callback>),
26    /// Initialization completed successfully; new callers fire immediately.
27    Done,
28}
29
30static STATE: Mutex<State> = Mutex::new(State::Idle);
31const CACHE_INIT_MAX_ATTEMPTS: u8 = 5;
32const CACHE_INIT_RETRY_DELAY: Duration = Duration::from_secs(3);
33
34/// Initializes IL2CPP symbol loading and cache hydration.
35///
36/// This function must be called before using cache-backed helpers such as
37/// [`crate::api::cache::csharp`] or class/method lookups that depend on the
38/// hydrated metadata cache.
39///
40/// Behavior:
41///
42/// - First call: spawns the initialization worker and queues `on_complete`.
43/// - Calls while initialization is running: queue additional callbacks.
44/// - Calls after successful initialization: execute `on_complete` immediately
45///   on a newly spawned thread.
46/// - On failure: the internal state resets to idle so initialization can be
47///   attempted again.
48///
49/// The `target_image` should be the loaded module used to compute image base
50/// addresses and method RVA/VA information. Common values include
51/// `UnityFramework` on iOS and `GameAssembly` on many desktop Unity builds.
52///
53/// # Example
54///
55/// ```no_run
56/// use il2cpp_bridge_rs::{api, init};
57///
58/// init("GameAssembly", || {
59///     let asm = api::cache::csharp();
60///     println!("Ready: {}", asm.name);
61/// });
62/// ```
63///
64/// # Parameters
65///
66/// - `target_image`: name of the loaded image backing RVA/VA calculations
67/// - `on_complete`: callback executed after a successful initialization pass
68pub fn init<F>(target_image: &str, on_complete: F)
69where
70    F: FnOnce() + Send + 'static,
71{
72    TARGET_IMAGE_NAME
73        .set(target_image.to_owned())
74        .unwrap_or_else(|_| {
75            #[cfg(dev_release)]
76            logger::info("TARGET_IMAGE_NAME already set, ignoring new value.");
77        });
78    let mut guard = STATE.lock().unwrap();
79
80    match &mut *guard {
81        State::Done => {
82            drop(guard);
83            std::thread::spawn(on_complete);
84        }
85        State::Running(callbacks) => {
86            callbacks.push(Box::new(on_complete));
87        }
88        State::Idle => {
89            *guard = State::Running(vec![Box::new(on_complete)]);
90            drop(guard);
91
92            std::thread::spawn(move || {
93                if let Err(missing) = api::load(|symbol| match resolve_symbol(symbol) {
94                    Ok(addr) => addr as *mut c_void,
95                    Err(_e) => {
96                        #[cfg(dev_release)]
97                        logger::error(&format!("{}", _e));
98                        std::ptr::null_mut()
99                    }
100                }) {
101                    #[cfg(dev_release)]
102                    logger::error(&format!(
103                        "Failed to load IL2CPP API symbols: {}",
104                        missing.join(", ")
105                    ));
106
107                    let mut guard = STATE.lock().unwrap();
108                    *guard = State::Idle;
109                    return;
110                }
111
112                #[cfg(dev_release)]
113                logger::info("IL2CPP API loaded, waiting for runtime...");
114
115                #[cfg(dev_release)]
116                logger::info("IL2CPP runtime ready, installing early hook...");
117
118                let _thread = Thread::attach(true);
119
120                let mut cache_ready = false;
121                for attempt in 1..=CACHE_INIT_MAX_ATTEMPTS {
122                    if cache::init() {
123                        cache_ready = true;
124                        break;
125                    }
126
127                    if attempt < CACHE_INIT_MAX_ATTEMPTS {
128                        #[cfg(dev_release)]
129                        logger::info(&format!(
130                            "Cache initialization attempt {}/{} failed. Retrying in {}s...",
131                            attempt,
132                            CACHE_INIT_MAX_ATTEMPTS,
133                            CACHE_INIT_RETRY_DELAY.as_secs()
134                        ));
135                        thread::sleep(CACHE_INIT_RETRY_DELAY);
136                    }
137                }
138
139                if cache_ready {
140                    #[cfg(dev_release)]
141                    logger::info("Cache ready, starting hooks...");
142
143                    let callbacks = {
144                        let mut guard = STATE.lock().unwrap();
145                        let old = std::mem::replace(&mut *guard, State::Done);
146                        match old {
147                            State::Running(cbs) => cbs,
148                            _ => vec![],
149                        }
150                    };
151
152                    cache::ensure_hydrated();
153
154                    std::thread::spawn(move || {
155                        for cb in callbacks {
156                            cb();
157                        }
158                    });
159                } else {
160                    #[cfg(dev_release)]
161                    logger::error("Cache initialization failed after all retry attempts.");
162
163                    let mut guard = STATE.lock().unwrap();
164                    *guard = State::Idle;
165                }
166            });
167        }
168    }
169}