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