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