From 5aec97f64c09f1e673d61190bb55780f5b1dd5f3 Mon Sep 17 00:00:00 2001 From: samyyc Date: Thu, 30 Oct 2025 00:34:06 +0800 Subject: [PATCH 1/2] More vtable methods --- Cargo.toml | 2 +- s2binlib.h | 126 ++++++++++++++++++ src/c_bindings.rs | 320 ++++++++++++++++++++++++++++++++++++++++++++++ src/s2binlib.rs | 45 +++++-- 4 files changed, 482 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9991da6..009b419 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [lib] -crate-type = ["staticlib", "dylib", "rlib"] +crate-type = ["staticlib"] [profile.release] opt-level = 3 diff --git a/s2binlib.h b/s2binlib.h index 126ad0b..fdb69ae 100644 --- a/s2binlib.h +++ b/s2binlib.h @@ -244,6 +244,132 @@ extern "C" */ 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 + * + * Searches for a vtable using the mangled/decorated RTTI name directly. + * Unlike s2binlib_find_vtable_va which auto-decorates the name, this function + * uses the provided name as-is. + * + * If the binary is not yet loaded, it will be loaded automatically. + * + * @param binary_name Name of the binary to search (null-terminated C string) + * @param vtable_name Mangled RTTI name to search for (null-terminated C string) + * - Windows: ".?AVClassName@@" format + * - Linux: "{length}ClassName" format + * @param result Pointer to store the resulting vtable virtual address + * + * @return 0 on success (address written to result) + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -3 if failed to load binary + * -4 if vtable not found + * -5 if internal error + * + * @example + * // Windows mangled name example + * void* vtable_va; + * int result = s2binlib_find_vtable_mangled_va("server", ".?AVCBaseEntity@@", &vtable_va); + * if (result == 0) { + * printf("VTable VA: %p\n", vtable_va); + * } + * + * // 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); + + /** + * Find a vtable by mangled name and return its runtime memory address + * + * Searches for a vtable using the mangled/decorated RTTI name directly and + * returns its runtime memory address. + * + * If the binary is not yet loaded, it will be loaded automatically. + * + * @param binary_name Name of the binary to search (null-terminated C string) + * @param vtable_name Mangled RTTI name to search for (null-terminated C string) + * - Windows: ".?AVClassName@@" format + * - Linux: "{length}ClassName" format + * @param result Pointer to store the resulting vtable memory address + * + * @return 0 on success (address written to result) + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -3 if failed to load binary + * -4 if vtable not found + * -5 if internal error + * + * @example + * void* vtable_addr; + * int result = s2binlib_find_vtable_mangled("server", ".?AVCBaseEntity@@", &vtable_addr); + * if (result == 0) { + * printf("VTable at: %p\n", vtable_addr); + * } + */ + 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 + * + * Searches for a vtable of a nested class (e.g., Class1::Class2). + * The function automatically decorates the names according to the platform's + * RTTI name mangling scheme: + * - Windows: ".?AVClass2@Class1@@" + * - Linux: "N{len1}Class1{len2}Class2E" + * + * If the binary is not yet loaded, it will be loaded automatically. + * + * @param binary_name Name of the binary to search (null-terminated C string) + * @param class1_name Outer class name (null-terminated C string) + * @param class2_name Inner/nested class name (null-terminated C string) + * @param result Pointer to store the resulting vtable virtual address + * + * @return 0 on success (address written to result) + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -3 if failed to load binary + * -4 if vtable not found + * -5 if internal error + * + * @example + * void* vtable_va; + * int result = s2binlib_find_vtable_nested_2_va("server", "CEntitySystem", "CEntitySubsystem", &vtable_va); + * if (result == 0) { + * 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); + + /** + * Find a nested vtable (2 levels) by class names and return its runtime memory address + * + * Searches for a vtable of a nested class (e.g., Class1::Class2) and returns + * its runtime memory address. + * + * If the binary is not yet loaded, it will be loaded automatically. + * + * @param binary_name Name of the binary to search (null-terminated C string) + * @param class1_name Outer class name (null-terminated C string) + * @param class2_name Inner/nested class name (null-terminated C string) + * @param result Pointer to store the resulting vtable memory address + * + * @return 0 on success (address written to result) + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -3 if failed to load binary + * -4 if vtable not found + * -5 if internal error + * + * @example + * void* vtable_addr; + * int result = s2binlib_find_vtable_nested_2("server", "CEntitySystem", "CEntitySubsystem", &vtable_addr); + * if (result == 0) { + * 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); + /** * Find a symbol by name in the specified binary * diff --git a/src/c_bindings.rs b/src/c_bindings.rs index 73feea1..02f2899 100644 --- a/src/c_bindings.rs +++ b/src/c_bindings.rs @@ -803,6 +803,326 @@ pub extern "C" fn s2binlib_find_vtable_va( } } +/// Find a vtable by mangled name and return its virtual address +/// +/// Searches for a vtable using the mangled/decorated RTTI name directly. +/// Unlike find_vtable_va which auto-decorates the name, this function uses +/// the provided name as-is. +/// +/// If the binary is not yet loaded, it will be loaded automatically. +/// +/// # Parameters +/// * `binary_name` - Name of the binary to search (null-terminated C string) +/// * `vtable_name` - Mangled RTTI name to search for (null-terminated C string) +/// * `result` - Pointer to store the resulting vtable virtual address +/// +/// # Returns +/// * 0 on success (address written to result) +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -3 if failed to load binary +/// * -4 if vtable not found +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// void* vtable_va; +/// int result = s2binlib_find_vtable_mangled_va("server", ".?AVCBaseEntity@@", &vtable_va); +/// if (result == 0) { +/// printf("VTable VA: %p\n", vtable_va); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_find_vtable_mangled_va( + binary_name: *const c_char, + vtable_name: *const c_char, + result: *mut *mut c_void, +) -> i32 { + unsafe { + if binary_name.is_null() || vtable_name.is_null() || result.is_null() { + return -2; + } + + let binary_name_str = match CStr::from_ptr(binary_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let mut s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_mut() { + Some(lib) => lib, + None => return -1, + }; + + if !s2binlib.is_binary_loaded(binary_name_str) { + s2binlib.load_binary(binary_name_str); + } + + match s2binlib.find_vtable_mangled_va(binary_name_str, vtable_name_str) { + Ok(addr) => { + *result = addr as *mut c_void; + 0 + } + Err(_) => -4, + } + } +} + +/// Find a vtable by mangled name and return its runtime memory address +/// +/// Searches for a vtable using the mangled/decorated RTTI name directly and +/// returns its runtime memory address. +/// +/// If the binary is not yet loaded, it will be loaded automatically. +/// +/// # Parameters +/// * `binary_name` - Name of the binary to search (null-terminated C string) +/// * `vtable_name` - Mangled RTTI name to search for (null-terminated C string) +/// * `result` - Pointer to store the resulting vtable memory address +/// +/// # Returns +/// * 0 on success (address written to result) +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -3 if failed to load binary +/// * -4 if vtable not found +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// void* vtable_addr; +/// int result = s2binlib_find_vtable_mangled("server", ".?AVCBaseEntity@@", &vtable_addr); +/// if (result == 0) { +/// printf("VTable at: %p\n", vtable_addr); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_find_vtable_mangled( + binary_name: *const c_char, + vtable_name: *const c_char, + result: *mut *mut c_void, +) -> i32 { + unsafe { + if binary_name.is_null() || vtable_name.is_null() || result.is_null() { + return -2; + } + + let binary_name_str = match CStr::from_ptr(binary_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let vtable_name_str = match CStr::from_ptr(vtable_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let mut s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_mut() { + Some(lib) => lib, + None => return -1, + }; + + if !s2binlib.is_binary_loaded(binary_name_str) { + s2binlib.load_binary(binary_name_str); + } + + match s2binlib.find_vtable_mangled(binary_name_str, vtable_name_str) { + Ok(addr) => { + *result = addr as *mut c_void; + 0 + } + Err(_) => -4, + } + } +} + +/// Find a nested vtable (2 levels) by class names and return its virtual address +/// +/// Searches for a vtable of a nested class (e.g., Class1::Class2). +/// The function automatically decorates the names according to the platform's +/// RTTI name mangling scheme. +/// +/// If the binary is not yet loaded, it will be loaded automatically. +/// +/// # Parameters +/// * `binary_name` - Name of the binary to search (null-terminated C string) +/// * `class1_name` - Outer class name (null-terminated C string) +/// * `class2_name` - Inner/nested class name (null-terminated C string) +/// * `result` - Pointer to store the resulting vtable virtual address +/// +/// # Returns +/// * 0 on success (address written to result) +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -3 if failed to load binary +/// * -4 if vtable not found +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// void* vtable_va; +/// int result = s2binlib_find_vtable_nested_2_va("server", "CEntitySystem", "CEntitySubsystem", &vtable_va); +/// if (result == 0) { +/// printf("Nested VTable VA: %p\n", vtable_va); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_find_vtable_nested_2_va( + binary_name: *const c_char, + class1_name: *const c_char, + class2_name: *const c_char, + result: *mut *mut c_void, +) -> i32 { + unsafe { + if binary_name.is_null() || class1_name.is_null() || class2_name.is_null() || result.is_null() { + return -2; + } + + let binary_name_str = match CStr::from_ptr(binary_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let class1_name_str = match CStr::from_ptr(class1_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let class2_name_str = match CStr::from_ptr(class2_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let mut s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_mut() { + Some(lib) => lib, + None => return -1, + }; + + if !s2binlib.is_binary_loaded(binary_name_str) { + s2binlib.load_binary(binary_name_str); + } + + match s2binlib.find_vtable_nested_2_va(binary_name_str, class1_name_str, class2_name_str) { + Ok(addr) => { + *result = addr as *mut c_void; + 0 + } + Err(_) => -4, + } + } +} + +/// Find a nested vtable (2 levels) by class names and return its runtime memory address +/// +/// Searches for a vtable of a nested class (e.g., Class1::Class2) and returns +/// its runtime memory address. +/// +/// If the binary is not yet loaded, it will be loaded automatically. +/// +/// # Parameters +/// * `binary_name` - Name of the binary to search (null-terminated C string) +/// * `class1_name` - Outer class name (null-terminated C string) +/// * `class2_name` - Inner/nested class name (null-terminated C string) +/// * `result` - Pointer to store the resulting vtable memory address +/// +/// # Returns +/// * 0 on success (address written to result) +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -3 if failed to load binary +/// * -4 if vtable not found +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// void* vtable_addr; +/// int result = s2binlib_find_vtable_nested_2("server", "CEntitySystem", "CEntitySubsystem", &vtable_addr); +/// if (result == 0) { +/// printf("Nested VTable at: %p\n", vtable_addr); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_find_vtable_nested_2( + binary_name: *const c_char, + class1_name: *const c_char, + class2_name: *const c_char, + result: *mut *mut c_void, +) -> i32 { + unsafe { + if binary_name.is_null() || class1_name.is_null() || class2_name.is_null() || result.is_null() { + return -2; + } + + let binary_name_str = match CStr::from_ptr(binary_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let class1_name_str = match CStr::from_ptr(class1_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let class2_name_str = match CStr::from_ptr(class2_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let mut s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_mut() { + Some(lib) => lib, + None => return -1, + }; + + if !s2binlib.is_binary_loaded(binary_name_str) { + s2binlib.load_binary(binary_name_str); + } + + match s2binlib.find_vtable_nested_2(binary_name_str, class1_name_str, class2_name_str) { + Ok(addr) => { + *result = addr as *mut c_void; + 0 + } + Err(_) => -4, + } + } +} + /// Get the number of virtual functions in a vtable /// /// Returns the count of virtual functions (vfuncs) in the specified vtable. diff --git a/src/s2binlib.rs b/src/s2binlib.rs index 4035f7b..540b11f 100644 --- a/src/s2binlib.rs +++ b/src/s2binlib.rs @@ -126,6 +126,14 @@ impl S2BinLib { _ => 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), + } + } + pub fn new(game_path: &str, game_type: &str, os: &str) -> Self { Self { game_path: PathBuf::from(game_path), @@ -363,9 +371,8 @@ impl S2BinLib { fn find_vtable_va_windows(&self, binary_name: &str, vtable_name: &str) -> Result { let binary_data = self.get_binary(binary_name)?; - let decorated_name = self.decorate_rtti_type_descriptor_name(vtable_name); - let type_descriptor_name = self.find_pattern_string_in_section(binary_name, ".data", &decorated_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)?; @@ -393,23 +400,22 @@ impl S2BinLib { fn find_vtable_va_linux(&self, binary_name: &str, vtable_name: &str) -> Result { let binary_data = self.get_binary(binary_name)?; - let decorated_name = self.decorate_rtti_type_descriptor_name(vtable_name); let data_range = self.get_section_range(binary_name, ".rodata")?; let offset = data_range.0; - let mut type_info_name = find_pattern_simd(&binary_data[offset as usize..], &decorated_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)?; - if type_info_name_str == decorated_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..], &decorated_name.as_bytes(), &vec![])?; + 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; } @@ -496,12 +502,21 @@ impl S2BinLib { } pub fn find_vtable_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), - } + 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_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), + } } + pub fn get_vtable_vfunc_count(&self, binary_name: &str, vtable_name: &str) -> Result { let vtable = self.find_vtable_va(binary_name, vtable_name)?; let mut offset = 0; @@ -525,6 +540,16 @@ impl S2BinLib { 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)?) + } + + 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) } From ab5b9c45890746eb5ee19bcfbe8e02b9daaa360a Mon Sep 17 00:00:00 2001 From: samyyc Date: Thu, 30 Oct 2025 00:40:22 +0800 Subject: [PATCH 2/2] Add dylib --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 009b419..88a868c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [lib] -crate-type = ["staticlib"] +crate-type = ["staticlib", "dylib"] [profile.release] opt-level = 3