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::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                match api::load(|symbol| match resolve_symbol(symbol) {
91                    Ok(addr) => addr as *mut c_void,
92                    Err(e) => {
93                        logger::error(&format!("{}", e));
94                        std::ptr::null_mut()
95                    }
96                }) {
97                    Ok(_) => {}
98                    Err(missing) => {
99                        logger::error(&format!(
100                            "Failed to load IL2CPP API symbols: {}",
101                            missing.join(", ")
102                        ));
103                    }
104                }
105
106                logger::info("IL2CPP API loaded, waiting for cache initialization...");
107
108                let _thread = Thread::attach(true);
109
110                let mut cache_ready = false;
111                let mut asm_count = 0usize;
112                let mut cls_count = 0usize;
113
114                for attempt in 1..=CACHE_INIT_MAX_ATTEMPTS {
115                    cache::clear();
116
117                    match unsafe { cache::load_all_assemblies() } {
118                        Ok(a) => {
119                            asm_count = a;
120                            match cache::hydrate_all_classes() {
121                                Ok(c) => {
122                                    cls_count = c;
123                                    logger::info(&format!(
124                                        "Cache initialized: {} assemblies loaded, {} classes hydrated",
125                                        a, c
126                                    ));
127                                    cache_ready = true;
128                                    break;
129                                }
130                                Err(e) => {
131                                    logger::error(&format!(
132                                        "Cache init failed during class hydration: {}",
133                                        e
134                                    ));
135                                }
136                            }
137                        }
138                        Err(e) => {
139                            logger::error(&format!("Cache init failed: {}", e));
140                        }
141                    }
142
143                    if attempt < CACHE_INIT_MAX_ATTEMPTS {
144                        logger::info(&format!(
145                            "Cache initialization attempt {}/{} failed. Retrying in {}s...",
146                            attempt,
147                            CACHE_INIT_MAX_ATTEMPTS,
148                            CACHE_INIT_RETRY_DELAY.as_secs()
149                        ));
150                        thread::sleep(CACHE_INIT_RETRY_DELAY);
151                    }
152                }
153
154                let _ = (asm_count, cls_count);
155
156                if cache_ready {
157                    logger::info("Cache ready, starting callback...");
158
159                    let callbacks = {
160                        let mut guard = STATE.lock().unwrap();
161                        let old = std::mem::replace(&mut *guard, State::Done);
162                        match old {
163                            State::Running(cbs) => cbs,
164                            _ => vec![],
165                        }
166                    };
167
168                    std::thread::spawn(move || {
169                        for cb in callbacks {
170                            cb();
171                        }
172                    });
173                } else {
174                    logger::error("Cache initialization failed after all retry attempts.");
175
176                    let mut guard = STATE.lock().unwrap();
177                    *guard = State::Idle;
178                }
179            });
180        }
181    }
182}