Skip to main content

il2cpp_bridge_rs/memory/info/
symbol.rs

1//! Symbol resolution and caching utilities
2
3use dashmap::DashMap;
4use once_cell::sync::Lazy;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8/// Errors that can occur during symbol resolution
9pub enum SymbolError {
10    /// The specified symbol was not found
11    #[error("Symbol not found: {0}")]
12    NotFound(String),
13    /// Failed to convert the symbol name to a CString
14    #[error("CString error")]
15    StringError,
16}
17
18static CACHE: Lazy<DashMap<String, usize>> = Lazy::new(DashMap::new);
19
20/// Resolves a symbol to its address using platform-specific lookup.
21///
22/// # Arguments
23/// * `symbol` - The name of the symbol to resolve (e.g., "MGCopyAnswer")
24///
25/// # Returns
26/// * `Result<usize, SymbolError>` - The address of the symbol or an error
27pub fn resolve_symbol(symbol: &str) -> Result<usize, SymbolError> {
28    if let Some(entry) = CACHE.get(symbol) {
29        return Ok(*entry);
30    }
31
32    let addr = platform::raw_resolve(symbol)?;
33    CACHE.insert(symbol.into(), addr);
34    Ok(addr)
35}
36
37/// Manually caches a symbol address
38///
39/// Use this if you have resolved a symbol via other means and want to store it for future lookups.
40///
41/// # Arguments
42/// * `s` - The symbol name
43/// * `a` - The symbol address
44pub fn cache_symbol(s: &str, a: usize) {
45    CACHE.insert(s.into(), a);
46}
47
48/// Clears the symbol cache
49pub fn clear_cache() {
50    CACHE.clear();
51}
52
53/// Promotes the named library's symbols to global visibility.
54///
55/// On Linux/Android, Unity may load `GameAssembly.so` with `RTLD_LOCAL`, making
56/// its symbols invisible to `dlsym(RTLD_DEFAULT, ...)`. This function re-opens
57/// the library with `RTLD_GLOBAL` to fix that.
58///
59/// Called automatically during initialization when a target image name is
60/// available, but can also be called manually before [`crate::init`] if needed.
61///
62/// On non-Linux platforms this is a no-op.
63pub fn promote_library_to_global(library_name: &str) {
64    platform::ensure_global_visibility(library_name);
65}
66
67// macOS / iOS: use dlsym with RTLD_DEFAULT to search all loaded dylibs.
68#[cfg(any(target_os = "macos", target_os = "ios"))]
69mod platform {
70    use super::SymbolError;
71    use std::ffi::CString;
72
73    pub fn raw_resolve(symbol: &str) -> Result<usize, SymbolError> {
74        let c_str = CString::new(symbol).map_err(|_| SymbolError::StringError)?;
75        unsafe {
76            let addr_ptr = libc::dlsym(libc::RTLD_DEFAULT, c_str.as_ptr());
77            if addr_ptr.is_null() {
78                Err(SymbolError::NotFound(symbol.into()))
79            } else {
80                Ok(addr_ptr as usize)
81            }
82        }
83    }
84
85    pub fn ensure_global_visibility(_library_name: &str) {}
86}
87
88// Linux / Android: use dlsym with RTLD_DEFAULT to search all loaded shared objects.
89#[cfg(any(target_os = "linux", target_os = "android"))]
90mod platform {
91    use super::SymbolError;
92    use std::ffi::{CStr, CString};
93    use std::sync::Once;
94
95    static PROMOTE_ONCE: Once = Once::new();
96
97    /// Promotes the library's symbols to global visibility so that
98    /// `dlsym(RTLD_DEFAULT, ...)` can find them.
99    ///
100    /// Unity on Linux may load `GameAssembly.so` with `RTLD_LOCAL`, confining
101    /// its symbols to a private namespace. Re-opening with `RTLD_GLOBAL`
102    /// makes them discoverable via `RTLD_DEFAULT`.
103    pub fn ensure_global_visibility(library_name: &str) {
104        PROMOTE_ONCE.call_once(|| {
105            let path = match crate::memory::image::get_image_path(library_name) {
106                Some(p) => p,
107                None => {
108                    crate::logger::warning(&format!(
109                        "Could not find loaded library matching '{}'; symbol promotion skipped",
110                        library_name
111                    ));
112                    return;
113                }
114            };
115
116            let c_path = match CString::new(path.as_str()) {
117                Ok(c) => c,
118                Err(_) => return,
119            };
120
121            unsafe {
122                let handle = libc::dlopen(
123                    c_path.as_ptr(),
124                    libc::RTLD_NOW | libc::RTLD_GLOBAL | libc::RTLD_NOLOAD,
125                );
126                if handle.is_null() {
127                    let err = libc::dlerror();
128                    let msg = if err.is_null() {
129                        "unknown error".to_string()
130                    } else {
131                        CStr::from_ptr(err).to_string_lossy().into_owned()
132                    };
133                    crate::logger::warning(&format!(
134                        "dlopen promotion failed for '{}': {}",
135                        path, msg
136                    ));
137                } else {
138                    crate::logger::info(&format!(
139                        "Promoted '{}' symbols to global visibility",
140                        path
141                    ));
142                }
143            }
144        });
145    }
146
147    pub fn raw_resolve(symbol: &str) -> Result<usize, SymbolError> {
148        let c_str = CString::new(symbol).map_err(|_| SymbolError::StringError)?;
149        unsafe {
150            let addr_ptr = libc::dlsym(libc::RTLD_DEFAULT, c_str.as_ptr());
151            if addr_ptr.is_null() {
152                Err(SymbolError::NotFound(symbol.into()))
153            } else {
154                Ok(addr_ptr as usize)
155            }
156        }
157    }
158}
159
160// Windows: use GetProcAddress with a null module handle to search the main executable.
161#[cfg(target_os = "windows")]
162mod platform {
163    use super::SymbolError;
164    use std::ffi::CString;
165    use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
166
167    pub fn raw_resolve(symbol: &str) -> Result<usize, SymbolError> {
168        let c_str = CString::new(symbol).map_err(|_| SymbolError::StringError)?;
169        unsafe {
170            // Null handle returns the calling process module, similar to RTLD_DEFAULT on Unix.
171            let handle = GetModuleHandleA(std::ptr::null());
172            if handle == 0 {
173                return Err(SymbolError::NotFound(symbol.into()));
174            }
175            let addr = GetProcAddress(handle, c_str.as_ptr() as *const u8);
176            match addr {
177                Some(f) => Ok(f as usize),
178                None => Err(SymbolError::NotFound(symbol.into())),
179            }
180        }
181    }
182
183    pub fn ensure_global_visibility(_library_name: &str) {}
184}
185
186// Unsupported platforms: always return an error.
187#[cfg(not(any(
188    target_os = "macos",
189    target_os = "ios",
190    target_os = "linux",
191    target_os = "android",
192    target_os = "windows",
193)))]
194mod platform {
195    use super::SymbolError;
196
197    pub fn raw_resolve(symbol: &str) -> Result<usize, SymbolError> {
198        Err(SymbolError::NotFound(format!(
199            "{} (unsupported platform)",
200            symbol
201        )))
202    }
203
204    pub fn ensure_global_visibility(_library_name: &str) {}
205}