diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..65ae769 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ + +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/Cargo.toml b/Cargo.toml index 009b419..c95a728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,12 +4,17 @@ version = "0.1.0" edition = "2024" [lib] -crate-type = ["staticlib"] +crate-type = ["staticlib", "dylib"] + +[features] +default = [] +debug_c_bindings = [] [profile.release] opt-level = 3 lto = true + [dependencies] anyhow = "1.0.100" object = "0.37.3" diff --git a/README.md b/README.md index 8036f0f..c149597 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ This library mainly read data from original binary file instead of from memory, - Find symbol - Get module base address - Install trampoline to vtable with enough bytes padded with NOP (for safetyhook to hook empty virtual function) +- Find all CEmbeddedNetworkVar NetworkStateChanged function index +- Follow xref safely ## Compiling @@ -26,6 +28,20 @@ Run the following command to compile. cargo build --release ``` +### Debug Mode for C Bindings + +To enable debug output for all C binding errors, compile with the `debug_c_bindings` feature: +``` +cargo build --release --features debug_c_bindings +``` + +When enabled, all error returns in C bindings will print detailed debug information to stdout, including: +- Error code +- Error message +- File name and line number where the error occurred + +This is useful for debugging integration issues with the C API. + ## Linking and building On windows, if you are seeing linking error like `"__imp_NtReadFile"`, you also need to link `kernel32.dll` and `ntdll.dll`. diff --git a/s2binlib.h b/s2binlib.h index 4cc5156..68b112d 100644 --- a/s2binlib.h +++ b/s2binlib.h @@ -20,6 +20,12 @@ #ifndef S2BINLIB_H #define S2BINLIB_H +#ifdef _WIN32 +#define S2BINLIB_API +#else +#define S2BINLIB_API __attribute__((visibility("hidden"))) +#endif + #include #include @@ -47,7 +53,7 @@ extern "C" * // Handle error * } */ - int s2binlib_initialize(const char *game_path, const char *game_type); + S2BINLIB_API int s2binlib_initialize(const char *game_path, const char *game_type); /** * Initialize the global S2BinLib instance with a specific operating system @@ -68,7 +74,7 @@ extern "C" * // Handle error * } */ - int s2binlib_initialize_with_os(const char *game_path, const char *game_type, const char *os); + S2BINLIB_API int s2binlib_initialize_with_os(const char *game_path, const char *game_type, const char *os); /** * Scan for a pattern in the specified binary @@ -93,7 +99,7 @@ extern "C" * printf("Found at: %p\n", address); * } */ - int s2binlib_pattern_scan(const char *binary_name, const char *pattern, void **result); + S2BINLIB_API int s2binlib_pattern_scan(const char *binary_name, const char *pattern, void **result); /** * Pattern scan and return the virtual address @@ -122,7 +128,7 @@ extern "C" * printf("Pattern found at VA: %p\n", va); * } */ - int s2binlib_pattern_scan_va(const char *binary_name, const char *pattern, void **result); + S2BINLIB_API int s2binlib_pattern_scan_va(const char *binary_name, const char *pattern, void **result); /** * Callback function type for pattern_scan_all functions @@ -170,8 +176,8 @@ extern "C" * printf("Found %d matches\n", count); * } */ - int s2binlib_pattern_scan_all_va(const char *binary_name, const char *pattern, - s2binlib_pattern_scan_callback callback, void *user_data); + S2BINLIB_API int s2binlib_pattern_scan_all_va(const char *binary_name, const char *pattern, + s2binlib_pattern_scan_callback callback, void *user_data); /** * Find all occurrences of a pattern in a binary and return their memory addresses @@ -210,8 +216,8 @@ extern "C" * printf("Found %d matches\n", count); * } */ - int s2binlib_pattern_scan_all(const char *binary_name, const char *pattern, - s2binlib_pattern_scan_callback callback, void *user_data); + S2BINLIB_API int s2binlib_pattern_scan_all(const char *binary_name, const char *pattern, + s2binlib_pattern_scan_callback callback, void *user_data); /** * Find a vtable by class name in the specified binary @@ -236,7 +242,7 @@ extern "C" * printf("VTable at: %p\n", vtable_addr); * } */ - int s2binlib_find_vtable(const char *binary_name, const char *vtable_name, void **result); + S2BINLIB_API int s2binlib_find_vtable(const char *binary_name, const char *vtable_name, void **result); /** * Find a vtable by class name and return its virtual address @@ -261,7 +267,7 @@ extern "C" * printf("VTable VA: %p\n", vtable_va); * } */ - int s2binlib_find_vtable_va(const char *binary_name, const char *vtable_name, void **result); + S2BINLIB_API int s2binlib_find_vtable_va(const char *binary_name, const char *vtable_name, void **result); /** * Find a vtable by mangled name and return its virtual address @@ -296,7 +302,7 @@ extern "C" * // Linux mangled name example * int result = s2binlib_find_vtable_mangled_va("server", "11CBaseEntity", &vtable_va); */ - int s2binlib_find_vtable_mangled_va(const char *binary_name, const char *vtable_name, void **result); + S2BINLIB_API int s2binlib_find_vtable_mangled_va(const char *binary_name, const char *vtable_name, void **result); /** * Find a vtable by mangled name and return its runtime memory address @@ -326,7 +332,7 @@ extern "C" * printf("VTable at: %p\n", vtable_addr); * } */ - int s2binlib_find_vtable_mangled(const char *binary_name, const char *vtable_name, void **result); + S2BINLIB_API int s2binlib_find_vtable_mangled(const char *binary_name, const char *vtable_name, void **result); /** * Find a nested vtable (2 levels) by class names and return its virtual address @@ -358,7 +364,7 @@ extern "C" * printf("Nested VTable VA: %p\n", vtable_va); * } */ - int s2binlib_find_vtable_nested_2_va(const char *binary_name, const char *class1_name, const char *class2_name, void **result); + S2BINLIB_API int s2binlib_find_vtable_nested_2_va(const char *binary_name, const char *class1_name, const char *class2_name, void **result); /** * Find a nested vtable (2 levels) by class names and return its runtime memory address @@ -387,7 +393,7 @@ extern "C" * printf("Nested VTable at: %p\n", vtable_addr); * } */ - int s2binlib_find_vtable_nested_2(const char *binary_name, const char *class1_name, const char *class2_name, void **result); + S2BINLIB_API int s2binlib_find_vtable_nested_2(const char *binary_name, const char *class1_name, const char *class2_name, void **result); /** * Get the number of virtual functions in a vtable @@ -415,7 +421,7 @@ extern "C" * printf("VTable has %zu virtual functions\n", vfunc_count); * } */ - int s2binlib_get_vtable_vfunc_count(const char *binary_name, const char *vtable_name, size_t *result); + S2BINLIB_API int s2binlib_get_vtable_vfunc_count(const char *binary_name, const char *vtable_name, size_t *result); /** * Get the number of virtual functions in a vtable by virtual address @@ -451,7 +457,7 @@ extern "C" * printf("VTable has %zu virtual functions\n", vfunc_count); * } */ - int s2binlib_get_vtable_vfunc_count_by_va(const char *binary_name, uint64_t vtable_va, size_t *result); + S2BINLIB_API int s2binlib_get_vtable_vfunc_count_by_va(const char *binary_name, uint64_t vtable_va, size_t *result); /** * Find a symbol by name in the specified binary @@ -475,7 +481,7 @@ extern "C" * printf("Symbol at: %p\n", symbol_addr); * } */ - int s2binlib_find_symbol(const char *binary_name, const char *symbol_name, void **result); + S2BINLIB_API int s2binlib_find_symbol(const char *binary_name, const char *symbol_name, void **result); /** * Find a symbol by name and return its virtual address @@ -499,7 +505,7 @@ extern "C" * printf("Symbol VA: %p\n", symbol_va); * } */ - int s2binlib_find_symbol_va(const char *binary_name, const char *symbol_name, void **result); + S2BINLIB_API int s2binlib_find_symbol_va(const char *binary_name, const char *symbol_name, void **result); /** * Manually set the base address for a module from a pointer @@ -521,7 +527,7 @@ extern "C" * printf("Server base address set successfully\n"); * } */ - int s2binlib_set_module_base_from_pointer(const char *binary_name, void *pointer); + S2BINLIB_API int s2binlib_set_module_base_from_pointer(const char *binary_name, void *pointer); /** * Clear manually set base address for a module @@ -541,7 +547,31 @@ extern "C" * printf("Server base address cleared\n"); * } */ - int s2binlib_clear_module_base_address(const char *binary_name); + S2BINLIB_API int s2binlib_clear_module_base_address(const char *binary_name); + + /** + * Set a custom binary path for a specific binary and operating system + * + * This allows overriding the default binary path resolution for a specific binary. + * You can specify different paths for Windows and Linux builds. + * + * @param binary_name Name of the binary (e.g., "server", "client") (null-terminated C string) + * @param path The custom file path to the binary (null-terminated C string) + * @param os Operating system identifier ("windows" or "linux") (null-terminated C string) + * + * @return 0 on success + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -4 if unsupported OS specified + * -5 if internal error + * + * @example + * int result = s2binlib_set_custom_binary_path("server", "/custom/path/server.dll", "windows"); + * if (result == 0) { + * printf("Custom binary path set successfully\n"); + * } + */ + S2BINLIB_API int s2binlib_set_custom_binary_path(const char *binary_name, const char *path, const char *os); /** * Get the module base address @@ -566,7 +596,7 @@ extern "C" * printf("Module base: %p\n", base_addr); * } */ - int s2binlib_get_module_base_address(const char *binary_name, void **result); + S2BINLIB_API int s2binlib_get_module_base_address(const char *binary_name, void **result); /** * Check if a binary is already loaded @@ -585,7 +615,7 @@ extern "C" * printf("Server is loaded\n"); * } */ - int s2binlib_is_binary_loaded(const char *binary_name); + S2BINLIB_API int s2binlib_is_binary_loaded(const char *binary_name); /** * Load a binary into memory @@ -606,7 +636,7 @@ extern "C" * printf("Server loaded successfully\n"); * } */ - int s2binlib_load_binary(const char *binary_name); + S2BINLIB_API int s2binlib_load_binary(const char *binary_name); /** * Get the full path to a binary file @@ -630,7 +660,7 @@ extern "C" * printf("Binary path: %s\n", path); * } */ - int s2binlib_get_binary_path(const char *binary_name, char *buffer, size_t buffer_size); + S2BINLIB_API int s2binlib_get_binary_path(const char *binary_name, char *buffer, size_t buffer_size); /** * Find an exported symbol by name and return its virtual address @@ -655,7 +685,7 @@ extern "C" * printf("Export VA: %p\n", export_va); * } */ - int s2binlib_find_export_va(const char *binary_name, const char *export_name, void **result); + S2BINLIB_API int s2binlib_find_export_va(const char *binary_name, const char *export_name, void **result); /** * Find an exported symbol by name and return its runtime memory address @@ -680,7 +710,7 @@ extern "C" * printf("Export at: %p\n", export_addr); * } */ - int s2binlib_find_export(const char *binary_name, const char *export_name, void **result); + S2BINLIB_API int s2binlib_find_export(const char *binary_name, const char *export_name, void **result); /** * Read bytes from binary at a file offset @@ -705,7 +735,7 @@ extern "C" * // Use buffer * } */ - int s2binlib_read_by_file_offset(const char *binary_name, uint64_t file_offset, uint8_t *buffer, size_t buffer_size); + S2BINLIB_API int s2binlib_read_by_file_offset(const char *binary_name, uint64_t file_offset, uint8_t *buffer, size_t buffer_size); /** * Read bytes from binary at a virtual address @@ -730,7 +760,7 @@ extern "C" * // Use buffer * } */ - int s2binlib_read_by_va(const char *binary_name, uint64_t va, uint8_t *buffer, size_t buffer_size); + S2BINLIB_API int s2binlib_read_by_va(const char *binary_name, uint64_t va, uint8_t *buffer, size_t buffer_size); /** * Read bytes from binary at a runtime memory address @@ -755,7 +785,7 @@ extern "C" * // Use buffer * } */ - int s2binlib_read_by_mem_address(const char *binary_name, uint64_t mem_address, uint8_t *buffer, size_t buffer_size); + S2BINLIB_API int s2binlib_read_by_mem_address(const char *binary_name, uint64_t mem_address, uint8_t *buffer, size_t buffer_size); /** * Find a virtual function by vtable name and index, return virtual address @@ -784,7 +814,7 @@ extern "C" * printf("VFunc VA: %p\n", vfunc_va); * } */ - int s2binlib_find_vfunc_by_vtbname_va(const char *binary_name, const char *vtable_name, size_t vfunc_index, void **result); + S2BINLIB_API int s2binlib_find_vfunc_by_vtbname_va(const char *binary_name, const char *vtable_name, size_t vfunc_index, void **result); /** * Find a virtual function by vtable name and index, return runtime address @@ -813,7 +843,7 @@ extern "C" * printf("VFunc at: %p\n", vfunc_addr); * } */ - int s2binlib_find_vfunc_by_vtbname(const char *binary_name, const char *vtable_name, size_t vfunc_index, void **result); + S2BINLIB_API int s2binlib_find_vfunc_by_vtbname(const char *binary_name, const char *vtable_name, size_t vfunc_index, void **result); /** * Find a virtual function by vtable pointer and index, return virtual address @@ -839,7 +869,7 @@ extern "C" * printf("VFunc VA: %p\n", vfunc_va); * } */ - int s2binlib_find_vfunc_by_vtbptr_va(void *vtable_ptr, size_t vfunc_index, void **result); + S2BINLIB_API int s2binlib_find_vfunc_by_vtbptr_va(void *vtable_ptr, size_t vfunc_index, void **result); /** * Find a virtual function by vtable pointer and index, return runtime address @@ -865,7 +895,7 @@ extern "C" * printf("VFunc at: %p\n", vfunc_addr); * } */ - int s2binlib_find_vfunc_by_vtbptr(void *vtable_ptr, size_t vfunc_index, void **result); + S2BINLIB_API int s2binlib_find_vfunc_by_vtbptr(void *vtable_ptr, size_t vfunc_index, void **result); /** * Find a string in the binary and return its virtual address @@ -891,7 +921,7 @@ extern "C" * printf("String VA: %p\n", string_va); * } */ - int s2binlib_find_string_va(const char *binary_name, const char *string, void **result); + S2BINLIB_API int s2binlib_find_string_va(const char *binary_name, const char *string, void **result); /** * Find a string in the binary and return its runtime memory address @@ -917,7 +947,7 @@ extern "C" * printf("String at: %p\n", string_addr); * } */ - int s2binlib_find_string(const char *binary_name, const char *string, void **result); + S2BINLIB_API int s2binlib_find_string(const char *binary_name, const char *string, void **result); /** * Dump and cache all cross-references in a binary @@ -942,7 +972,7 @@ extern "C" * printf("Xrefs cached successfully\n"); * } */ - int s2binlib_dump_xrefs(const char *binary_name); + S2BINLIB_API int s2binlib_dump_xrefs(const char *binary_name); /** * Get the count of cached cross-references for a target virtual address @@ -974,7 +1004,7 @@ extern "C" * free(xrefs); * } */ - int s2binlib_get_xrefs_count(const char *binary_name, void *target_va); + S2BINLIB_API int s2binlib_get_xrefs_count(const char *binary_name, void *target_va); /** * Get cached cross-references for a target virtual address into a buffer @@ -1013,7 +1043,7 @@ extern "C" * free(xrefs); * } */ - int s2binlib_get_xrefs_cached(const char *binary_name, void *target_va, void **buffer, size_t buffer_size); + S2BINLIB_API int s2binlib_get_xrefs_cached(const char *binary_name, void *target_va, void **buffer, size_t buffer_size); /** * Unload a specific binary from memory @@ -1034,7 +1064,7 @@ extern "C" * printf("Binary unloaded successfully\n"); * } */ - int s2binlib_unload_binary(const char *binary_name); + S2BINLIB_API int s2binlib_unload_binary(const char *binary_name); /** * Unload all binaries from memory @@ -1052,7 +1082,7 @@ extern "C" * printf("All binaries unloaded successfully\n"); * } */ - int s2binlib_unload_all_binaries(void); + S2BINLIB_API int s2binlib_unload_all_binaries(void); /** * Install a JIT trampoline at a memory address @@ -1087,7 +1117,7 @@ extern "C" * printf("Trampoline installed successfully\n"); * } */ - int s2binlib_install_trampoline(void *mem_address, void **trampoline_address_out); + S2BINLIB_API int s2binlib_install_trampoline(void *mem_address, void **trampoline_address_out); /** * @brief Follow cross-reference from memory address to memory address @@ -1123,7 +1153,7 @@ extern "C" * printf("Xref target: %p\n", target_addr); * } */ - int s2binlib_follow_xref_mem_to_mem(const void *mem_address, void **target_address_out); + S2BINLIB_API int s2binlib_follow_xref_mem_to_mem(const void *mem_address, void **target_address_out); /** * @brief Follow cross-reference from virtual address to memory address @@ -1154,7 +1184,7 @@ extern "C" * printf("Xref target: %p\n", target_addr); * } */ - int s2binlib_follow_xref_va_to_mem(const char *binary_name, uint64_t va, void **target_address_out); + S2BINLIB_API int s2binlib_follow_xref_va_to_mem(const char *binary_name, uint64_t va, void **target_address_out); /** * @brief Follow cross-reference from virtual address to virtual address @@ -1185,7 +1215,7 @@ extern "C" * printf("Xref target VA: 0x%llX\n", target_va); * } */ - int s2binlib_follow_xref_va_to_va(const char *binary_name, uint64_t va, uint64_t *target_va_out); + S2BINLIB_API int s2binlib_follow_xref_va_to_va(const char *binary_name, uint64_t va, uint64_t *target_va_out); /** * @brief Find the NetworkVar_StateChanged vtable index by virtual address @@ -1211,7 +1241,7 @@ extern "C" * printf("StateChanged index: %llu\n", index); * } */ - int s2binlib_find_networkvar_vtable_statechanged_va(uint64_t vtable_va, uint64_t *result); + S2BINLIB_API int s2binlib_find_networkvar_vtable_statechanged_va(uint64_t vtable_va, uint64_t *result); /** * @brief Find the NetworkVar_StateChanged vtable index by memory address @@ -1238,7 +1268,7 @@ extern "C" * printf("StateChanged index: %llu\n", index); * } */ - int s2binlib_find_networkvar_vtable_statechanged(uint64_t vtable_mem_address, uint64_t *result); + S2BINLIB_API int s2binlib_find_networkvar_vtable_statechanged(uint64_t vtable_mem_address, uint64_t *result); #ifdef __cplusplus } diff --git a/src/c_bindings.rs b/src/c_bindings.rs index 6c9e505..9b68eea 100644 --- a/src/c_bindings.rs +++ b/src/c_bindings.rs @@ -23,6 +23,31 @@ use once_cell::sync::Lazy; use crate::S2BinLib; +/// Macro for debug logging in C bindings when the debug_c_bindings feature is enabled +#[cfg(feature = "debug_c_bindings")] +macro_rules! c_debug { + ($($arg:tt)*) => { + println!("[S2BinLib C Binding Debug] {}", format!($($arg)*)); + }; +} + +#[cfg(not(feature = "debug_c_bindings"))] +macro_rules! c_debug { + ($($arg:tt)*) => {}; +} + +/// Macro to return an error code with optional debug message +macro_rules! return_error { + ($code:expr, $msg:expr) => {{ + c_debug!("Error {}: {} (at {}:{})", $code, $msg, file!(), line!()); + return $code; + }}; + ($code:expr) => {{ + c_debug!("Error {} (at {}:{})", $code, file!(), line!()); + return $code; + }}; +} + /// Global S2BinLib instance static S2BINLIB: Lazy>> = Lazy::new(|| Mutex::new(None)); @@ -59,18 +84,18 @@ pub extern "C" fn s2binlib_initialize( unsafe { // Validate input pointers if game_path.is_null() || game_type.is_null() { - return -2; + return_error!(-2, "Invalid parameters: game_path or game_type is null"); } // Convert C strings to Rust strings let game_path_str = match CStr::from_ptr(game_path).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert game_path to UTF-8 string"), }; let game_type_str = match CStr::from_ptr(game_type).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert game_type to UTF-8 string"), }; // Automatically detect the operating system @@ -79,13 +104,13 @@ pub extern "C" fn s2binlib_initialize( } else if cfg!(target_os = "linux") { "linux" } else { - return -2; // Unsupported OS + return_error!(-2, "Unsupported operating system"); }; // Initialize the global instance let mut s2binlib = match S2BINLIB.lock() { Ok(lib) => lib, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; *s2binlib = Some(S2BinLib::new(game_path_str, game_type_str, os_str)); @@ -127,29 +152,29 @@ pub extern "C" fn s2binlib_initialize_with_os( unsafe { // Validate input pointers if game_path.is_null() || game_type.is_null() || os.is_null() { - return -2; + return_error!(-2, "Invalid parameters: game_path, game_type or os is null"); } // Convert C strings to Rust strings let game_path_str = match CStr::from_ptr(game_path).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert game_path to UTF-8 string"), }; let game_type_str = match CStr::from_ptr(game_type).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert game_type to UTF-8 string"), }; let os_str = match CStr::from_ptr(os).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert os to UTF-8 string"), }; // Initialize the global instance let mut s2binlib = match S2BINLIB.lock() { Ok(lib) => lib, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; *s2binlib = Some(S2BinLib::new(game_path_str, game_type_str, os_str)); @@ -195,29 +220,29 @@ pub extern "C" fn s2binlib_pattern_scan( unsafe { // Validate input pointers if binary_name.is_null() || pattern.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, pattern or result is null"); } // Convert C strings to Rust strings let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let pattern_str = match CStr::from_ptr(pattern).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; // Get the global instance let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; // Load binary if not already loaded @@ -231,7 +256,7 @@ pub extern "C" fn s2binlib_pattern_scan( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -274,29 +299,29 @@ pub extern "C" fn s2binlib_find_vtable( unsafe { // Validate input pointers if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } // Convert C strings to Rust strings let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; // Get the global instance let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; // Load binary if not already loaded @@ -310,7 +335,7 @@ pub extern "C" fn s2binlib_find_vtable( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -352,29 +377,29 @@ pub extern "C" fn s2binlib_find_symbol( unsafe { // Validate input pointers if binary_name.is_null() || symbol_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, symbol_name or result is null"); } // Convert C strings to Rust strings let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let symbol_name_str = match CStr::from_ptr(symbol_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; // Get the global instance let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; // Load binary if not already loaded @@ -388,7 +413,7 @@ pub extern "C" fn s2binlib_find_symbol( *result = addr as *mut c_void; 0 } - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -428,24 +453,24 @@ pub extern "C" fn s2binlib_set_module_base_from_pointer( unsafe { // Validate input pointers if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } // Convert C strings to Rust strings let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; // Get the global instance let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; // Set the base address for the module @@ -483,22 +508,22 @@ pub extern "C" fn s2binlib_clear_module_base_address( ) -> i32 { unsafe { if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; s2binlib.clear_module_base_address(binary_name_str); @@ -506,6 +531,80 @@ pub extern "C" fn s2binlib_clear_module_base_address( } } +/// Set a custom binary path for a specific binary and operating system +/// +/// This allows overriding the default binary path resolution for a specific binary. +/// You can specify different paths for Windows and Linux builds. +/// +/// # Parameters +/// * `binary_name` - Name of the binary (e.g., "server", "client") (null-terminated C string) +/// * `path` - The custom file path to the binary (null-terminated C string) +/// * `os` - Operating system identifier ("windows" or "linux") (null-terminated C string) +/// +/// # Returns +/// * 0 on success +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -4 if unsupported OS specified +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// int result = s2binlib_set_custom_binary_path("server", "/custom/path/server.dll", "windows"); +/// if (result == 0) { +/// printf("Custom binary path set successfully\n"); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_set_custom_binary_path( + binary_name: *const c_char, + path: *const c_char, + os: *const c_char +) -> i32 { + unsafe { + // Validate input pointers + if binary_name.is_null() || path.is_null() || os.is_null() { + return_error!(-2, "Invalid parameters: binary_name, path or os is null"); + } + + // Convert C strings to Rust strings + let binary_name_str = match CStr::from_ptr(binary_name).to_str() { + Ok(s) => s, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), + }; + + let path_str = match CStr::from_ptr(path).to_str() { + Ok(s) => s, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), + }; + + let os_str = match CStr::from_ptr(os).to_str() { + Ok(s) => s, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), + }; + + // Get the global instance + let mut s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), + }; + + let s2binlib = match s2binlib_guard.as_mut() { + Some(lib) => lib, + None => return_error!(-1, "S2BinLib not initialized"), + }; + + // Set the custom binary path + match s2binlib.set_custom_binary_path(binary_name_str, path_str, os_str) { + Ok(_) => 0, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), // Unsupported OS + } + } +} + /// Get the module base address /// /// Returns the base address of a loaded module. If a manual base address was set @@ -541,22 +640,22 @@ pub extern "C" fn s2binlib_get_module_base_address( ) -> i32 { unsafe { if binary_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.get_module_base_address(binary_name_str) { @@ -564,7 +663,7 @@ pub extern "C" fn s2binlib_get_module_base_address( *result = addr as *mut c_void; 0 } - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -597,22 +696,22 @@ pub extern "C" fn s2binlib_is_binary_loaded( ) -> i32 { unsafe { if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if s2binlib.is_binary_loaded(binary_name_str) { @@ -654,22 +753,22 @@ pub extern "C" fn s2binlib_load_binary( ) -> i32 { unsafe { if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; s2binlib.load_binary(binary_name_str); @@ -718,17 +817,17 @@ pub extern "C" fn s2binlib_get_binary_path( let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; let path = s2binlib.get_binary_path(binary_name_str); @@ -785,27 +884,27 @@ pub extern "C" fn s2binlib_find_vtable_va( ) -> i32 { unsafe { if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -817,7 +916,7 @@ pub extern "C" fn s2binlib_find_vtable_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -862,27 +961,27 @@ pub extern "C" fn s2binlib_find_vtable_mangled_va( ) -> i32 { unsafe { if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -894,7 +993,7 @@ pub extern "C" fn s2binlib_find_vtable_mangled_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -938,27 +1037,27 @@ pub extern "C" fn s2binlib_find_vtable_mangled( ) -> i32 { unsafe { if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -970,7 +1069,7 @@ pub extern "C" fn s2binlib_find_vtable_mangled( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1017,32 +1116,32 @@ pub extern "C" fn s2binlib_find_vtable_nested_2_va( ) -> i32 { unsafe { if binary_name.is_null() || class1_name.is_null() || class2_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, class names or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let class1_name_str = match CStr::from_ptr(class1_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let class2_name_str = match CStr::from_ptr(class2_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1054,7 +1153,7 @@ pub extern "C" fn s2binlib_find_vtable_nested_2_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1100,32 +1199,32 @@ pub extern "C" fn s2binlib_find_vtable_nested_2( ) -> i32 { unsafe { if binary_name.is_null() || class1_name.is_null() || class2_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, class names or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let class1_name_str = match CStr::from_ptr(class1_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let class2_name_str = match CStr::from_ptr(class2_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1137,7 +1236,7 @@ pub extern "C" fn s2binlib_find_vtable_nested_2( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1178,27 +1277,27 @@ pub extern "C" fn s2binlib_get_vtable_vfunc_count( ) -> i32 { unsafe { if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1210,7 +1309,7 @@ pub extern "C" fn s2binlib_get_vtable_vfunc_count( *result = count; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1263,22 +1362,22 @@ pub extern "C" fn s2binlib_get_vtable_vfunc_count_by_va( ) -> i32 { unsafe { if binary_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1290,7 +1389,7 @@ pub extern "C" fn s2binlib_get_vtable_vfunc_count_by_va( *result = count; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1338,27 +1437,27 @@ pub extern "C" fn s2binlib_pattern_scan_va( ) -> i32 { unsafe { if binary_name.is_null() || pattern.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, pattern or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let pattern_str = match CStr::from_ptr(pattern).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1370,7 +1469,7 @@ pub extern "C" fn s2binlib_pattern_scan_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1441,27 +1540,27 @@ pub extern "C" fn s2binlib_pattern_scan_all_va( ) -> i32 { unsafe { if binary_name.is_null() || pattern.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name or pattern is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let pattern_str = match CStr::from_ptr(pattern).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1473,7 +1572,7 @@ pub extern "C" fn s2binlib_pattern_scan_all_va( callback(index, addr as *mut c_void, user_data) }) { Ok(_) => 0, - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1532,27 +1631,27 @@ pub extern "C" fn s2binlib_pattern_scan_all( ) -> i32 { unsafe { if binary_name.is_null() || pattern.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name or pattern is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let pattern_str = match CStr::from_ptr(pattern).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1564,7 +1663,7 @@ pub extern "C" fn s2binlib_pattern_scan_all( callback(index, addr as *mut c_void, user_data) }) { Ok(_) => 0, - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1608,27 +1707,27 @@ pub extern "C" fn s2binlib_find_export_va( ) -> i32 { unsafe { if binary_name.is_null() || export_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, export_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let export_name_str = match CStr::from_ptr(export_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1640,7 +1739,7 @@ pub extern "C" fn s2binlib_find_export_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1684,27 +1783,27 @@ pub extern "C" fn s2binlib_find_export( ) -> i32 { unsafe { if binary_name.is_null() || export_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, export_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let export_name_str = match CStr::from_ptr(export_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1716,7 +1815,7 @@ pub extern "C" fn s2binlib_find_export( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -1759,27 +1858,27 @@ pub extern "C" fn s2binlib_find_symbol_va( ) -> i32 { unsafe { if binary_name.is_null() || symbol_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, symbol_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let symbol_name_str = match CStr::from_ptr(symbol_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1791,7 +1890,7 @@ pub extern "C" fn s2binlib_find_symbol_va( *result = addr as *mut c_void; 0 } - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -1840,17 +1939,17 @@ pub extern "C" fn s2binlib_read_by_file_offset( let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1863,7 +1962,7 @@ pub extern "C" fn s2binlib_read_by_file_offset( std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer, copy_size); 0 } - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -1912,17 +2011,17 @@ pub extern "C" fn s2binlib_read_by_va( let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -1935,7 +2034,7 @@ pub extern "C" fn s2binlib_read_by_va( std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer, copy_size); 0 } - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -1985,17 +2084,17 @@ pub extern "C" fn s2binlib_read_by_mem_address( let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2008,7 +2107,7 @@ pub extern "C" fn s2binlib_read_by_mem_address( std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer, copy_size); 0 } - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -2055,27 +2154,27 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbname_va( ) -> i32 { unsafe { if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2087,7 +2186,7 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbname_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2133,27 +2232,27 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbname( ) -> i32 { unsafe { if binary_name.is_null() || vtable_name.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, vtable_name or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2165,7 +2264,7 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbname( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2208,17 +2307,17 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbptr_va( ) -> i32 { unsafe { if result.is_null() { - return -2; + return_error!(-2, "Invalid parameter: result pointer is null"); } let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.find_vfunc_by_vtbptr_va(vtable_ptr as u64, vfunc_index) { @@ -2226,7 +2325,7 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbptr_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2269,17 +2368,17 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbptr( ) -> i32 { unsafe { if result.is_null() { - return -2; + return_error!(-2, "Invalid parameter: result pointer is null"); } let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.find_vfunc_by_vtbptr(vtable_ptr as u64, vfunc_index) { @@ -2287,7 +2386,7 @@ pub extern "C" fn s2binlib_find_vfunc_by_vtbptr( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2331,27 +2430,27 @@ pub extern "C" fn s2binlib_find_string_va( ) -> i32 { unsafe { if binary_name.is_null() || string.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, string or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let string_str = match CStr::from_ptr(string).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2363,7 +2462,7 @@ pub extern "C" fn s2binlib_find_string_va( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2407,27 +2506,27 @@ pub extern "C" fn s2binlib_find_string( ) -> i32 { unsafe { if binary_name.is_null() || string.is_null() || result.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name, string or result is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let string_str = match CStr::from_ptr(string).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2439,7 +2538,7 @@ pub extern "C" fn s2binlib_find_string( *result = addr as *mut c_void; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2484,22 +2583,22 @@ pub extern "C" fn s2binlib_dump_xrefs( ) -> i32 { unsafe { if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2508,7 +2607,7 @@ pub extern "C" fn s2binlib_dump_xrefs( match s2binlib.dump_xrefs(binary_name_str) { Ok(_) => 0, - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -2555,22 +2654,22 @@ pub extern "C" fn s2binlib_get_xrefs_count( ) -> i32 { unsafe { if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.find_xrefs_cached(binary_name_str, target_va as u64) { @@ -2636,17 +2735,17 @@ pub extern "C" fn s2binlib_get_xrefs_cached( let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.find_xrefs_cached(binary_name_str, target_va as u64) { @@ -2697,22 +2796,22 @@ pub extern "C" fn s2binlib_unload_binary( ) -> i32 { unsafe { if binary_name.is_null() { - return -2; + return_error!(-2, "Invalid parameter: binary_name is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; s2binlib.unload_binary(binary_name_str); @@ -2744,12 +2843,12 @@ pub extern "C" fn s2binlib_unload_binary( pub extern "C" fn s2binlib_unload_all_binaries() -> i32 { let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; s2binlib.unload_all_binaries(); @@ -2802,12 +2901,12 @@ pub extern "C" fn s2binlib_install_trampoline( ) -> i32 { let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.install_trampoline(mem_address as u64) { @@ -2817,7 +2916,7 @@ pub extern "C" fn s2binlib_install_trampoline( } 0 }, - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to install trampoline"), } } @@ -2867,17 +2966,17 @@ pub extern "C" fn s2binlib_follow_xref_mem_to_mem( target_address_out: *mut *mut c_void, ) -> i32 { if mem_address.is_null() || target_address_out.is_null() { - return -2; + return_error!(-2, "Invalid parameters: mem_address or target_address_out is null"); } let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.follow_xref_mem_to_mem(mem_address as u64) { @@ -2887,7 +2986,7 @@ pub extern "C" fn s2binlib_follow_xref_mem_to_mem( } 0 }, - Err(_) => -3, + Err(_) => return_error!(-3, "No valid xref found or invalid instruction"), } } @@ -2934,22 +3033,22 @@ pub extern "C" fn s2binlib_follow_xref_va_to_mem( ) -> i32 { unsafe { if binary_name.is_null() || target_address_out.is_null() { - return -2; + return_error!(-2, "Invalid parameters: binary_name or target_address_out is null"); } let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -2961,7 +3060,7 @@ pub extern "C" fn s2binlib_follow_xref_va_to_mem( *target_address_out = target as *mut c_void; 0 }, - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -3014,17 +3113,17 @@ pub extern "C" fn s2binlib_follow_xref_va_to_va( let binary_name_str = match CStr::from_ptr(binary_name).to_str() { Ok(s) => s, - Err(_) => return -2, + Err(_) => return_error!(-2, "Failed to convert C string to UTF-8"), }; let mut s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_mut() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; if !s2binlib.is_binary_loaded(binary_name_str) { @@ -3036,7 +3135,7 @@ pub extern "C" fn s2binlib_follow_xref_va_to_va( *target_va_out = target; 0 }, - Err(_) => -3, + Err(_) => return_error!(-3, "Failed to load binary or operation failed"), } } } @@ -3077,17 +3176,17 @@ pub extern "C" fn s2binlib_find_networkvar_vtable_statechanged_va( ) -> i32 { unsafe { if result.is_null() { - return -2; + return_error!(-2, "Invalid parameter: result pointer is null"); } let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.find_networkvar_vtable_statechanged_va(vtable_va) { @@ -3095,7 +3194,7 @@ pub extern "C" fn s2binlib_find_networkvar_vtable_statechanged_va( *result = index; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } @@ -3137,17 +3236,17 @@ pub extern "C" fn s2binlib_find_networkvar_vtable_statechanged( ) -> i32 { unsafe { if result.is_null() { - return -2; + return_error!(-2, "Invalid parameter: result pointer is null"); } let s2binlib_guard = match S2BINLIB.lock() { Ok(guard) => guard, - Err(_) => return -5, + Err(_) => return_error!(-5, "Failed to acquire global S2BinLib lock"), }; let s2binlib = match s2binlib_guard.as_ref() { Some(lib) => lib, - None => return -1, + None => return_error!(-1, "S2BinLib not initialized"), }; match s2binlib.find_networkvar_vtable_statechanged(vtable_mem_address) { @@ -3155,7 +3254,7 @@ pub extern "C" fn s2binlib_find_networkvar_vtable_statechanged( *result = index; 0 } - Err(_) => -4, + Err(_) => return_error!(-4, "Pattern not found or operation failed"), } } } diff --git a/src/lib.rs b/src/lib.rs index c310980..36b3351 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,11 @@ mod tests { // s2binlib.load_binary("tier0"); // println!("1"); - + + + let vtable = s2binlib.find_vtable_nested_2_va("server", "CBaseAnimGraphController", "NetworkVar_m_animGraphNetworkedVars")?; + let index = s2binlib.find_networkvar_vtable_statechanged_va(vtable)?; + println!("index {:X}", index); let start = Instant::now(); diff --git a/src/s2binlib.rs b/src/s2binlib.rs index 3d890ec..6d1d509 100644 --- a/src/s2binlib.rs +++ b/src/s2binlib.rs @@ -2,7 +2,7 @@ * S2BinLib - A static library that helps resolving memory from binary file * and map to absolute memory address, targeting source 2 game engine. * Copyright (C) 2025 samyyc - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -17,13 +17,21 @@ * along with this program. If not, see . ***********************************************************************************/ -use std::{collections::{btree_map::VacantEntry, HashMap}, fs, path::PathBuf}; +use std::{ + collections::{HashMap, btree_map::VacantEntry}, + fs, + path::PathBuf, +}; -use anyhow::{anyhow, Result}; -use object::{read::pe::ImageOptionalHeader, Object, ObjectSection, ObjectSymbol}; +use anyhow::{Result, anyhow}; use iced_x86::{Code, Decoder, DecoderOptions, Instruction, OpKind, Register}; +use object::{Object, ObjectSection, ObjectSymbol, read::pe::ImageOptionalHeader}; -use crate::{find_pattern_simd, is_executable, jit::JitTrampoline, memory::{get_module_base_from_pointer, set_mem_access} }; +use crate::{ + find_pattern_simd, is_executable, + jit::JitTrampoline, + memory::{get_module_base_from_pointer, set_mem_access}, +}; #[cfg(target_os = "windows")] use std::ffi::CString; @@ -36,7 +44,7 @@ use std::io::{BufRead, BufReader}; #[cfg(target_os = "windows")] unsafe extern "system" { fn GetModuleHandleA(lpModuleName: *const u8) -> *mut c_void; -} +} pub struct S2BinLib { game_path: PathBuf, @@ -47,33 +55,33 @@ pub struct S2BinLib { /// Cached cross-references: binary_name -> (target_va -> Vec) xrefs_cache: HashMap>>, /// Trampolines: (mem_address -> JitTrampoline) - trampolines: HashMap + trampolines: HashMap, + custom_binary_paths_windows: HashMap, + custom_binary_paths_linux: HashMap, } - fn read_int32(data: &[u8], offset: u64) -> u32 { - let mut value = 0; - for i in 0..4 { - value |= (data[offset as usize + i as usize] as u32) << (i * 8); - } - value + let mut value = 0; + for i in 0..4 { + value |= (data[offset as usize + i as usize] as u32) << (i * 8); + } + value } fn read_int64(data: &[u8], offset: u64) -> i64 { - let mut value = 0i64; - for i in 0..8 { - value |= (data[offset as usize + i as usize] as i64) << (i * 8); - } - value + let mut value = 0i64; + for i in 0..8 { + value |= (data[offset as usize + i as usize] as i64) << (i * 8); + } + value } impl S2BinLib { - fn get_os_name(&self) -> String { - match self.os.as_str() { - "windows" => "win64".to_string(), - _ => "linuxsteamrt64".to_string(), - } + match self.os.as_str() { + "windows" => "win64".to_string(), + _ => "linuxsteamrt64".to_string(), + } } fn get_os_lib_name(&self, lib_name: &str) -> String { @@ -101,7 +109,10 @@ impl S2BinLib { unsafe { let handle = GetModuleHandleA(c_module_name.as_ptr() as *const u8); if handle.is_null() { - return Err(anyhow::anyhow!("Module '{}' not found or not loaded", module_name)); + return Err(anyhow::anyhow!( + "Module '{}' not found or not loaded", + module_name + )); } Ok(handle as u64) } @@ -109,7 +120,9 @@ impl S2BinLib { #[cfg(not(target_os = "windows"))] fn get_module_base_address_windows(&self, _module_name: &str) -> Result { - Err(anyhow::anyhow!("Windows module loading not supported on this platform")) + Err(anyhow::anyhow!( + "Windows module loading not supported on this platform" + )) } #[cfg(target_os = "linux")] @@ -131,83 +144,122 @@ impl S2BinLib { } } - Err(anyhow::anyhow!("Module '{}' not found in process memory", module_name)) + Err(anyhow::anyhow!( + "Module '{}' not found in process memory", + module_name + )) } #[cfg(not(target_os = "linux"))] fn get_module_base_address_linux(&self, _module_name: &str) -> Result { - Err(anyhow::anyhow!("Linux module loading not supported on this platform")) + Err(anyhow::anyhow!( + "Linux module loading not supported on this platform" + )) } fn decorate_rtti_type_descriptor_name(&self, name: &str) -> String { - match self.os.as_str() { - "windows" => format!(".?AV{}@@", name), - _ => format!("{}{}", name.len(), name), - } + match self.os.as_str() { + "windows" => format!(".?AV{}@@", name), + _ => format!("{}{}", name.len(), name), + } } fn decorate_rtti_type_descriptor_name_nested_2(&self, class1: &str, class2: &str) -> String { - match self.os.as_str() { - "windows" => format!(".?AV{}@{}@@", class2, class1), - _ => format!("N{}{}{}{}E", class1.len(), class1, class2.len(), class2), - } + match self.os.as_str() { + "windows" => format!(".?AV{}@{}@@", class2, class1), + _ => format!("N{}{}{}{}E", class1.len(), class1, class2.len(), class2), + } + } + + pub fn set_custom_binary_path( + &mut self, + binary_name: &str, + path: &str, + os: &str, + ) -> Result<()> { + if os.to_lowercase() == "windows" { + self.custom_binary_paths_windows + .insert(binary_name.to_string(), path.to_string()); + Ok(()) + } else if os.to_lowercase() == "linux" { + self.custom_binary_paths_linux + .insert(binary_name.to_string(), path.to_string()); + Ok(()) + } else { + anyhow::bail!("Unsupported OS: {}", os); + } } pub fn new(game_path: &str, game_type: &str, os: &str) -> Self { - Self { - game_path: PathBuf::from(game_path), - game_type: game_type.to_string(), - os: os.to_string(), - binaries: HashMap::new(), - manual_base_addresses: HashMap::new(), - xrefs_cache: HashMap::new(), - trampolines: HashMap::new() + Self { + game_path: PathBuf::from(game_path), + game_type: game_type.to_string(), + os: os.to_string(), + binaries: HashMap::new(), + manual_base_addresses: HashMap::new(), + xrefs_cache: HashMap::new(), + trampolines: HashMap::new(), + custom_binary_paths_windows: HashMap::new(), + custom_binary_paths_linux: HashMap::new(), } } /// Manually set the base address for a module from a pointer - /// + /// /// This allows overriding the automatic base address detection. /// Useful when the module is loaded in a non-standard way or /// when you need to force a specific base address. - /// + /// /// # Arguments /// * `lib_name` - The library name without extension (e.g., "server", "engine2") /// * `pointer` - The pointer from the module - /// + /// /// # Example /// ```no_run /// let mut s2binlib = S2BinLib::new("path", "game", "windows"); /// s2binlib.set_module_base_from_pointer("server", 0x140000000); /// ``` pub fn set_module_base_from_pointer(&mut self, lib_name: &str, pointer: u64) { - self.manual_base_addresses.insert(lib_name.to_string(), get_module_base_from_pointer(pointer)); + self.manual_base_addresses + .insert(lib_name.to_string(), get_module_base_from_pointer(pointer)); } /// Clear manually set base address for a module - /// + /// /// After calling this, the module will use automatic base address detection again. - /// + /// /// # Arguments /// * `lib_name` - The library name without extension (e.g., "server", "engine2") pub fn clear_module_base_address(&mut self, lib_name: &str) { self.manual_base_addresses.remove(lib_name); } - pub fn get_binary_path(&self, binary_name: &str) -> String { + if self.os.to_lowercase() == "windows" { + if let Some(path) = self.custom_binary_paths_windows.get(binary_name) { + return path.clone(); + } + } else if self.os.to_lowercase() == "linux" { + if let Some(path) = self.custom_binary_paths_linux.get(binary_name) { + return path.clone(); + } + } match binary_name { - "server" | "client" | "matchmaking" | "host" => self.game_path - .join(self.game_type.clone()) - .join("bin") - .join(self.get_os_name()) - .join(self.get_os_lib_name(binary_name)) - .to_string_lossy().to_string(), - _ => self.game_path - .join("bin") - .join(self.get_os_name()) - .join(self.get_os_lib_name(binary_name)) - .to_string_lossy().to_string(), + "server" | "client" | "matchmaking" | "host" => self + .game_path + .join(self.game_type.clone()) + .join("bin") + .join(self.get_os_name()) + .join(self.get_os_lib_name(binary_name)) + .to_string_lossy() + .to_string(), + _ => self + .game_path + .join("bin") + .join(self.get_os_name()) + .join(self.get_os_lib_name(binary_name)) + .to_string_lossy() + .to_string(), } } @@ -219,9 +271,9 @@ impl S2BinLib { let binary_path = self.get_binary_path(binary_name); let binary_data = fs::read(binary_path.clone()); if let Ok(binary_data) = binary_data { - self.binaries.insert(binary_name.to_string(), binary_data); + self.binaries.insert(binary_name.to_string(), binary_data); } else { - println!("[Warning] Binary not found: {}", binary_path.clone()); + println!("[Warning] Binary not found: {}", binary_path.clone()); } } @@ -235,20 +287,19 @@ impl S2BinLib { fn file_offset_to_va(&self, binary_name: &str, file_offset: u64) -> Result { let binary_data = self.get_binary(binary_name)?; let object = object::File::parse(binary_data)?; - + for section in object.sections() { if let Some(file_range) = section.file_range() { let section_file_start = file_range.0; let section_file_end = file_range.0 + file_range.1; - if file_offset >= section_file_start && file_offset < section_file_end { let section_va = section.address(); let offset_in_section = file_offset - section_file_start; return Ok(section_va + offset_in_section); } } - }; + } Err(anyhow::anyhow!("File offset not found in any section.")) } @@ -257,161 +308,212 @@ impl S2BinLib { let object = object::File::parse(binary_data)?; for section in object.sections() { - let section_va = section.address(); - let section_size = section.size(); - let section_va_end = section_va + section_size; - - if va >= section_va && va < section_va_end { - if let Some(file_range) = section.file_range() { - let section_file_start = file_range.0; - let offset_in_section = va - section_va; - return Ok(section_file_start + offset_in_section); - } - } + let section_va = section.address(); + let section_size = section.size(); + let section_va_end = section_va + section_size; + + if va >= section_va && va < section_va_end { + if let Some(file_range) = section.file_range() { + let section_file_start = file_range.0; + let offset_in_section = va - section_va; + return Ok(section_file_start + offset_in_section); + } + } } Err(anyhow::anyhow!("va not found in any section.")) } fn is_file_offset_executable(&self, binary_name: &str, file_offset: u64) -> Result { - let binary_data = self.get_binary(binary_name)?; - let object = object::File::parse(binary_data)?; - for section in object.sections() { - if let Some(file_range) = section.file_range() { - let section_file_start = file_range.0; - let section_file_end = file_range.0 + file_range.1; - if file_offset >= section_file_start && file_offset < section_file_end { - return Ok(is_executable(section.flags())) - } + let binary_data = self.get_binary(binary_name)?; + let object = object::File::parse(binary_data)?; + for section in object.sections() { + if let Some(file_range) = section.file_range() { + let section_file_start = file_range.0; + let section_file_end = file_range.0 + file_range.1; + if file_offset >= section_file_start && file_offset < section_file_end { + return Ok(is_executable(section.flags())); + } + } } - }; - Err(anyhow::anyhow!("Address not found in any section.")) + Err(anyhow::anyhow!("Address not found in any section.")) } fn get_section_range(&self, binary_name: &str, section_name: &str) -> Result<(u64, u64)> { - let binary_data = self.get_binary(binary_name)?; - let object = object::File::parse(binary_data)?; - let section = object.section_by_name(section_name).ok_or_else(|| anyhow::anyhow!("Section not found."))?; - Ok(( section.file_range().unwrap().0, section.file_range().unwrap().1 + section.file_range().unwrap().0 )) + let binary_data = self.get_binary(binary_name)?; + let object = object::File::parse(binary_data)?; + let section = object + .section_by_name(section_name) + .ok_or_else(|| anyhow::anyhow!("Section not found."))?; + Ok(( + section.file_range().unwrap().0, + section.file_range().unwrap().1 + section.file_range().unwrap().0, + )) } fn find_pattern_string(&self, binary_name: &str, string: &str) -> Result { - let bytes = string.as_bytes().to_vec(); - // bytes.push(0); // null terminato + let bytes = string.as_bytes().to_vec(); + // bytes.push(0); // null terminato - self.find_pattern_bytes(binary_name, &bytes) + self.find_pattern_bytes(binary_name, &bytes) } - fn find_pattern_string_in_section(&self, binary_name: &str, section_name: &str, string: &str) -> Result { - let bytes = string.as_bytes().to_vec(); + fn find_pattern_string_in_section( + &self, + binary_name: &str, + section_name: &str, + string: &str, + ) -> Result { + let bytes = string.as_bytes().to_vec(); - self.find_pattern_bytes_in_section(binary_name, section_name, &bytes) - + self.find_pattern_bytes_in_section(binary_name, section_name, &bytes) } fn find_pattern_bytes(&self, binary_name: &str, pattern: &[u8]) -> Result { - let binary_data = self.get_binary(binary_name)?; - let pattern_wildcard = vec![]; - find_pattern_simd(binary_data, pattern, &pattern_wildcard) + let binary_data = self.get_binary(binary_name)?; + let pattern_wildcard = vec![]; + find_pattern_simd(binary_data, pattern, &pattern_wildcard) } - - fn find_pattern_int32_in_section(&self, binary_name: &str, section_name: &str, pattern: u32) -> Result { - let binary_data = self.get_binary(binary_name)?; - let pattern_wildcard = vec![]; - - let (start, end) = self.get_section_range(binary_name, section_name)?; - let mut result = find_pattern_simd(&binary_data[start as usize..end as usize], &pattern.to_le_bytes(), &pattern_wildcard)?; - if result != 0 { - result += start; - } - Ok(result) + fn find_pattern_int32_in_section( + &self, + binary_name: &str, + section_name: &str, + pattern: u32, + ) -> Result { + let binary_data = self.get_binary(binary_name)?; + let pattern_wildcard = vec![]; + + let (start, end) = self.get_section_range(binary_name, section_name)?; + let mut result = find_pattern_simd( + &binary_data[start as usize..end as usize], + &pattern.to_le_bytes(), + &pattern_wildcard, + )?; + if result != 0 { + result += start; + } + Ok(result) } - - fn find_pattern_bytes_in_section(&self, binary_name: &str, section_name: &str, pattern: &[u8]) -> Result { - let binary_data = self.get_binary(binary_name)?; - let (start, end) = self.get_section_range(binary_name, section_name)?; - let pattern_wildcard = vec![]; - let mut result = find_pattern_simd(&binary_data[start as usize..end as usize], pattern, &pattern_wildcard)?; - if result != 0 { - result += start; - } - Ok(result) + fn find_pattern_bytes_in_section( + &self, + binary_name: &str, + section_name: &str, + pattern: &[u8], + ) -> Result { + let binary_data = self.get_binary(binary_name)?; + let (start, end) = self.get_section_range(binary_name, section_name)?; + let pattern_wildcard = vec![]; + let mut result = find_pattern_simd( + &binary_data[start as usize..end as usize], + pattern, + &pattern_wildcard, + )?; + if result != 0 { + result += start; + } + Ok(result) } fn find_pattern_va(&self, binary_name: &str, pattern_string: &str) -> Result { - let binary_data = self.get_binary(binary_name)?; - let pattern = pattern_string.split(" ").map(|x| if x == "?" { 0u8 } else { u8::from_str_radix(x, 16).unwrap() }).collect::>(); - let pattern_wildcard = pattern_string.split(" ").enumerate().filter(|(_, x)| *x == "?").map(|(index, _)| index).collect::>(); - let result = find_pattern_simd(binary_data, &pattern, &pattern_wildcard)?; - Ok(self.file_offset_to_va(binary_name, result)?) - } + let binary_data = self.get_binary(binary_name)?; + let pattern = pattern_string + .split(" ") + .map(|x| { + if x == "?" { + 0u8 + } else { + u8::from_str_radix(x, 16).unwrap() + } + }) + .collect::>(); + let pattern_wildcard = pattern_string + .split(" ") + .enumerate() + .filter(|(_, x)| *x == "?") + .map(|(index, _)| index) + .collect::>(); + let result = find_pattern_simd(binary_data, &pattern, &pattern_wildcard)?; + Ok(self.file_offset_to_va(binary_name, result)?) + } fn get_image_base(&self, binary_name: &str) -> Result { - let binary_data = self.get_binary(binary_name)?; - let object = object::File::parse(binary_data)?; - - match object { - object::File::Pe64(pe) => { - let image_base = pe.nt_headers().optional_header.image_base(); - Ok(image_base) - } - object::File::Pe32(pe) => { - let image_base = pe.nt_headers().optional_header.image_base() as u64; - Ok(image_base) - } - object::File::Elf64(_) | object::File::Elf32(_) => { - Ok(0) - } - _ => Err(anyhow::anyhow!("Unsupported file format")), - } + let binary_data = self.get_binary(binary_name)?; + let object = object::File::parse(binary_data)?; + + match object { + object::File::Pe64(pe) => { + let image_base = pe.nt_headers().optional_header.image_base(); + Ok(image_base) + } + object::File::Pe32(pe) => { + let image_base = pe.nt_headers().optional_header.image_base() as u64; + Ok(image_base) + } + object::File::Elf64(_) | object::File::Elf32(_) => Ok(0), + _ => Err(anyhow::anyhow!("Unsupported file format")), + } } fn read_string(&self, binary_name: &str, file_offset: u64) -> Result { - let binary_data = self.get_binary(binary_name)?; - let mut bytes = vec![]; - let mut file_offset = file_offset; - while binary_data[file_offset as usize] != 0 { - bytes.push(binary_data[file_offset as usize]); - file_offset += 1; - } - Ok(String::from_utf8_lossy(&bytes).to_string()) + let binary_data = self.get_binary(binary_name)?; + let mut bytes = vec![]; + let mut file_offset = file_offset; + while binary_data[file_offset as usize] != 0 { + bytes.push(binary_data[file_offset as usize]); + file_offset += 1; + } + Ok(String::from_utf8_lossy(&bytes).to_string()) } fn get_binary_name_by_ptr(&self, ptr: u64) -> Result { - for (binary_name, binary_data) in self.binaries.iter() { - let base_address = self.get_module_base_address(binary_name)?; - if ptr >= base_address && ptr < base_address + binary_data.len() as u64 { - return Ok(binary_name.clone()); + for (binary_name, binary_data) in self.binaries.iter() { + let base_address = self.get_module_base_address(binary_name)?; + if ptr >= base_address && ptr < base_address + binary_data.len() as u64 { + return Ok(binary_name.clone()); + } } - } - Err(anyhow::anyhow!("Binary not found.")) + Err(anyhow::anyhow!("Binary not found.")) } fn find_vtable_va_windows(&self, binary_name: &str, vtable_name: &str) -> Result { let binary_data = self.get_binary(binary_name)?; - let type_descriptor_name = self.find_pattern_string_in_section(binary_name, ".data", &vtable_name)?; + let type_descriptor_name = + self.find_pattern_string_in_section(binary_name, ".data", &vtable_name)?; - let rtti_type_descriptor = self.file_offset_to_va(binary_name, type_descriptor_name)? - 0x10 - self.get_image_base(binary_name)?; + let rtti_type_descriptor = self.file_offset_to_va(binary_name, type_descriptor_name)? + - 0x10 + - self.get_image_base(binary_name)?; let rtti_type_descriptor_ptr_pattern = rtti_type_descriptor.to_le_bytes().to_vec(); let (_start, end) = self.get_section_range(binary_name, ".rdata")?; - let mut reference = self.find_pattern_int32_in_section(binary_name, ".rdata", rtti_type_descriptor as u32)?; + let mut reference = + self.find_pattern_int32_in_section(binary_name, ".rdata", rtti_type_descriptor as u32)?; loop { - if read_int32(&binary_data, reference - 0xC) == 1 && read_int32(&binary_data, reference - 0x8) == 0 { - let reference_offset = self.file_offset_to_va(binary_name, reference - 0xC)?; - let rtti_complete_object_locator = self.find_pattern_int32_in_section(binary_name, ".rdata", reference_offset as u32)?; - return Ok(self.file_offset_to_va(binary_name, rtti_complete_object_locator + 8)?); - } - let last_reference = reference + 1; - let result = find_pattern_simd(&binary_data[last_reference as usize..end as usize], &rtti_type_descriptor_ptr_pattern[0..4], &vec![]); - if let Err(_) = result { - break; - } - reference = result.unwrap() + last_reference as u64; + if read_int32(&binary_data, reference - 0xC) == 1 + && read_int32(&binary_data, reference - 0x8) == 0 + { + let reference_offset = self.file_offset_to_va(binary_name, reference - 0xC)?; + let rtti_complete_object_locator = self.find_pattern_int32_in_section( + binary_name, + ".rdata", + reference_offset as u32, + )?; + return Ok(self.file_offset_to_va(binary_name, rtti_complete_object_locator + 8)?); + } + let last_reference = reference + 1; + let result = find_pattern_simd( + &binary_data[last_reference as usize..end as usize], + &rtti_type_descriptor_ptr_pattern[0..4], + &vec![], + ); + if let Err(_) = result { + break; + } + reference = result.unwrap() + last_reference as u64; } Err(anyhow::anyhow!("Vtable not found.")) @@ -424,26 +526,37 @@ impl S2BinLib { let offset = data_range.0; - let mut type_info_name = find_pattern_simd(&binary_data[offset as usize..], &vtable_name.as_bytes(), &vec![])?; + let mut type_info_name = find_pattern_simd( + &binary_data[offset as usize..], + &vtable_name.as_bytes(), + &vec![], + )?; type_info_name += offset; while type_info_name != 0 { + let type_info_name_str = self.read_string(binary_name, type_info_name)?; - let type_info_name_str = self.read_string(binary_name, type_info_name)?; - - if type_info_name_str == vtable_name { - break; - } - let last_type_descriptor_name = type_info_name + 1; - type_info_name = find_pattern_simd(&binary_data[last_type_descriptor_name as usize..], &vtable_name.as_bytes(), &vec![])?; - type_info_name += last_type_descriptor_name; + if type_info_name_str == vtable_name { + break; + } + let last_type_descriptor_name = type_info_name + 1; + type_info_name = find_pattern_simd( + &binary_data[last_type_descriptor_name as usize..], + &vtable_name.as_bytes(), + &vec![], + )?; + type_info_name += last_type_descriptor_name; } - + // Find reference to type name in .data.rel.ro section (8-byte pointer) let type_info_name_va = self.file_offset_to_va(binary_name, type_info_name)?; let type_info_name_ptr_pattern = type_info_name_va.to_le_bytes(); - - let reference_type_name = self.find_pattern_bytes_in_section(binary_name, ".data.rel.ro", &type_info_name_ptr_pattern[0..4])?; - + + let reference_type_name = self.find_pattern_bytes_in_section( + binary_name, + ".data.rel.ro", + &type_info_name_ptr_pattern[0..4], + )?; + // Offset back by 0x8 to get typeinfo let type_info = reference_type_name - 0x8; let type_info_va = self.file_offset_to_va(binary_name, type_info)?; @@ -453,21 +566,21 @@ impl S2BinLib { for section_name in &[".data.rel.ro", ".data.rel.ro.local"] { if let Ok((start, end)) = self.get_section_range(binary_name, section_name) { let mut search_offset = start; - + loop { // Find reference to typeinfo let result = find_pattern_simd( - &binary_data[search_offset as usize..end as usize], - &type_info_ptr_pattern, - &vec![] + &binary_data[search_offset as usize..end as usize], + &type_info_ptr_pattern, + &vec![], ); - + if result.is_err() || result.as_ref().unwrap() == &0 { break; } - + let reference = result.unwrap() + search_offset; - + // Check if offset to this is 0 (at -0x8 from the reference) if reference >= 0x8 { let offset_to_this = read_int64(binary_data, reference - 0x8); @@ -476,7 +589,7 @@ impl S2BinLib { return Ok(self.file_offset_to_va(binary_name, reference + 0x8)?); } } - + // Continue searching after this match search_offset = reference + 8; if search_offset >= end { @@ -490,237 +603,302 @@ impl S2BinLib { } fn is_valid_va(&self, binary_name: &str, va: u64) -> Result { - let Ok(file_offset) = self.va_to_file_offset(binary_name, va) else { - return Ok(false); - }; + let Ok(file_offset) = self.va_to_file_offset(binary_name, va) else { + return Ok(false); + }; - - Ok(file_offset > 0 && file_offset < self.get_binary(binary_name)?.len() as u64) + Ok(file_offset > 0 && file_offset < self.get_binary(binary_name)?.len() as u64) } fn is_valid_executable_va(&self, binary_name: &str, va: u64) -> Result { - if !self.is_valid_va(binary_name, va)? { - return Ok(false); - } + if !self.is_valid_va(binary_name, va)? { + return Ok(false); + } - let file_offset = self.va_to_file_offset(binary_name, va)?; - self.is_file_offset_executable(binary_name, file_offset) + let file_offset = self.va_to_file_offset(binary_name, va)?; + self.is_file_offset_executable(binary_name, file_offset) } - fn mem_address_to_va(&self, binary_name: &str, address: u64) -> Result { - let base_address = self.get_module_base_address(binary_name)?; - let image_base = self.get_image_base(binary_name)?; - Ok(address - base_address + image_base) + let base_address = self.get_module_base_address(binary_name)?; + let image_base = self.get_image_base(binary_name)?; + Ok(address - base_address + image_base) } - fn va_to_mem_address(&self, binary_name: &str, address: u64) -> Result { - let base_address = self.get_module_base_address(binary_name)?; - let image_base = self.get_image_base(binary_name)?; - Ok(address - image_base + base_address) + fn va_to_mem_address(&self, binary_name: &str, address: u64) -> Result { + let base_address = self.get_module_base_address(binary_name)?; + let image_base = self.get_image_base(binary_name)?; + Ok(address - image_base + base_address) } pub fn find_vtable_va(&self, binary_name: &str, vtable_name: &str) -> Result { - self.find_vtable_mangled_va(binary_name, &self.decorate_rtti_type_descriptor_name(vtable_name)) + self.find_vtable_mangled_va( + binary_name, + &self.decorate_rtti_type_descriptor_name(vtable_name), + ) } - pub fn find_vtable_nested_2_va(&self, binary_name: &str, class1_name: &str, class2_name: &str) -> Result { - self.find_vtable_mangled_va(binary_name, &self.decorate_rtti_type_descriptor_name_nested_2(class1_name, class2_name)) + pub fn find_vtable_nested_2_va( + &self, + binary_name: &str, + class1_name: &str, + class2_name: &str, + ) -> Result { + self.find_vtable_mangled_va( + binary_name, + &self.decorate_rtti_type_descriptor_name_nested_2(class1_name, class2_name), + ) } pub fn find_vtable_mangled_va(&self, binary_name: &str, vtable_name: &str) -> Result { - match self.os.as_str() { - "windows" => self.find_vtable_va_windows(binary_name, vtable_name), - _ => self.find_vtable_va_linux(binary_name, vtable_name), - } + match self.os.as_str() { + "windows" => self.find_vtable_va_windows(binary_name, vtable_name), + _ => self.find_vtable_va_linux(binary_name, vtable_name), + } } - pub fn get_vtable_vfunc_count(&self, binary_name: &str, vtable_name: &str) -> Result { - let vtable_va = self.find_vtable_va(binary_name, vtable_name)?; - self.get_vtable_vfunc_count_by_va(binary_name, vtable_va) + let vtable_va = self.find_vtable_va(binary_name, vtable_name)?; + self.get_vtable_vfunc_count_by_va(binary_name, vtable_va) } pub fn get_vtable_vfunc_count_by_va(&self, binary_name: &str, vtable_va: u64) -> Result { - let mut offset = 0; + let mut offset = 0; - loop { - let vfunc_va = self.read_by_va(binary_name, vtable_va + offset, 8)?; - let vfunc_va = u64::from_le_bytes(vfunc_va.try_into().unwrap()); + loop { + let vfunc_va = self.read_by_va(binary_name, vtable_va + offset, 8)?; + let vfunc_va = u64::from_le_bytes(vfunc_va.try_into().unwrap()); - if vfunc_va == 0 || !self.is_valid_executable_va(binary_name, vfunc_va)? { - break; - } + if vfunc_va == 0 || !self.is_valid_executable_va(binary_name, vfunc_va)? { + break; + } - // check if its a valid function - offset += 8; - } - Ok(offset as usize / 8) + // check if its a valid function + offset += 8; + } + Ok(offset as usize / 8) } pub fn find_vtable(&self, binary_name: &str, vtable_name: &str) -> Result { - let result = self.find_vtable_va(binary_name, vtable_name)?; - Ok(self.va_to_mem_address(binary_name, result)?) + let result = self.find_vtable_va(binary_name, vtable_name)?; + Ok(self.va_to_mem_address(binary_name, result)?) } pub fn find_vtable_mangled(&self, binary_name: &str, vtable_name: &str) -> Result { - let result = self.find_vtable_mangled_va(binary_name, vtable_name)?; - Ok(self.va_to_mem_address(binary_name, result)?) + let result = self.find_vtable_mangled_va(binary_name, vtable_name)?; + Ok(self.va_to_mem_address(binary_name, result)?) } - pub fn find_vtable_nested_2(&self, binary_name: &str, class1_name: &str, class2_name: &str) -> Result { - let result = self.find_vtable_nested_2_va(binary_name, class1_name, class2_name)?; - Ok(self.va_to_mem_address(binary_name, result)?) + pub fn find_vtable_nested_2( + &self, + binary_name: &str, + class1_name: &str, + class2_name: &str, + ) -> Result { + let result = self.find_vtable_nested_2_va(binary_name, class1_name, class2_name)?; + Ok(self.va_to_mem_address(binary_name, result)?) } pub fn pattern_scan_va(&self, binary_name: &str, pattern_string: &str) -> Result { - self.find_pattern_va(binary_name, pattern_string) + self.find_pattern_va(binary_name, pattern_string) } pub fn pattern_scan(&self, binary_name: &str, pattern_string: &str) -> Result { - let result = self.find_pattern_va(binary_name, pattern_string)?; - Ok(self.va_to_mem_address(binary_name, result)?) - } - - pub fn pattern_scan_all_va(&self, binary_name: &str, pattern_string: &str, callback: impl Fn(usize, u64) -> bool) -> Result<()> { - let binary_data = self.get_binary(binary_name)?; - let pattern = pattern_string.split(" ").map(|x| if x == "?" { 0u8 } else { u8::from_str_radix(x, 16).unwrap() }).collect::>(); - let pattern_wildcard = pattern_string.split(" ").enumerate().filter(|(_, x)| *x == "?").map(|(index, _)| index).collect::>(); - - let mut offset = 0; - let mut match_index = 0; - while offset + pattern.len() < binary_data.len() { - let result = find_pattern_simd(&binary_data[offset..], &pattern, &pattern_wildcard)?; - - if result == 0 { - return Ok(()); - } + let result = self.find_pattern_va(binary_name, pattern_string)?; + Ok(self.va_to_mem_address(binary_name, result)?) + } + + pub fn pattern_scan_all_va( + &self, + binary_name: &str, + pattern_string: &str, + callback: impl Fn(usize, u64) -> bool, + ) -> Result<()> { + let binary_data = self.get_binary(binary_name)?; + let pattern = pattern_string + .split(" ") + .map(|x| { + if x == "?" { + 0u8 + } else { + u8::from_str_radix(x, 16).unwrap() + } + }) + .collect::>(); + let pattern_wildcard = pattern_string + .split(" ") + .enumerate() + .filter(|(_, x)| *x == "?") + .map(|(index, _)| index) + .collect::>(); + + let mut offset = 0; + let mut match_index = 0; + while offset + pattern.len() < binary_data.len() { + let result = find_pattern_simd(&binary_data[offset..], &pattern, &pattern_wildcard)?; + + if result == 0 { + return Ok(()); + } - offset += result as usize; + offset += result as usize; - if callback(match_index, self.file_offset_to_va(binary_name, offset as u64)?) { - return Ok(()); + if callback( + match_index, + self.file_offset_to_va(binary_name, offset as u64)?, + ) { + return Ok(()); + } + + match_index += 1; + offset += 1; } - - match_index += 1; - offset += 1; - } - Err(anyhow::anyhow!("Pattern not found.")) + Err(anyhow::anyhow!("Pattern not found.")) } - pub fn pattern_scan_all(&self, binary_name: &str, pattern_string: &str, callback: impl Fn(usize, u64) -> bool) -> Result<()> { - // pre check error - let _ = self.get_module_base_address(binary_name)?; - let _ = self.get_image_base(binary_name)?; + pub fn pattern_scan_all( + &self, + binary_name: &str, + pattern_string: &str, + callback: impl Fn(usize, u64) -> bool, + ) -> Result<()> { + // pre check error + let _ = self.get_module_base_address(binary_name)?; + let _ = self.get_image_base(binary_name)?; - self.pattern_scan_all_va(binary_name, pattern_string, |index, x | callback(index, self.va_to_mem_address(binary_name, x).unwrap()) ) + self.pattern_scan_all_va(binary_name, pattern_string, |index, x| { + callback(index, self.va_to_mem_address(binary_name, x).unwrap()) + }) } - pub fn find_export_va(&self, binary_name: &str, export_name: &str) -> Result { - let binary_data = self.get_binary(binary_name)?; - let object = object::File::parse(binary_data)?; + let binary_data = self.get_binary(binary_name)?; + let object = object::File::parse(binary_data)?; - for export in object.exports()? { - if String::from_utf8_lossy(export.name()) == export_name { - return Ok(export.address() as u64) + for export in object.exports()? { + if String::from_utf8_lossy(export.name()) == export_name { + return Ok(export.address() as u64); + } } - } - Err(anyhow::anyhow!("Export not found.")) + Err(anyhow::anyhow!("Export not found.")) } pub fn find_export(&self, binary_name: &str, export_name: &str) -> Result { - let result = self.find_export_va(binary_name, export_name)?; - Ok(self.mem_address_to_va(binary_name, result)?) + let result = self.find_export_va(binary_name, export_name)?; + Ok(self.mem_address_to_va(binary_name, result)?) } pub fn find_symbol_va(&self, binary_name: &str, symbol_name: &str) -> Result { - let binary_data = self.get_binary(binary_name)?; - let object = object::File::parse(binary_data)?; + let binary_data = self.get_binary(binary_name)?; + let object = object::File::parse(binary_data)?; - let symbol = object.dynamic_symbols().into_iter().find(|s| s.name() == Ok(symbol_name)).ok_or_else(|| anyhow::anyhow!("Symbol not found."))?; - Ok(symbol.address() as u64) + let symbol = object + .dynamic_symbols() + .into_iter() + .find(|s| s.name() == Ok(symbol_name)) + .ok_or_else(|| anyhow::anyhow!("Symbol not found."))?; + Ok(symbol.address() as u64) } pub fn find_symbol(&self, binary_name: &str, symbol_name: &str) -> Result { - let result = self.find_symbol_va(binary_name, symbol_name)?; - Ok(self.va_to_mem_address(binary_name, result)?) + let result = self.find_symbol_va(binary_name, symbol_name)?; + Ok(self.va_to_mem_address(binary_name, result)?) } - pub fn read_by_file_offset(&self, binary_name: &str, file_offset: u64, size: usize) -> Result> { - let binary_data: &[u8] = self.get_binary(binary_name)?; - Ok(binary_data[file_offset as usize..file_offset as usize + size].to_vec()) + pub fn read_by_file_offset( + &self, + binary_name: &str, + file_offset: u64, + size: usize, + ) -> Result> { + let binary_data: &[u8] = self.get_binary(binary_name)?; + Ok(binary_data[file_offset as usize..file_offset as usize + size].to_vec()) } pub fn read_by_va(&self, binary_name: &str, address: u64, size: usize) -> Result> { - let file_offset = self.va_to_file_offset(binary_name, address)?; - self.read_by_file_offset(binary_name, file_offset, size) + let file_offset = self.va_to_file_offset(binary_name, address)?; + self.read_by_file_offset(binary_name, file_offset, size) } - pub fn read_by_mem_address(&self, binary_name: &str, address: u64, size: usize) -> Result> { - let va = self.mem_address_to_va(binary_name, address)?; - self.read_by_va(binary_name, va, size) + pub fn read_by_mem_address( + &self, + binary_name: &str, + address: u64, + size: usize, + ) -> Result> { + let va = self.mem_address_to_va(binary_name, address)?; + self.read_by_va(binary_name, va, size) } - pub fn find_vfunc_by_vtbname_va(&self, binary_name: &str, vtb_name: &str, vfunc_index: usize) -> Result { - let vtb = self.find_vtable_va(binary_name, vtb_name)?; + pub fn find_vfunc_by_vtbname_va( + &self, + binary_name: &str, + vtb_name: &str, + vfunc_index: usize, + ) -> Result { + let vtb = self.find_vtable_va(binary_name, vtb_name)?; - let vfuncptr = self.read_by_va(binary_name, vtb + vfunc_index as u64 * 8, 8)?; - Ok(u64::from_le_bytes(vfuncptr.try_into().unwrap())) + let vfuncptr = self.read_by_va(binary_name, vtb + vfunc_index as u64 * 8, 8)?; + Ok(u64::from_le_bytes(vfuncptr.try_into().unwrap())) } - pub fn find_vfunc_by_vtbname(&self, binary_name: &str, vtb_name: &str, vfunc_index: usize) -> Result { - let vtb = self.find_vfunc_by_vtbname_va(binary_name, vtb_name, vfunc_index)?; - Ok(self.va_to_mem_address(binary_name, vtb)?) + pub fn find_vfunc_by_vtbname( + &self, + binary_name: &str, + vtb_name: &str, + vfunc_index: usize, + ) -> Result { + let vtb = self.find_vfunc_by_vtbname_va(binary_name, vtb_name, vfunc_index)?; + Ok(self.va_to_mem_address(binary_name, vtb)?) } pub fn find_vfunc_by_vtbptr_va(&self, vtb_ptr: u64, vfunc_index: usize) -> Result { - let binary_name = self.get_binary_name_by_ptr(vtb_ptr)?; - let vtb_va = self.mem_address_to_va(&binary_name, vtb_ptr)?; - let vfuncptr = self.read_by_va(&binary_name, vtb_va + vfunc_index as u64 * 8, 8)?; - Ok(u64::from_le_bytes(vfuncptr.try_into().unwrap())) + let binary_name = self.get_binary_name_by_ptr(vtb_ptr)?; + let vtb_va = self.mem_address_to_va(&binary_name, vtb_ptr)?; + let vfuncptr = self.read_by_va(&binary_name, vtb_va + vfunc_index as u64 * 8, 8)?; + Ok(u64::from_le_bytes(vfuncptr.try_into().unwrap())) } pub fn find_vfunc_by_vtbptr(&self, vtb_ptr: u64, vfunc_index: usize) -> Result { - let binary_name = self.get_binary_name_by_ptr(vtb_ptr)?; - let vtb_va = self.mem_address_to_va(&binary_name, vtb_ptr)?; - let vfuncptr = self.read_by_va(&binary_name, vtb_va + vfunc_index as u64 * 8, 8)?; - let vfunc_va = u64::from_le_bytes(vfuncptr.try_into().unwrap()); - Ok(self.va_to_mem_address(&binary_name, vfunc_va)?) + let binary_name = self.get_binary_name_by_ptr(vtb_ptr)?; + let vtb_va = self.mem_address_to_va(&binary_name, vtb_ptr)?; + let vfuncptr = self.read_by_va(&binary_name, vtb_va + vfunc_index as u64 * 8, 8)?; + let vfunc_va = u64::from_le_bytes(vfuncptr.try_into().unwrap()); + Ok(self.va_to_mem_address(&binary_name, vfunc_va)?) } pub fn find_string_va(&self, binary_name: &str, string: &str) -> Result { - let binary_data = self.get_binary(binary_name)?; - let string_bytes = string.as_bytes(); - let result = find_pattern_simd(binary_data, string_bytes, &vec![])?; - Ok(self.file_offset_to_va(binary_name, result)?) + let binary_data = self.get_binary(binary_name)?; + let string_bytes = string.as_bytes(); + let result = find_pattern_simd(binary_data, string_bytes, &vec![])?; + Ok(self.file_offset_to_va(binary_name, result)?) } - + pub fn find_string(&self, binary_name: &str, string: &str) -> Result { - let result = self.find_string_va(binary_name, string)?; - Ok(self.va_to_mem_address(binary_name, result)?) + let result = self.find_string_va(binary_name, string)?; + Ok(self.va_to_mem_address(binary_name, result)?) } /// Dump cross-references from all executable sections - /// + /// /// This function scans all executable sections in the binary, disassembles /// the instructions using iced-x86, and extracts cross-references (xrefs). /// The results are cached in the `xrefs_cache` HashMap. - /// + /// /// # Arguments /// * `binary_name` - The name of the binary to analyze - /// + /// /// # Returns /// Returns Ok(()) on success, or an error if the binary cannot be processed pub fn dump_xrefs(&mut self, binary_name: &str) -> Result<()> { let binary_data = self.get_binary(binary_name)?; let object = object::File::parse(binary_data)?; let image_base = self.get_image_base(binary_name)?; - + // Temporary storage for xrefs let mut xrefs_map: HashMap> = HashMap::new(); - + // Determine bitness for decoder let bitness = match object { object::File::Pe64(_) | object::File::Elf64(_) => 64, @@ -745,19 +923,15 @@ impl S2BinLib { let section_va = section.address(); // Create decoder - let mut decoder = Decoder::with_ip( - bitness, - section_data, - section_va, - DecoderOptions::NONE, - ); + let mut decoder = + Decoder::with_ip(bitness, section_data, section_va, DecoderOptions::NONE); let mut instruction = Instruction::default(); - + // Decode all instructions in the section while decoder.can_decode() { decoder.decode_out(&mut instruction); - + // Skip invalid instructions if instruction.is_invalid() { continue; @@ -768,7 +942,7 @@ impl S2BinLib { // Analyze instruction operands for memory references for i in 0..instruction.op_count() { let op_kind = instruction.op_kind(i); - + match op_kind { // Direct memory operand (e.g., mov rax, [0x140001000]) OpKind::Memory => { @@ -779,8 +953,9 @@ impl S2BinLib { .entry(target_va) .or_insert_with(Vec::new) .push(instr_va); - } else if instruction.memory_base() == Register::None - && instruction.memory_index() == Register::None { + } else if instruction.memory_base() == Register::None + && instruction.memory_index() == Register::None + { // Absolute addressing let displacement = instruction.memory_displacement64(); if displacement != 0 { @@ -791,7 +966,7 @@ impl S2BinLib { } } } - + // Near branch (call, jmp, jcc) OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64 => { let target_va = instruction.near_branch_target(); @@ -800,7 +975,7 @@ impl S2BinLib { .or_insert_with(Vec::new) .push(instr_va); } - + // Immediate values that might be addresses OpKind::Immediate32 | OpKind::Immediate64 => { let immediate = if bitness == 64 { @@ -808,7 +983,7 @@ impl S2BinLib { } else { instruction.immediate(i) as u32 as u64 }; - + // Only consider values that look like valid virtual addresses // For PE files, check if it's near the image base // For ELF files, check if it's in a reasonable range @@ -817,7 +992,7 @@ impl S2BinLib { } else { immediate >= image_base && immediate < image_base + 0x1000000 }; - + if is_likely_address { xrefs_map .entry(immediate) @@ -825,7 +1000,7 @@ impl S2BinLib { .push(instr_va); } } - + _ => {} } } @@ -839,14 +1014,14 @@ impl S2BinLib { } /// Get cached cross-references for a target virtual address - /// + /// /// Returns None if the binary hasn't been analyzed with `dump_xrefs` yet, /// or if there are no references to the target address. - /// + /// /// # Arguments /// * `binary_name` - The name of the binary /// * `target_va` - The target virtual address to find references to - /// + /// /// # Returns /// An optional reference to a vector of virtual addresses that reference the target pub fn find_xrefs_cached(&self, binary_name: &str, target_va: u64) -> Option<&Vec> { @@ -856,40 +1031,38 @@ impl S2BinLib { } pub fn unload_binary(&mut self, binary_name: &str) { - self.binaries.remove(binary_name); + self.binaries.remove(binary_name); } pub fn unload_all_binaries(&mut self) { - self.binaries.clear(); + self.binaries.clear(); } pub fn install_trampoline(&mut self, mem_address: u64) -> Result { + if let Some(trampoline) = self.trampolines.get(&mem_address) { + return Ok(trampoline.address()); + } - if let Some(trampoline) = self.trampolines.get(&mem_address) { - return Ok(trampoline.address()); - } - - - let original_func_ptr = unsafe { std::ptr::read(mem_address as *const u64) }; + let original_func_ptr = unsafe { std::ptr::read(mem_address as *const u64) }; - let trampoline = JitTrampoline::new(original_func_ptr)?; + let trampoline = JitTrampoline::new(original_func_ptr)?; - set_mem_access(mem_address, 8)?; + set_mem_access(mem_address, 8)?; - unsafe { - std::ptr::write(mem_address as *mut u64, trampoline.address() ); - } + unsafe { + std::ptr::write(mem_address as *mut u64, trampoline.address()); + } - let address = trampoline.address(); - self.trampolines.insert(mem_address, trampoline); + let address = trampoline.address(); + self.trampolines.insert(mem_address, trampoline); - Ok(address) + Ok(address) } pub fn follow_xref_mem_to_mem(&self, mem_address: u64) -> Result { const MAX_INSTR_LEN: usize = 15; let mut instruction_bytes = [0u8; MAX_INSTR_LEN]; - + unsafe { std::ptr::copy_nonoverlapping( mem_address as *const u8, @@ -897,106 +1070,101 @@ impl S2BinLib { MAX_INSTR_LEN, ); } - - let mut decoder = Decoder::with_ip( - 64, - &instruction_bytes, - mem_address, - DecoderOptions::NONE, - ); - + + let mut decoder = + Decoder::with_ip(64, &instruction_bytes, mem_address, DecoderOptions::NONE); + let mut instruction = Instruction::default(); decoder.decode_out(&mut instruction); - + if instruction.is_invalid() { - return Err(anyhow::anyhow!("Invalid instruction at address 0x{:X}", mem_address)); + return Err(anyhow::anyhow!( + "Invalid instruction at address 0x{:X}", + mem_address + )); } - + for i in 0..instruction.op_count() { let op_kind = instruction.op_kind(i); - + match op_kind { OpKind::Memory => { if instruction.is_ip_rel_memory_operand() { let target_address = instruction.ip_rel_memory_address(); return Ok(target_address); - } else if instruction.memory_base() == Register::None - && instruction.memory_index() == Register::None { + } else if instruction.memory_base() == Register::None + && instruction.memory_index() == Register::None + { let displacement = instruction.memory_displacement64(); if displacement != 0 { return Ok(displacement); } } } - + OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64 => { let target_address = instruction.near_branch_target(); return Ok(target_address); } - - + _ => {} } } - + Err(anyhow::anyhow!( - "No valid xref found in instruction at address 0x{:X}", + "No valid xref found in instruction at address 0x{:X}", mem_address )) } pub fn follow_xref_va_to_mem(&self, binary_name: &str, va: u64) -> Result { - let mem_address = self.va_to_mem_address(binary_name, va)?; - self.follow_xref_mem_to_mem(mem_address) + let mem_address = self.va_to_mem_address(binary_name, va)?; + self.follow_xref_mem_to_mem(mem_address) } pub fn follow_xref_va_to_va(&self, binary_name: &str, va: u64) -> Result { let file_offset = self.va_to_file_offset(binary_name, va)?; let binary_data = self.get_binary(binary_name)?; - + const MAX_INSTR_LEN: usize = 15; - + if file_offset as usize + MAX_INSTR_LEN > binary_data.len() { return Err(anyhow::anyhow!( "Instruction at VA 0x{:X} extends beyond binary data", va )); } - - let instruction_bytes = &binary_data[file_offset as usize..file_offset as usize + MAX_INSTR_LEN]; - - - let mut decoder = Decoder::with_ip( - 64, - instruction_bytes, - va, - DecoderOptions::NONE, - ); - + + let instruction_bytes = + &binary_data[file_offset as usize..file_offset as usize + MAX_INSTR_LEN]; + + let mut decoder = Decoder::with_ip(64, instruction_bytes, va, DecoderOptions::NONE); + let mut instruction = Instruction::default(); decoder.decode_out(&mut instruction); - + if instruction.is_invalid() { return Err(anyhow::anyhow!("Invalid instruction at VA 0x{:X}", va)); } - + for i in 0..instruction.op_count() { let op_kind = instruction.op_kind(i); - + match op_kind { OpKind::Memory => { if instruction.is_ip_rel_memory_operand() { let target_address = instruction.ip_rel_memory_address(); return Ok(target_address); - } else if instruction.memory_base() == Register::None - && instruction.memory_index() == Register::None { + } else if instruction.memory_base() == Register::None + && instruction.memory_index() == Register::None + { let displacement = instruction.memory_displacement64(); if displacement != 0 { return Ok(displacement); } } } - + OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64 => { let target_address = instruction.near_branch_target(); return Ok(target_address); @@ -1005,15 +1173,14 @@ impl S2BinLib { _ => {} } } - + Err(anyhow::anyhow!( - "No valid xref found in instruction at VA 0x{:X}", + "No valid xref found in instruction at VA 0x{:X}", va )) } pub fn find_networkvar_vtable_statechanged_va(&self, vtable_va: u64) -> Result { - let vfunc_count = self.get_vtable_vfunc_count_by_va("server", vtable_va)?; for i in 0..vfunc_count { @@ -1035,14 +1202,12 @@ impl S2BinLib { } } } - } - Err(anyhow::anyhow!("NetworkVar_StateChanged not found")) + Err(anyhow::anyhow!("NetworkVar_StateChanged not found")) } pub fn find_networkvar_vtable_statechanged(&self, vtable_mem_address: u64) -> Result { - let vtable_va = self.mem_address_to_va("server", vtable_mem_address)?; - self.find_networkvar_vtable_statechanged_va(vtable_va) + let vtable_va = self.mem_address_to_va("server", vtable_mem_address)?; + self.find_networkvar_vtable_statechanged_va(vtable_va) } - -} \ No newline at end of file +}