diff --git a/.vscode/settings.json b/.vscode/settings.json index 451605c..758e290 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { "rust-analyzer.linkedProjects": [ ".\\src\\macro\\Cargo.toml", - ".\\Cargo.toml" + ".\\Cargo.toml", + "src/macro/Cargo.toml", + "Cargo.toml", ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a8a5757..dbb72e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "checksum" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5c24f6a463e9973db3df3c2cc276f689f5baf289c87a693dc859e004d3eb45f" + [[package]] name = "chrono" version = "0.4.26" @@ -191,6 +197,29 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "dfint_hook" +version = "0.1.0" +dependencies = [ + "backtrace", + "checksum", + "chrono", + "dlopen2", + "encoding_rs", + "encoding_rs_io", + "exe", + "log", + "macro", + "regex", + "retour", + "serde", + "serde_derive", + "simple-logging", + "static_init", + "toml", + "winapi", +] + [[package]] name = "digest" version = "0.9.0" @@ -200,6 +229,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dlopen2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -270,27 +322,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hook" -version = "0.1.0" -dependencies = [ - "backtrace", - "chrono", - "encoding_rs", - "encoding_rs_io", - "exe", - "log", - "macro", - "regex", - "retour", - "serde", - "serde_derive", - "simple-logging", - "static_init", - "toml", - "winapi", -] - [[package]] name = "iana-time-zone" version = "0.1.56" @@ -351,9 +382,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libudis86-sys" @@ -404,7 +435,7 @@ name = "macro" version = "0.1.0" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 925faf4..3922389 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hook" +name = "dfint_hook" version = "0.1.0" edition = "2021" @@ -13,20 +13,26 @@ macro = { path = "src/macro" } retour = { version = "0.3.1", features = ["static-detour"] } log = "0.4.20" simple-logging = "2.0.2" -winapi = { version = "^0.3.9", features = [ - "minwindef", - "winnt", - "libloaderapi", - "windef", - "winuser", -] } encoding_rs = "*" encoding_rs_io = "*" regex = "*" toml = "0.8.0" -exe = "0.5.6" backtrace = "0.3.69" chrono = "0.4.26" serde_derive = "1.0.164" serde = "1.0.185" static_init = "1.0.3" +dlopen2 = "0.6.1" + +[target.'cfg(target_os = "windows")'.dependencies] +exe = "0.5.6" +winapi = { version = "^0.3.9", features = [ + "minwindef", + "winnt", + "libloaderapi", + "windef", + "winuser", +] } + +[target.'cfg(target_os = "linux")'.dependencies] +checksum = "0.2.1" diff --git a/README.md b/README.md index ee3a138..ac3689f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ # df-steam-hook-rs + [![Build](https://github.com/dfint/df-steam-hook-rs/actions/workflows/build.yml/badge.svg)](https://github.com/dfint/df-steam-hook-rs/actions/workflows/build.yml) -Re-implementation of [df-steam-hook](https://github.com/dfint/df-steam-hook) in Rust. +Re-implementation of [df-steam-hook](https://github.com/dfint/df-steam-hook) in +Rust. + +Supports: + +- Windows (classic/steam/itch.io) +- Linux (classic/steam/itch.io) Implemented: + - config/offsets files -- crash reports - dictionary from csv - translation hooks - enter string hooks (search) diff --git a/rustfmt.toml b/rustfmt.toml index 2e38381..bafe2b7 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,3 @@ tab_spaces = 2 -chain_width = 100 +chain_width = 120 max_width = 120 diff --git a/src/config.rs b/src/config.rs index a204e5b..a4fe299 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,22 +2,25 @@ use std::error::Error; use std::path::Path; use crate::utils; -use exe::{VecPE, PE}; -use static_init::dynamic; -static EXE_FILE: &str = "./Dwarf Fortress.exe"; -static CONFIG_FILE: &str = "./dfint_data/dfint_config.toml"; -static OFFSETS_DIR: &str = "./dfint_data/offsets/"; +#[cfg(target_os = "windows")] +const EXE_FILE: &'static str = "./Dwarf Fortress.exe"; -#[dynamic] -pub static CONFIG: Config = Config::new().unwrap(); +#[cfg(target_os = "linux")] +const EXE_FILE: &'static str = "./dwarfort"; + +const CONFIG_FILE: &'static str = "./dfint_data/dfint_config.toml"; +const OFFSETS_DIR: &'static str = "./dfint_data/offsets/"; +#[static_init::dynamic] +pub static CONFIG: Config = Config::new().unwrap(); pub struct Config { pub metadata: ConfigMetadata, pub settings: Settings, pub offset: OffsetsValues, pub offset_metadata: OffsetsMetadata, + pub symbol: Option, pub hook_version: String, } @@ -49,6 +52,7 @@ pub struct Settings { pub struct Offsets { pub metadata: OffsetsMetadata, pub offsets: OffsetsValues, + pub symbols: Option, } #[derive(Deserialize)] @@ -60,62 +64,72 @@ pub struct OffsetsMetadata { #[derive(Deserialize)] pub struct OffsetsValues { - pub string_copy: usize, - pub string_copy_n: usize, - pub string_append: usize, - pub string_append_0: usize, - pub string_append_n: usize, - pub convert_ulong_to_string: usize, - pub addst: usize, - pub addst_top: usize, - pub addcoloredst: usize, - pub addst_flag: usize, - pub addst_template: Option, - pub standardstringentry: usize, - pub simplify_string: usize, - pub upper_case_string: usize, - pub lower_case_string: usize, - pub capitalize_string_words: usize, - pub capitalize_string_first_word: usize, - pub addchar: usize, - pub addchar_top: usize, - pub add_texture: usize, - pub gps_allocate: usize, - pub cleanup_arrays: usize, - pub screen_to_texid: usize, - pub screen_to_texid_top: usize, - pub loading_world_new_game_loop: usize, - pub loading_world_continuing_game_loop: usize, - pub loading_world_start_new_game_loop: usize, - pub menu_interface_loop: usize, - pub keybinding: Option, + pub string_copy_n: Option, + pub string_append_n: Option, + pub addst: Option, + pub addst_top: Option, + pub addst_flag: Option, + pub standardstringentry: Option, + pub simplify_string: Option, + pub upper_case_string: Option, + pub lower_case_string: Option, + pub capitalize_string_words: Option, + pub capitalize_string_first_word: Option, + pub utf_input: Option, +} + +#[derive(Deserialize)] +pub struct SymbolsValues { + pub addst: Option>, + pub addst_top: Option>, + pub addst_flag: Option>, + pub standardstringentry: Option>, + pub simplify_string: Option>, + pub upper_case_string: Option>, + pub lower_case_string: Option>, + pub capitalize_string_words: Option>, + pub capitalize_string_first_word: Option>, + pub std_string_append: Option>, + pub enabler: Option>, } impl Config { pub fn new() -> Result> { - let pe_timestamp = Self::pe_timestamp(Path::new(EXE_FILE))?; + let checksum = Self::checksum(Path::new(EXE_FILE))?; let main_config = Self::parse_toml::(Path::new(CONFIG_FILE))?; - match Self::walk_offsets(Path::new(OFFSETS_DIR), pe_timestamp) { + match Self::walk_offsets(Path::new(OFFSETS_DIR), checksum) { Ok(offsets) => Ok(Self { metadata: main_config.metadata, settings: main_config.settings, offset: offsets.offsets, offset_metadata: offsets.metadata, + symbol: offsets.symbols, hook_version: match option_env!("HOOK_VERSION") { Some(version) => String::from(version), None => String::from("not-defined"), }, }), - Err(_) => Err("Config Error".into()), + Err(e) => Err(format!("Config error {:?}", e).into()), } } - fn pe_timestamp(path: &Path) -> Result> { + #[cfg(target_os = "windows")] + fn checksum(path: &Path) -> Result> { + use exe::{VecPE, PE}; let pefile = VecPE::from_disk_file(path)?; Ok(pefile.get_nt_headers_64()?.file_header.time_date_stamp) } - fn walk_offsets(path: &Path, target_timestamp: u32) -> Result> { + #[cfg(target_os = "linux")] + fn checksum(path: &Path) -> Result> { + let mut crc = checksum::crc::Crc::new(path.to_str().unwrap()); + match crc.checksum() { + Ok(checksum) => Ok(checksum.crc32), + Err(e) => Err(format!("Checksum error {:?}", e).into()), + } + } + + fn walk_offsets(path: &Path, target_checksum: u32) -> Result> { for entry in std::fs::read_dir(path)? { let entry = entry?; let pentry = entry.path(); @@ -123,24 +137,22 @@ impl Config { continue; } let offsets = Self::parse_toml::(&pentry)?; - if offsets.metadata.checksum == target_timestamp { + if offsets.metadata.checksum == target_checksum { return Ok(offsets); } } - unsafe { - utils::message_box( - format!( - "unable to find offsets file for current version of DF\nchecksum: 0x{:x}", - target_timestamp - ) - .as_str(), - "dfint hook error", - utils::MessageIconType::Error, - ); - } + utils::message_box( + format!( + "unable to find offsets file for current version of DF\nchecksum: 0x{:x}", + target_checksum + ) + .as_str(), + "dfint hook error", + utils::MessageIconType::Error, + ); + std::process::exit(2); - // Err("Unable to find offsets file".into()) } fn parse_toml serde::Deserialize<'de>>(path: &Path) -> Result> { diff --git a/src/cxxset.rs b/src/cxxset.rs index f8d8a0f..42ee8da 100644 --- a/src/cxxset.rs +++ b/src/cxxset.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use std::alloc::{dealloc, Layout}; #[repr(C)] diff --git a/src/cxxstring.rs b/src/cxxstring.rs index bb18d62..55144ee 100644 --- a/src/cxxstring.rs +++ b/src/cxxstring.rs @@ -1,6 +1,151 @@ +#![allow(dead_code)] + use std::alloc::{alloc_zeroed, realloc, Layout}; use std::ops::{Index, IndexMut}; +#[cfg(target_os = "linux")] +#[repr(C)] +pub struct CxxString { + pub ptr: *mut u8, + pub len: usize, + pub sso: CxxSSO, +} + +#[cfg(target_os = "linux")] +#[repr(C)] +pub union CxxSSO { + pub capa: usize, + pub buf: [u8; 16], +} + +#[cfg(target_os = "linux")] +impl CxxString { + pub unsafe fn new(ptr: *mut T, size: usize) -> Self { + if size >= 16 { + return Self { + ptr: ptr as *mut u8, + len: size, + sso: CxxSSO { capa: size }, + }; + } + let array_ptr: *const [u8; 16] = ptr as *const [u8; 16]; + Self { + ptr: ptr as *mut u8, + len: size, + sso: CxxSSO { + buf: std::mem::transmute(*array_ptr), + }, + } + } + + // TODO: maybe wrong, not tested + pub unsafe fn resize(&mut self, size: usize) { + if size > self.len { + match (size >= 16, self.len) { + (true, v) if v < 16 => { + let new_array = alloc_zeroed(Layout::array::(32).unwrap()); + std::ptr::copy_nonoverlapping(self.sso.buf.as_ptr(), new_array, 16); + self.ptr = new_array; + self.sso.capa = 32; + } + (true, v) if v >= 16 && size > self.sso.capa => { + self.ptr = realloc( + self.ptr, + Layout::array::(self.sso.capa).unwrap(), + self.sso.capa + 16, + ); + self.sso.capa += 16; + } + (_, _) => (), + } + } else { + match (size >= 16, self.len) { + (true, _) => { + let target = self.ptr as usize + size; + let slice = std::slice::from_raw_parts_mut(target as *mut u8, 1); + slice[0] = 0; + } + (false, v) if v >= 16 => { + std::ptr::copy_nonoverlapping(self.ptr, self.sso.buf.as_mut_ptr(), 16); + } + (_, _) => {} + } + } + self.len = size; + } + + pub unsafe fn from_ptr(ptr: *const u8) -> &'static mut Self { + std::mem::transmute(ptr) + } + + pub unsafe fn as_ptr(&mut self) -> *const u8 { + std::mem::transmute(self) + } + + pub unsafe fn as_mut_ptr(&mut self) -> *mut u8 { + std::mem::transmute(self) + } + + pub unsafe fn to_str(&mut self) -> Result<&'static str, Box> { + match std::ffi::CStr::from_bytes_with_nul(std::slice::from_raw_parts(self.ptr, self.len + 1)) { + Ok(value) => match value.to_str() { + Ok(value) => Ok(value), + Err(err) => Err(err.into()), + }, + Err(err) => Err(err.into()), + } + } + + pub fn size(&self) -> usize { + self.len + } + + pub unsafe fn pop_back(&mut self) { + let index = self.len; + self[index] = 0; + self.resize(self.len - 1); + } + + pub unsafe fn push_back(&mut self, symbol: u8) { + let index = self.len; + self.resize(index + 1); + self[index] = symbol; + } +} + +#[cfg(target_os = "linux")] +impl Index for CxxString { + type Output = u8; + + fn index(&self, index: usize) -> &Self::Output { + unsafe { + let mut data: *const u8 = self.sso.buf.as_ptr(); + if self.len >= 16 { + data = self.ptr; + } + let target = data as usize + index; + let slice = std::slice::from_raw_parts(target as *const u8, 1); + &slice[0] + } + } +} + +#[cfg(target_os = "linux")] +impl IndexMut for CxxString { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + unsafe { + let mut data: *mut u8 = self.sso.buf.as_mut_ptr(); + if self.len >= 16 { + data = self.ptr; + } + let target = data as usize + index; + let slice = std::slice::from_raw_parts_mut(target as *mut u8, 1); + &mut slice[0] + } + } +} + +#[cfg(target_os = "windows")] #[repr(C)] pub struct CxxString { pub data: CxxStringContent, @@ -8,17 +153,19 @@ pub struct CxxString { pub capa: usize, } +#[cfg(target_os = "windows")] #[repr(C)] pub union CxxStringContent { pub buf: [u8; 16], pub ptr: *mut u8, } +#[cfg(target_os = "windows")] impl CxxString { - pub unsafe fn new(ptr: *mut u8, size: usize) -> Self { + pub unsafe fn new(ptr: *mut T, size: usize) -> Self { if size >= 16 { return Self { - data: CxxStringContent { ptr }, + data: CxxStringContent { ptr: ptr as *mut u8 }, len: size, capa: size, }; @@ -43,11 +190,7 @@ impl CxxString { self.capa = 32; } (true, v) if v < size => { - self.data.ptr = realloc( - self.data.ptr, - Layout::array::(self.capa).unwrap(), - self.capa + 16, - ); + self.data.ptr = realloc(self.data.ptr, Layout::array::(self.capa).unwrap(), self.capa + 16); self.capa += 16; } (_, _) => (), @@ -85,8 +228,29 @@ impl CxxString { pub unsafe fn as_ptr(&mut self) -> *const u8 { std::mem::transmute(self) } + + pub unsafe fn as_mut_ptr(&mut self) -> *mut u8 { + std::mem::transmute(self) + } + + pub fn size(&self) -> usize { + self.len + } + + pub unsafe fn pop_back(&mut self) { + let index = self.len; + self[index] = 0; + self.resize(self.len - 1); + } + + pub unsafe fn push_back(&mut self, symbol: u8) { + let index = self.len; + self.resize(index + 1); + self[index] = symbol; + } } +#[cfg(target_os = "windows")] impl Index for CxxString { type Output = u8; @@ -103,6 +267,7 @@ impl Index for CxxString { } } +#[cfg(target_os = "windows")] impl IndexMut for CxxString { fn index_mut(&mut self, index: usize) -> &mut Self::Output { unsafe { diff --git a/src/dictionary.rs b/src/dictionary.rs index 80a14a4..a1c5a12 100644 --- a/src/dictionary.rs +++ b/src/dictionary.rs @@ -36,10 +36,9 @@ impl Dictionary { reader.read_to_string(&mut contents)?; let mut map = HashMap::>::new(); for item in Regex::new(r#""(.+)","(.+)""#)?.captures_iter(&contents) { - map.insert( - String::from(item.get(1).unwrap().as_str()), - Vec::from(WINDOWS_1251.encode(item.get(2).unwrap().as_str()).0.as_ref()), - ); + let mut v = Vec::from(WINDOWS_1251.encode(item.get(2).unwrap().as_str()).0.as_ref()); + v.push(0); + map.insert(String::from(item.get(1).unwrap().as_str()), v); } Ok(map) } diff --git a/src/hooks.rs b/src/hooks.rs index 7b66bfc..de33bae 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,20 +1,25 @@ use retour::static_detour; -use std::error::Error; -use std::mem; -use std::os::raw::c_char; +use std::ffi::c_char; use crate::config::CONFIG; -use crate::cxxset::CxxSet; +// use crate::cxxset::CxxSet; use crate::cxxstring::CxxString; use crate::dictionary::DICTIONARY; use crate::utils; use r#macro::hook; -pub unsafe fn attach_all() -> Result<(), Box> { +#[cfg(target_os = "linux")] +#[static_init::dynamic] +static ENABLER: usize = unsafe { + utils::symbol_handle_self::<*const i64>(&CONFIG.symbol.as_ref().unwrap().enabler.as_ref().unwrap()[1]) as usize +}; + +pub unsafe fn attach_all() -> Result<(), Box> { if CONFIG.settings.enable_translation { attach_string_copy_n()?; attach_string_append_n()?; + attach_std_string_append()?; attach_addst()?; attach_addst_top()?; attach_addst_flag()?; @@ -30,12 +35,36 @@ pub unsafe fn attach_all() -> Result<(), Box> { Ok(()) } -#[hook] -extern "cdecl" fn string_copy_n(dst: *mut c_char, src: *const u8, size: usize) -> *mut c_char { +pub unsafe fn detach_all() -> Result<(), Box> { + if CONFIG.settings.enable_translation { + detach_string_copy_n()?; + detach_string_append_n()?; + detach_std_string_append()?; + detach_addst()?; + detach_addst_top()?; + detach_addst_flag()?; + } + if CONFIG.settings.enable_search { + detach_standardstringentry()?; + detach_simplify_string()?; + detach_upper_case_string()?; + detach_lower_case_string()?; + detach_capitalize_string_words()?; + detach_capitalize_string_first_word()?; + } + Ok(()) +} + +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(bypass))] +fn string_copy_n(dst: *mut c_char, src: *const c_char, size: usize) -> *mut c_char { unsafe { match (utils::cstr(src, size + 1), size > 1) { (Ok(value), true) => match DICTIONARY.get(value) { - Some(translate) => original!(dst, translate.as_ptr(), translate.len()), + Some(translate) => { + let (ptr, len, _) = translate.to_owned().into_raw_parts(); + original!(dst, ptr as *const c_char, len - 1) + } _ => original!(dst, src, size), }, (_, _) => original!(dst, src, size), @@ -43,12 +72,16 @@ extern "cdecl" fn string_copy_n(dst: *mut c_char, src: *const u8, size: usize) - } } -#[hook] -extern "cdecl" fn string_append_n(dst: *mut c_char, src: *const u8, size: usize) -> *mut c_char { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(bypass))] +fn string_append_n(dst: *mut c_char, src: *const c_char, size: usize) -> *mut c_char { unsafe { match (utils::cstr(src, size + 1), size > 1) { (Ok(value), true) => match DICTIONARY.get(value) { - Some(translate) => original!(dst, translate.as_ptr(), translate.len()), + Some(translate) => { + let (ptr, len, _) = translate.to_owned().into_raw_parts(); + original!(dst, ptr as *const c_char, len - 1) + } _ => original!(dst, src, size), }, (_, _) => original!(dst, src, size), @@ -56,14 +89,39 @@ extern "cdecl" fn string_append_n(dst: *mut c_char, src: *const u8, size: usize) } } -#[hook] -extern "fastcall" fn addst(gps: usize, src: *const u8, justify: u8, space: u32) { +#[cfg_attr(target_os = "windows", hook(bypass))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn std_string_append(dst: *const u8, src: *const c_char) -> *const u8 { + unsafe { + match std::ffi::CStr::from_ptr(src).to_str() { + (Ok(value)) => match DICTIONARY.get(value) { + Some(translate) => { + let (ptr, _, _) = translate.to_owned().into_raw_parts(); + original!(dst, ptr as *const c_char) + } + _ => original!(dst, src), + }, + _ => original!(dst, src), + } + } +} + +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn addst(gps: usize, src: *const u8, justify: u8, space: u32) { unsafe { let s = CxxString::from_ptr(src); match s.to_str() { Ok(converted) => match DICTIONARY.get(converted) { Some(translate) => { - let mut cxxstr = CxxString::new(translate.clone().as_mut_ptr(), translate.len()); + let (ptr, len, _) = translate.to_owned().into_raw_parts(); + let mut cxxstr = CxxString::new(ptr, len - 1); + #[cfg(target_os = "linux")] + { + if cxxstr.len < 16 { + cxxstr.ptr = cxxstr.sso.buf.as_mut_ptr(); + } + } original!(gps, cxxstr.as_ptr(), justify, space) } _ => original!(gps, src, justify, space), @@ -73,14 +131,22 @@ extern "fastcall" fn addst(gps: usize, src: *const u8, justify: u8, space: u32) } } -#[hook] -extern "fastcall" fn addst_top(gps: usize, src: *const u8, a3: usize) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(bypass))] +fn addst_top(gps: usize, src: *const u8, a3: usize) { unsafe { let s = CxxString::from_ptr(src); match s.to_str() { Ok(converted) => match DICTIONARY.get(converted) { Some(translate) => { - let mut cxxstr = CxxString::new(translate.clone().as_mut_ptr(), translate.len()); + let (ptr, len, _) = translate.to_owned().into_raw_parts(); + let mut cxxstr = CxxString::new(ptr, len - 1); + #[cfg(target_os = "linux")] + { + if cxxstr.len < 16 { + cxxstr.ptr = cxxstr.sso.buf.as_mut_ptr(); + } + } original!(gps, cxxstr.as_ptr(), a3) } _ => original!(gps, src, a3), @@ -90,14 +156,22 @@ extern "fastcall" fn addst_top(gps: usize, src: *const u8, a3: usize) { } } -#[hook] -extern "fastcall" fn addst_flag(gps: usize, src: *const u8, a3: usize, a4: usize, flag: u32) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn addst_flag(gps: usize, src: *const u8, a3: usize, a4: usize, flag: u32) { unsafe { let s = CxxString::from_ptr(src); match s.to_str() { Ok(converted) => match DICTIONARY.get(converted) { Some(translate) => { - let mut cxxstr = CxxString::new(translate.clone().as_mut_ptr(), translate.len()); + let (ptr, len, _) = translate.to_owned().into_raw_parts(); + let mut cxxstr = CxxString::new(ptr, len - 1); + #[cfg(target_os = "linux")] + { + if cxxstr.len < 16 { + cxxstr.ptr = cxxstr.sso.buf.as_mut_ptr(); + } + } original!(gps, cxxstr.as_ptr(), a3, a4, flag) } _ => original!(gps, src, a3, a4, flag), @@ -110,77 +184,91 @@ extern "fastcall" fn addst_flag(gps: usize, src: *const u8, a3: usize, a4: usize #[non_exhaustive] struct StringEntry; +#[allow(dead_code)] impl StringEntry { pub const LETTERS: u8 = 1; pub const SPACE: u8 = 2; pub const NUMBERS: u8 = 4; pub const CAPS: u8 = 8; pub const SYMBOLS: u8 = 16; + pub const STRINGENTRY_FILENAME: u8 = 32; } -#[hook] -extern "fastcall" fn standardstringentry(src: *const u8, maxlen: i64, flag: u8, events_ptr: *const u8) -> i32 { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn standardstringentry(src: *const u8, maxlen: usize, flag: u8, events_ptr: *const u8, utf: *const u32) -> bool { unsafe { - let content = CxxString::from_ptr(src); - let events = CxxSet::::from_ptr(events_ptr); - let shift = CONFIG.offset.keybinding.unwrap_or(0); - let mut entry = shift + 1; - let mut ranges = vec![shift..=shift]; - - if (flag & StringEntry::SYMBOLS) > 0 { - ranges.push(shift..=(shift + 255)); - } - if (flag & StringEntry::LETTERS) > 0 { - ranges.push((shift + 65)..=(shift + 90)); - ranges.push((shift + 97)..=(shift + 122)); - ranges.push((shift + 192)..=(shift + 255)); - } - if (flag & StringEntry::SPACE) > 0 { - ranges.push((shift + 32)..=(shift + 32)); - } - if (flag & StringEntry::NUMBERS) > 0 { - ranges.push((shift + 48)..=(shift + 57)); + let utf_a = std::slice::from_raw_parts_mut(utf as *mut u32, 8); + #[cfg(target_os = "linux")] + { + let utf_a = std::slice::from_raw_parts_mut((*ENABLER + &CONFIG.offset.utf_input.unwrap()) as *mut u32, 8); } - 'outer: for range in ranges.into_iter() { - 'inner: for item in range.into_iter() { - if events.contains(item) { - entry = item; - if item > shift + 192 && item <= shift + 255 { - entry += 1; - } - break 'outer; - } + for i in 0..8 { + if utf_a[i] > 122 && utils::UTF_TO_CP1251.contains_key(&utf_a[i]) { + let entry = utils::UTF_TO_CP1251[&utf_a[i]]; + utf_a[i] = match (flag & StringEntry::CAPS) > 0 { + true => capitalize(entry), + false => entry, + } as u32; } } - match entry - shift { - 1 => return 0, - 0 => { - if content.len > 0 { - content.resize(content.len - 1); - } - } - value => { - let mut cursor = content.len; - if cursor >= maxlen as usize { - cursor = (maxlen - 1) as usize; - } - if cursor < 0 { - cursor = 0; - } - if content.len < cursor + 1 { - content.resize(cursor + 1); - } - let letter = match flag & StringEntry::CAPS > 0 { - true => capitalize(value as u8), - false => value as u8, - }; - content[cursor] = letter; - } - } - events.clear(); - 1 + original!(src, maxlen, flag, events_ptr, utf_a.as_ptr()) + + // let mut content = CxxString::from_ptr(src); + // let events = CxxSet::::from_ptr(events_ptr); + // let shift = CONFIG.offset.keybinding.unwrap_or(0); + + // if events.contains(shift + 1) && content.size() > 0 { + // content.pop_back(); + // } + + // // if INTERFACEKEY_SELECT || INTERFACEKEY_LEAVESCREEN + // // lost mouse rbut here, cause it is in enabler instance + // if events.contains(1) || events.contains(2) { + // return false; + // } + // events.clear(); + + // if content.size() >= maxlen { + // return false; + // } + + // let mut any_valid = false; + // let utf_a = std::slice::from_raw_parts(utf, 8); + + // for i in 0..8 { + // let mut entry: u8; + // if utf_a[i] > 122 && utils::UTF_TO_CP1251.contains_key(&utf_a[i]) { + // entry = utils::UTF_TO_CP1251[&utf_a[i]]; + // } else { + // entry = utf_a[i] as u8; + // } + // if entry == 0 { + // break; + // } + + // if content.size() < maxlen && (entry == 10) + // || (flag & StringEntry::SYMBOLS) > 0 + // || ((flag & StringEntry::LETTERS) > 0 + // && ((entry >= 97 && entry <= 122) || (entry >= 65 && entry <= 90) || (entry >= 192 && entry <= 255))) + // || ((flag & StringEntry::SPACE) > 0 && entry == 32) + // || ((flag & StringEntry::NUMBERS) > 0 && (entry >= 48 && entry <= 57)) + // { + // if (flag & StringEntry::CAPS) > 0 { + // entry = capitalize(entry); + // } + + // any_valid = true; + // content.push_back(entry); + // if entry == 0 || entry == 10 || content.size() >= maxlen { + // break; + // } + // } + // } + + // any_valid } } @@ -202,8 +290,9 @@ fn lowercast(symbol: u8) -> u8 { } } -#[hook] -extern "fastcall" fn simplify_string(src: *const u8) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn simplify_string(src: *const u8) { unsafe { let mut content = CxxString::from_ptr(src); for i in 0..content.len { @@ -222,8 +311,9 @@ extern "fastcall" fn simplify_string(src: *const u8) { } } -#[hook] -extern "fastcall" fn upper_case_string(src: *const u8) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn upper_case_string(src: *const u8) { unsafe { let mut content = CxxString::from_ptr(src); for i in 0..content.len { @@ -242,8 +332,9 @@ extern "fastcall" fn upper_case_string(src: *const u8) { } } -#[hook] -extern "fastcall" fn lower_case_string(src: *const u8) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn lower_case_string(src: *const u8) { unsafe { let mut content = CxxString::from_ptr(src); for i in 0..content.len { @@ -262,11 +353,27 @@ extern "fastcall" fn lower_case_string(src: *const u8) { } } -#[hook] -extern "fastcall" fn capitalize_string_words(src: *const u8) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn capitalize_string_words(src: *const u8) { unsafe { + let mut bracket_count: i32 = 0; let mut content = CxxString::from_ptr(src); for i in 0..content.len { + match content[i] { + 91 => { + bracket_count += 1; + continue; + } + 93 => { + bracket_count -= 1; + continue; + } + _ => (), + }; + if bracket_count > 0 { + continue; + } let mut conf = false; if (i > 0 && content[i - 1] == 32 || content[i - 1] == 34) || (i >= 2 && content[i - 1] == 39 && (content[i - 2] == 32 || content[i - 2] == 44)) @@ -290,11 +397,27 @@ extern "fastcall" fn capitalize_string_words(src: *const u8) { } } -#[hook] -extern "fastcall" fn capitalize_string_first_word(src: *const u8) { +#[cfg_attr(target_os = "windows", hook(by_offset))] +#[cfg_attr(target_os = "linux", hook(by_symbol))] +fn capitalize_string_first_word(src: *const u8) { unsafe { + let mut bracket_count: i32 = 0; let mut content = CxxString::from_ptr(src); for i in 0..content.len { + match content[i] { + 91 => { + bracket_count += 1; + continue; + } + 93 => { + bracket_count -= 1; + continue; + } + _ => (), + }; + if bracket_count > 0 { + continue; + } let mut conf = false; if (i > 0 && content[i - 1] == 32 || content[i - 1] == 34) || (i >= 2 && content[i - 1] == 39 && (content[i - 2] == 32 || content[i - 2] == 44)) diff --git a/src/lib.rs b/src/lib.rs index 49e9c0c..c6309cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ +#![feature(vec_into_raw_parts)] + #[macro_use] extern crate serde_derive; extern crate toml; mod config; -mod crash; +// mod crash; mod cxxset; mod cxxstring; mod dictionary; @@ -18,19 +20,18 @@ use crate::config::CONFIG; #[static_init::constructor] #[no_mangle] extern "C" fn attach() { - unsafe { - crash::install(); - } + std::env::set_var("RUST_BACKTRACE", "1"); + // unsafe { + // crash::install(); + // } simple_logging::log_to_file(&CONFIG.settings.log_file, LevelFilter::Trace).unwrap(); if CONFIG.metadata.name != "dfint localization hook" { error!("unable to find config file"); - unsafe { - utils::message_box( - "unable to find config file", - "dfint hook error", - utils::MessageIconType::Error, - ); - } + utils::message_box( + "unable to find config file", + "dfint hook error", + utils::MessageIconType::Error, + ); std::process::exit(2); } info!("pe checksum: 0x{:x}", CONFIG.offset_metadata.checksum); @@ -45,5 +46,10 @@ extern "C" fn attach() { #[static_init::destructor] #[no_mangle] extern "C" fn detach() { + unsafe { + match hooks::detach_all() { + _ => (), + }; + } trace!("hooks detached"); } diff --git a/src/macro/src/lib.rs b/src/macro/src/lib.rs index 142d519..e6b036e 100644 --- a/src/macro/src/lib.rs +++ b/src/macro/src/lib.rs @@ -5,8 +5,49 @@ extern crate syn; use proc_macro::TokenStream; +struct Args { + offset: usize, + module: String, + symbol: String, + bypass: bool, + by_offset: bool, + by_symbol: bool, +} + +impl Args { + pub fn parse(args: TokenStream) -> Self { + let mut offset = 0; + let mut module = String::from(""); + let mut symbol = String::from(""); + let mut bypass = false; + let mut by_offset: bool = false; + let mut by_symbol: bool = false; + for arg_pair in args.to_string().split(",") { + let arg = arg_pair.split("=").collect::>(); + match arg[0].trim() { + "offset" => offset = usize::from_str_radix(&arg[1].trim().replace("\"", ""), 16).unwrap(), + "module" => module = String::from(arg[1].trim()), + "symbol" => symbol = String::from(arg[1].trim()), + "bypass" => bypass = true, + "by_offset" => by_offset = true, + "by_symbol" => by_symbol = true, + _ => (), + } + } + Self { + offset, + module, + symbol, + bypass, + by_offset, + by_symbol, + } + } +} + #[proc_macro_attribute] -pub fn hook(_attr: TokenStream, input: TokenStream) -> TokenStream { +pub fn hook(args: TokenStream, input: TokenStream) -> TokenStream { + let args = Args::parse(args); let item: syn::Item = syn::parse_macro_input!(input); if let syn::Item::Fn(function) = item { let syn::ItemFn { @@ -26,30 +67,100 @@ pub fn hook(_attr: TokenStream, input: TokenStream) -> TokenStream { } = function; let attach_ident = format_ident!("attach_{}", ident); + let detach_ident = format_ident!("detach_{}", ident); let handle_ident = format_ident!("handle_{}", ident); + let ret_type = quote!(#output).to_string(); + let inputs_unnamed = quote!(#inputs) + .to_string() + .split(",") + .map(|arg| arg.split(":").collect::>()[1]) + .collect::>() + .join(","); - let output = quote!( - pub unsafe fn #attach_ident() -> Result<(), Box> { - let target = mem::transmute(utils::address(CONFIG.offset.#ident)); + let mut attach = quote!( + pub unsafe fn #attach_ident() -> Result<(), Box> { + let target = target(); #handle_ident.initialize(target, #ident)?.enable()?; Ok(()) } + ) + .to_string(); + + attach = match (args.offset, args.module, args.symbol, args.by_offset, args.by_symbol) { + (o, _, _, _, _) if o > 0 => attach.replace( + "target()", + format!("std::mem::transmute(utils::address({}))", o).as_str(), + ), + (_, m, s, _, _) if m == "self" && s != "" => attach.replace( + "target()", + format!( + "utils::symbol_handle_self::({})", + inputs_unnamed, ret_type, s + ) + .as_str(), + ), + (_, m, s, _, _) if m != "" && s != "" => attach.replace( + "target()", + format!( + "utils::symbol_handle::({}, {})", + inputs_unnamed, ret_type, m, s + ) + .as_str(), + ), + (_, _, _, _, bs) if bs => attach.replace( + "target()", + format!( + "utils::symbol_handle::(&CONFIG.symbol.as_ref().unwrap().{}.as_ref().unwrap()[0], &CONFIG.symbol.as_ref().unwrap().{}.as_ref().unwrap()[1])", + inputs_unnamed, ret_type, ident.to_string(), ident.to_string() + ) + .as_str(), + ), + (_, _, _, bo, _) if bo => attach.replace( + "target()", + format!( + "std::mem::transmute(utils::address(CONFIG.offset.{}.unwrap()))", + ident.to_string() + ) + .as_str(), + ), + (_, _, _, _, _) => attach.replace( + "target()", + format!( + "std::mem::transmute(utils::address(CONFIG.offset.{}.unwrap()))", + ident.to_string() + ) + .as_str(), + ), + }; + + if args.bypass { + attach = quote!( + pub unsafe fn #attach_ident() -> Result<(), Box> { + Ok(()) + } + ) + .to_string(); + } + + let result = quote!( + pub unsafe fn #detach_ident() -> Result<(), Box> { + #handle_ident.disable()?; + Ok(()) + } static_detour! { static #handle_ident: unsafe #abi fn() #output; } #vis #unsafety #constness fn #ident(#inputs) #output #block ); - let inputs_unnamed = format!("{}", quote!(#inputs)) - .split(",") - .map(|arg| arg.split(":").collect::>()[1]) - .collect::>() - .join(","); - - return output - .to_string() - .replace("original!", format!("handle_{}.call", ident.to_string()).as_str()) - .replace("fn()", format!("fn({})", inputs_unnamed).as_str()) - .parse() - .unwrap(); + return format!( + "{}\n{}", + attach.to_string(), + result + .to_string() + .replace("original!", format!("handle_{}.call", ident.to_string()).as_str()) + .replace("fn()", format!("fn({})", inputs_unnamed).as_str()) + ) + .parse() + .unwrap(); } else { panic!("Fatal error in hook macro") } diff --git a/src/utils.rs b/src/utils.rs index 117f417..c538b24 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,39 @@ -use winapi::um::libloaderapi::GetModuleHandleW; -use winapi::um::winuser::MessageBoxW; +use std::collections::HashMap; + +use dlopen2::raw::Library; + +#[cfg(target_os = "windows")] +const SDL2: &'static str = "SDL2.dll"; +#[cfg(target_os = "linux")] +const SDL2: &'static str = "libSDL2-2.0.so.0"; + +#[cfg(target_os = "windows")] +#[static_init::dynamic] +pub static MODULE: usize = unsafe { winapi::um::libloaderapi::GetModuleHandleW(std::ptr::null()) as usize }; + +#[cfg(target_os = "linux")] +#[static_init::dynamic] +pub static MODULE: usize = 0; + +#[static_init::dynamic] +static SDL_MESSAGE_BOX: fn(u32, *const i8, *const i8, *const u8) -> i32 = + unsafe { symbol_handle:: i32>(SDL2, "SDL_ShowSimpleMessageBox") }; #[static_init::dynamic] -static MODULE: usize = unsafe { GetModuleHandleW(std::ptr::null()) as usize }; +static SDL_ERROR: fn() -> *const i8 = unsafe { symbol_handle:: *const i8>(SDL2, "SDL_GetError") }; + +pub unsafe fn symbol_handle(module: &str, symbol: &str) -> T { + if module == "self" { + return symbol_handle_self::(symbol); + } + let lib = Library::open(module).expect("Could not open library"); + unsafe { lib.symbol(symbol) }.unwrap() +} + +pub unsafe fn symbol_handle_self(symbol: &str) -> T { + let lib = Library::open_self().expect("Could not open self"); + unsafe { lib.symbol(symbol) }.unwrap() +} #[allow(dead_code)] pub fn address(offset: usize) -> usize { @@ -10,30 +41,109 @@ pub fn address(offset: usize) -> usize { } #[allow(dead_code)] +#[repr(u32)] pub enum MessageIconType { - Error = 16, - Info = 64, - Question = 66, - Warning = 58, + Error = 0x10, + Warning = 0x20, + Info = 0x40, } -#[allow(dead_code)] -pub unsafe fn message_box(message: &str, caption: &str, icon: MessageIconType) { - let message = message.encode_utf16(); - let mut message_vec: Vec = message.collect(); - message_vec.push(0); - let caption = caption.encode_utf16(); - let mut caption_vec: Vec = caption.collect(); - caption_vec.push(0); - MessageBoxW( - std::ptr::null_mut(), - message_vec.as_ptr(), - caption_vec.as_ptr(), +#[allow(temporary_cstring_as_ptr)] +pub fn message_box(title: &str, text: &str, icon: MessageIconType) { + let ret = SDL_MESSAGE_BOX( icon as u32, + std::ffi::CString::new(title).unwrap().as_ptr(), + std::ffi::CString::new(text).unwrap().as_ptr(), + std::ptr::null(), ); + if ret == -1 { + log::error!("SDL_ShowSimpleMessageBox: {}", unsafe { + std::ffi::CStr::from_ptr(SDL_ERROR()).to_str().unwrap() + }); + } } #[allow(dead_code)] -pub unsafe fn cstr(src: *const u8, size: usize) -> Result<&'static str, std::str::Utf8Error> { - std::ffi::CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(src, size)).to_str() +pub unsafe fn cstr(src: *const T, size: usize) -> Result<&'static str, std::str::Utf8Error> { + std::ffi::CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(src as *const u8, size)).to_str() } + +#[static_init::dynamic] +pub static UTF_TO_CP1251: std::collections::HashMap = HashMap::from([ + (37072, 192), + (37328, 193), + (37584, 194), + (37840, 195), + (38096, 196), + (38352, 197), + (38608, 198), + (38864, 199), + (39120, 200), + (39376, 201), + (39632, 202), + (39888, 203), + (40144, 204), + (40400, 205), + (40656, 206), + (40912, 207), + (41168, 208), + (41424, 209), + (41680, 210), + (41936, 211), + (42192, 212), + (42448, 213), + (42704, 214), + (42960, 215), + (43216, 216), + (43472, 217), + (43728, 218), + (43984, 219), + (44240, 220), + (44496, 221), + (44752, 222), + (45008, 223), + (45264, 224), + (45520, 225), + (45776, 226), + (46032, 227), + (46288, 228), + (46544, 229), + (46800, 230), + (47056, 231), + (47312, 232), + (47568, 233), + (47824, 234), + (48080, 235), + (48336, 236), + (48592, 237), + (48848, 238), + (49104, 239), + (32977, 240), + (33233, 241), + (33489, 242), + (33745, 243), + (34001, 244), + (34257, 245), + (34513, 246), + (34769, 247), + (35025, 248), + (35281, 249), + (35537, 250), + (35793, 251), + (36049, 252), + (36305, 253), + (36561, 254), + (36817, 255), + (33232, 168), + (37329, 184), + (34000, 170), + (38097, 186), + (34768, 175), + (38865, 191), + (34512, 178), + (38609, 179), + (37074, 165), + (37330, 180), + (36560, 161), + (40657, 162), +]);