From b4bf778867c3c9e7ccf83eca7846086087fd072d Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Sat, 7 Jun 2025 19:29:56 +0200 Subject: [PATCH 01/18] (WIP) Local state (rust): - switch to nightly build (for retour) - add simple CREATE_HOOK macro - ExecuteConsoleCommand + --- rust-toolchain.toml | 2 + sleuth/CMakeLists.txt | 2 +- sleuth/Cargo.lock | 191 +++++++++++++++++++++++++- sleuth/Cargo.toml | 63 ++++++++- sleuth/src/lib.rs | 30 +++- sleuth/src/resolvers/admin_control.rs | 171 ++++++++++++++++++++++- sleuth/src/resolvers/backend_hooks.rs | 80 ++++++++++- sleuth/src/resolvers/hook_retour.rs | 61 ++++++++ sleuth/src/resolvers/macros.rs | 116 +++++++++++++++- sleuth/src/resolvers/mod.rs | 80 ++++++++++- sleuth/src/resolvers/rcon.rs | 46 +++++++ sleuth/src/scan.rs | 77 ++++++++++- src/main.cpp | 2 +- 13 files changed, 894 insertions(+), 27 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 sleuth/src/resolvers/hook_retour.rs create mode 100644 sleuth/src/resolvers/rcon.rs diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/sleuth/CMakeLists.txt b/sleuth/CMakeLists.txt index 7413f3e..e400705 100644 --- a/sleuth/CMakeLists.txt +++ b/sleuth/CMakeLists.txt @@ -4,7 +4,7 @@ project(sleuthlib C) add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/target/release/sleuthlib.lib - COMMAND cargo build --release + COMMAND cargo +nightly build --release WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS # ${CMAKE_CURRENT_SOURCE_DIR}/sleuthlib.rs diff --git a/sleuth/Cargo.lock b/sleuth/Cargo.lock index 33446b8..5aa0705 100644 --- a/sleuth/Cargo.lock +++ b/sleuth/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -20,6 +20,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.1" @@ -32,6 +38,15 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -88,7 +103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -111,6 +126,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "erased-serde" version = "0.4.6" @@ -232,12 +253,28 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + [[package]] name = "heck" version = "0.4.1" @@ -253,6 +290,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + [[package]] name = "inventory" version = "0.3.20" @@ -289,6 +336,16 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libudis86-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "lock_api" version = "0.4.13" @@ -299,6 +356,15 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.4" @@ -314,6 +380,16 @@ dependencies = [ "adler2", ] +[[package]] +name = "mmap-fixed-fixed" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "object" version = "0.32.2" @@ -331,6 +407,16 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" version = "0.9.11" @@ -353,7 +439,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "patternsleuth" version = "0.1.0" -source = "git+https://github.com/trumank/patternsleuth.git?rev=5786164#5786164189604fd55f647aa38ef0bfd6f23f7d58" +source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" dependencies = [ "anyhow", "futures", @@ -377,7 +463,7 @@ dependencies = [ [[package]] name = "patternsleuth_scanner" version = "0.1.0" -source = "git+https://github.com/trumank/patternsleuth.git?rev=5786164#5786164189604fd55f647aa38ef0bfd6f23f7d58" +source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" dependencies = [ "anyhow", "memchr", @@ -460,7 +546,35 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags", + "bitflags 2.9.1", +] + +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys", +] + +[[package]] +name = "retour" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9af44d40e2400b44d491bfaf8eae111b09f23ac4de6e92728e79d93e699c527" +dependencies = [ + "cfg-if", + "generic-array", + "libc", + "libudis86-sys", + "mmap-fixed-fixed", + "once_cell", + "region", + "slice-pool2", ] [[package]] @@ -524,6 +638,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -538,15 +658,27 @@ name = "sleuthlib" version = "0.1.0" dependencies = [ "anyhow", + "bitflags 2.9.1", "futures", + "indexmap", "once_cell", + "parking_lot", "paste", "patternsleuth", + "retour", "serde", "serde_json", "tracing", + "widestring", + "winapi", ] +[[package]] +name = "slice-pool2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" + [[package]] name = "smallvec" version = "1.15.0" @@ -639,6 +771,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "typetag" version = "0.2.20" @@ -669,6 +807,40 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.52.0" @@ -688,6 +860,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.0" diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 7a9c86b..1f342ca 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -4,14 +4,67 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["staticlib"] +crate-type = ["staticlib", "cdylib", "rlib"] [dependencies] -patternsleuth = { git = "https://github.com/trumank/patternsleuth.git", rev = "5786164", features = ["process-internal", "serde-resolvers", "image-pe"] } +winapi = { version = "0.3.9", features = ["winnt", "wincrypt", "winuser"] } + +patternsleuth = { git = "https://github.com/Knutschbert/patternsleuth.git", rev = "644f980", features = ["process-internal", "serde-resolvers", "image-pe"] } +# dll_hook = { git = "https://github.com/Knutschbert/patternsleuth.git", rev = "644f980", package = "dll_hook" } +# patternsleuth = { path = "../../patternsleuth/patternsleuth", features = ["process-internal", "symbols", "serde-resolvers", "image-pe", "image-elf"] } +# dll_hook = { path = "../../patternsleuth/examples/dll_hook"} anyhow = "1.0.79" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0" +# typetag = "0.2.20" tracing = "0.1.40" futures = "0.3.31" once_cell = "1.19" -paste = "1.0" \ No newline at end of file +paste = "1.0.15" +widestring = "1.0.2" +# backtrace = "0.3.69" +bitflags = "2.4.1" +# eframe = "0.24.0" +# egui-winit = { version = "0.24.0", default-features = false } +indexmap = "2.1.0" +parking_lot = "0.12.1" +# simple-log = "1.6.0" +# thread_local = "1.1.7" + +retour = { version = "0.3.1", features = ["static-detour"]} +# windows = { version = "0.48.0", features = ["Win32_Foundation", +# "Win32_System_Console", +# "Win32_System_LibraryLoader", +# "Win32_System_SystemServices", +# "Win32_Graphics_Gdi", +# "Win32_Graphics_Direct3D", +# "Win32_Graphics_Direct3D11", +# "Win32_Graphics_Dxgi", +# "Win32_Graphics_Dxgi_Common", +# "Win32_UI_WindowsAndMessaging", +# "Win32_System_Threading", + +# "Win32_Security", +# "Win32_System_Kernel", +# "Win32_System_Memory", +# "Win32_System_ProcessStatus", +# ]} + +# [target."cfg(windows)".dev-dependencies.windows] +# version = "0.48" +# features = ["Win32_Foundation", +# "Win32_System_Console", "Win32_System_LibraryLoader", "Win32_System_SystemServices", +# "Win32_Graphics_Gdi", "Win32_Graphics_Direct3D", "Win32_Graphics_Direct3D11", +# "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common", +# "Win32_UI_WindowsAndMessaging", +# ] + +# [target.'cfg(windows)'.dependencies] +# winapi = { version = "0.3.9", features = ["winnt", "wincrypt", "winuser"] } + + + +[features] +default = ["serde-resolvers"] +serde-resolvers = [] +image-elf = [] diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index f616ece..8a55351 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -2,7 +2,6 @@ mod resolvers; mod scan; - use std::env; use std::fs::File; use std::io::{BufReader, Read}; @@ -13,7 +12,7 @@ use std::path::PathBuf; use anyhow::Result; use serde::Serialize; use serde_json::to_writer_pretty; -use self::resolvers::{PLATFORM, PlatformType}; +use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; // IEEE use std::arch::x86_64::_mm_crc32_u8; @@ -93,6 +92,15 @@ pub fn dump_builds(offsets: HashMap) -> Result<()> { Ok(()) } + +pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) -> Result<(), Box> { + + // attach_GameEngineTick(base_address, offsets).unwrap(); + resolvers::admin_control::attach_UGameEngineTick(base_address, offsets.clone()).unwrap(); + resolvers::admin_control::attach_ExecuteConsoleCommand(base_address, offsets).unwrap(); + Ok(()) + } + #[no_mangle] pub extern "C" fn generate_json() -> u8 { println!("test asd"); @@ -102,10 +110,24 @@ pub extern "C" fn generate_json() -> u8 { false => PlatformType::STEAM }; - PLATFORM.set(platform).expect("Platform already set"); + std::thread::spawn(|| { + resolvers::rcon::handle_rcon(); + }); + PLATFORM.set(platform).expect("Platform already set"); + let image = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); + let exe = image; + println!("GAME '{:x?}'", exe.base_address); + BASE_ADDR.set(exe.base_address).expect("BASE_ADDR already set"); + // let scan = scan::scan(); let offsets = scan::scan().expect("Failed to scan"); let len_u8 = offsets.len() as u8; - dump_builds(offsets).expect("Failed to dump builds JSON"); + // FIXME: ? + let offset_copy = offsets.clone(); + // let base_addr = scan::scan().1; + unsafe { + attach_hooks(exe.base_address, offsets).unwrap(); + } + dump_builds(offset_copy).expect("Failed to dump builds JSON"); len_u8 } diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 5fbe97a..54769a4 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -1,4 +1,14 @@ +use patternsleuth::_impl_try_collector; +use patternsleuth::resolvers::Singleton; +use retour::static_detour; +use std::{collections::HashMap, sync::Arc}; +use std::error::Error; +use std::os::raw::c_void; +use std::mem; +use crate::resolvers::BASE_ADDR; +// use crate::resolvers::ue::FString; +use crate::scan::{OFFSETS, RESOLUTION}; define_pattern_resolver![UTBLLocalPlayer_Exec, { // "75 18 ?? ?? ?? ?? 75 12 4d 85 f6 74 0d 41 38 be ?? ?? ?? ?? 74 04 32 db eb 9b 48 8b 5d 7f 49 8b d5 4c 8b 45 77 4c 8b cb 49 8b cf", // EGS - latest @@ -13,6 +23,154 @@ define_pattern_resolver!(ExecuteConsoleCommand, [ "40 53 48 83 EC 30 48 8B 05 ?? ?? ?? ?? 48 8B D9 48 8B 90 58 0C 00 00" ]); +CREATE_HOOK!(ExecuteConsoleCommand, (string:*mut super::FString), { + println!(">>>>>>>>ExecuteConsoleCommand_d"); +}); + +// static_detour!{ +// pub static o_ExecuteConsoleCommand:unsafe extern "C" fn(*mut super::FString); +// }#[allow(non_snake_case)] +// pub fn ExecuteConsoleCommand_detour_fkt(string: *mut super::FString){ +// {} +// unsafe { +// println!(">>>>>>>>ExecuteConsoleCommand_detour_fkt {}", string.as_ref().unwrap().letter_count); +// o_ExecuteConsoleCommand.call(string) +// } +// } +// #[allow(non_snake_case)] +// pub unsafe fn attach_ExecuteConsoleCommand(base_address:usize,offsets:HashMap) -> Result<(),Box>{ +// let address = base_address+offsets[stringify![ExecuteConsoleCommand]]as usize; +// let target:FnExecuteConsoleCommand = mem::transmute(address); +// type FnExecuteConsoleCommand = unsafe extern "C" fn(*mut super::FString); +// println!("o_ExecuteConsoleCommand INITED: {:x?}", address); +// o_ExecuteConsoleCommand.initialize(target,ExecuteConsoleCommand_detour_fkt)? .enable()?; +// Ok(()) +// } + +// unsafe fn attach_ExecuteConsoleCommand(base_address: usize, offsets: HashMap) -> Result<(), Box>{ +// let address = base_address + offsets["ExecuteConsoleCommand"] as usize; +// let target: FnExecuteConsoleCommand = mem::transmute(address); +// type FnExecuteConsoleCommand = unsafe extern "C" fn(*mut c_void, f32, u8); +// static_detour! { +// static ExecuteConsoleCommand: unsafe extern "C" fn(*mut c_void, f32, u8); +// } +// fn detour_fkt(engine:*mut c_void, delta:f32, state:u8) { +// println!("rust ExecuteConsoleCommand delta: {}", delta); +// unsafe { ExecuteConsoleCommand.call( engine, delta, state) } +// } +// ExecuteConsoleCommand +// .initialize(target, detour_fkt)? +// .enable()?; +// Ok(()) + +CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { + let asd = Arc::clone(&crate::resolvers::rcon::COMMAND_PENDING); + let pending = match asd.lock().unwrap().as_ref() { + Some(true) => { + println!("got pending"); + true + } + _ => false + }; + if pending { + + let cmd_buf = Arc::clone(&crate::resolvers::rcon::LAST_COMMAND); + if let Some(cmd) = crate::resolvers::rcon::LAST_COMMAND.lock().unwrap().as_ref() { + if let Some(offset_map) = OFFSETS.get() { + println!("offsets: {}", offset_map.len()); + if let Some(val) = offset_map.get("ExecuteConsoleCommand") { + println!("Offset for ExecuteConsoleCommand is: 0x{:X}", val); + let u16pat = widestring::U16CString::from_str(cmd.as_str()).unwrap(); + let mut sstring = crate::resolvers::FString::new_from_wide_str(u16pat.as_slice()); + unsafe { + // let str_teeemp = widestring::U16CString::from_ptr(sstring.str, sstring.letter_count as usize).unwrap(); + // println!("[Rust RCOM] asdasdasdasdasd o_ExecuteConsoleCommand: {:x?} {}", val, str_teeemp.to_string_lossy()); + *asd.lock().unwrap() = Some(false); + unsafe { o_ExecuteConsoleCommand.call(&mut sstring); } + println!("executed command"); + } + } + } + } + } +}); +// CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { +// // println!("rust UGameEngineTick delta: {}", delta); +// // ExecuteConsoleCommand::get(&self).ok_or("Cant get addr"); +// // ExecuteConsoleCommand::resolver() +// // RESOLUTION +// // ExecuteConsoleCommand.get() +// // ExecuteConsoleCommand::get(ExecuteConsoleCommand.clone()); + +// let asd = Arc::clone(&crate::resolvers::rcon::COMMAND_PENDING); +// let pending = match asd.lock().unwrap().as_ref() { +// Some(true) => { +// println!("got pending"); +// true +// } +// _ => false +// }; +// // if let Some(pending) = crate::resolvers::rcon::COMMAND_PENDING.lock().unwrap().as_ref() { +// if (pending == true) { + +// let cmd_buf = Arc::clone(&crate::resolvers::rcon::LAST_COMMAND); +// println!("pending: {pending}", ); +// if let Some(cmd) = crate::resolvers::rcon::LAST_COMMAND.lock().unwrap().as_ref() { +// println!("cmd: {}", cmd); + +// if let Some(offset_map) = OFFSETS.get() { +// println!("offsets: {}", offset_map.len()); +// if let Some(val) = offset_map.get("ExecuteConsoleCommand") { +// println!("Offset for ExecuteConsoleCommand is: 0x{:X}", val); + +// println!("[Rust RCOM] Got command: {}", cmd); + +// let u16pat = widestring::U16CString::from_str( +// cmd.as_str() +// ).unwrap(); +// let mut sstring = crate::resolvers::FString::new_from_wide_str(u16pat.as_slice()); +// let base_address = BASE_ADDR.get().unwrap(); +// unsafe { +// let target:FnExecuteConsoleCommand = mem::transmute(val); +// type FnExecuteConsoleCommand = unsafe extern "C" fn(*mut super::FString); +// let str_teeemp = widestring::U16CString::from_ptr(sstring.str, sstring.letter_count as usize).unwrap(); +// println!("[Rust RCOM] asdasdasdasdasd o_ExecuteConsoleCommand: {:x?} {}", val, str_teeemp.to_string_lossy()); +// // o_ExecuteConsoleCommand.initialize(target,ExecuteConsoleCommand_detour_fkt).unwrap().enable(); +// *asd.lock().unwrap() = Some(false); +// // unsafe { ExecuteConsoleCommand_detour_fkt(&mut sstring); } +// unsafe { o_ExecuteConsoleCommand.call(&mut sstring); } +// println!("executed command"); +// } +// } else { +// println!("Key not found"); +// } +// } else { +// println!("OFFSETS not initialized yet"); +// } + +// println!("hello down here"); +// // let mut str_test = FString::from_string(cmd.as_str()).0; +// // let boxed = Box::new(str_test); +// // // Get *mut FString +// // let fstring_ptr: *mut FString = Box::into_raw(boxed); +// // unsafe { o_ExecuteConsoleCommand.call(fstring_ptr); } + + +// // attach_ExecuteConsoleCommand::detour_fkt(); + + +// // unsafe { ExecuteConsoleCommand_detour_fkt(&mut sstring); } +// // if let Some(addr) = OFFSETS.get().and_then(|m| m.get("ExecuteConsoleCommand")) { +// // println!("Address FOUND: 0x{:X}", addr); +// // } +// } +// *cmd_buf.lock().unwrap() = Some("\0".to_string()); +// } +// } +// // println!("hello down hereeeeee"); +// ); + + // FText* __cdecl FText::AsCultureInvariant(FText* __return_storage_ptr__, FString* param_1) define_pattern_resolver![FText_AsCultureInvariant, First, { EGS: ["48 89 5C 24 18 48 89 74 24 20 41 56 48 83 EC 60 33 C0 48 89 7C 24 78 48 63"], @@ -44,4 +202,15 @@ define_pattern_resolver![GetTBLGameMode, { define_pattern_resolver!(ClientMessage, [ "4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 73 18 49 8B D8 49 89 43 C8 48 8B F1 49 89 43 D0 49 89 43 D8 49 8D 43" -]); \ No newline at end of file +]); + +// use patternsleuth::resolvers::impl_try_collector; +// impl_try_collector! { +// #[derive(Debug, PartialEq, Clone)] +// struct DllHookResolution2 { +// guobject_array: patternsleuth::resolvers::unreal::guobject_array::GUObjectArray, + +// free_uobject: patternsleuth::resolvers::unreal::guobject_array::FUObjectArrayFreeUObjectIndex, + +// } +// } \ No newline at end of file diff --git a/sleuth/src/resolvers/backend_hooks.rs b/sleuth/src/resolvers/backend_hooks.rs index e85f3c9..709c9f4 100644 --- a/sleuth/src/resolvers/backend_hooks.rs +++ b/sleuth/src/resolvers/backend_hooks.rs @@ -1,4 +1,14 @@ +// use futures::future::join_all; +// use patternsleuth::resolvers::AsyncContext; +// use patternsleuth::resolvers::ensure_one; +// use crate::resolvers::macros::Simple_signature; + +// use super::macros::DefaultResult; +// use patternsleuth::resolvers::ResolveError; + +// use std::future::Future; +// use std::pin::Pin; define_pattern_resolver!(FString_AppendChars, [ "45 85 C0 0F 84 89 00 00 00 48 89 5C 24 18 48 89 6C 24 20 56 48 83 EC 20 48 89 7C 24 30 48 8B EA 48 63 79 08 48 8B D9 4C 89 74 24 38 45 33 F6 85 FF 49 63 F0 41 8B C6 0F 94 C0 03 C7 03 C6 89 41 08 3B 41 0C 7E 07 8B D7 E8 ?? ?? ?? ?? 85 FF 49 8B C6 48 8B CF 48 8B D5 0F 95 C0 48 2B C8 48 8B 03 48 8D 1C 36 4C 8B C3 48 8D 3C 48 48 8B CF E8 ?? ?? ?? ?? 48 8B 6C 24 48 66 44 89 34 3B 4C 8B 74 24 38 48 8B 7C 24 30 48 8B 5C 24 40 48 83 C4 20 5E C3" // Universal @@ -27,4 +37,72 @@ define_pattern_resolver!(GetCurrentGames, Call, [ define_pattern_resolver!(SendRequest, [ "48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 55 41 54 41 55 41 56 41 57 48 8B EC 48 83 EC 40 48 8B D9 49 8B F9" -]); \ No newline at end of file +]); + +// define_signature_fn!(function_first_signature, +// | ctx, patterns | { +// let futures = ::patternsleuth::resolvers::futures::future::join_all( +// patterns.iter() +// .map(|p| ctx.scan(::patternsleuth::scanner::Pattern::new(p).unwrap())) +// ).await; + +// // FIXME +// Ok(DefaultResult(futures.into_iter().flatten().collect::>()[0])) +// } +// ); + +// define_pattern_resolver!(GetMotdTwo, { +// EGS: [ +// // function_signature("4C 89 4C 24 20 4C 89 44 24 18 48 89 4C 24 08 55 56 57 41 54 48 8D 6C 24 C1 48 81 EC D8 00 00 00 83 79 08 01 4C 8B E2 48 8B F9 7F 19 33 F6 48 8B C2 48 89 32 48 89 72 08 48 81 C4 D8 00 00 00 41 5C 5F 5E 5D C3 48 89 9C 24 08 01 00 00 48 8D 55 B7 4C 89 AC 24 D0 00 00 00 4C 89 B4 24 C8 00 00 00 4C 89 BC 24 C0 00 00 00 E8 ?? ?? ?? ?? 4C 8B 6D B7 48 8D 4D 97 33 F6 48 89 75 97 48 89 75 9F 49 8B 45 00 8D 56 09"), +// function_first_signature("4C 89 4C 24 20 4C 89 44 24 18 48 89 4C 24 08 55 56 57 41 54 48 8D 6C 24 C1 48 81 EC D8 00 00 00 83 79 08 01 4C 8B E2 48 8B F9 7F 19 33 F6 48 8B C2 48 89 32 48 89 72 08 48 81 C4 D8 00 00 00 41 5C 5F 5E 5D C3 48 89 9C 24 08 01 00 00 48 8D 55 B7 4C 89 AC 24 D0 00 00 00 4C 89 B4 24 C8 00 00 00 4C 89 BC 24 C0 00 00 00 E8 ?? ?? ?? ?? 4C 8B 6D B7 48 8D 4D 97 33 F6 48 89 75 97 48 89 75 9F 49 8B 45 00 8D 56 09") +// ], +// STEAM: [call_signature("4C 89 4C 24 20 4C 89 44 24 18 48 89 4C 24 08 55 56 57 41 54 48 8D 6C 24 C1 48 81 EC E8 00 00 00 83 79 08 01 4C 8B E2 48 8B F9 7F 19 33 F6 48 8B C2 48 89 32 48 89 72 08 48 81 C4 E8 00 00 00 41 5C 5F 5E 5D C3 48 89 9C 24 18 01 00 00 48 8D 55 B7 4C 89 AC 24 E0 00 00 00 4C 89 B4 24 D8 00 00 00 4C 89 BC 24 D0 00 00 00 E8 ?? ?? ?? ?? 4C 8B 6D B7 48 8D 4C 24 20 33 F6 BA 09")] +// }, +// |ctx, patterns| { +// let mut results = Vec::new(); +// // FIXME: group sigs by type, run Signature func on multiple +// for pat in patterns { +// // match pat.kind { +// // SignatureKind::Call => println!("call"), +// // SignatureKind::Function => println!("function"), +// // _ => print!("NO MATCH") +// // } +// let offset = pat.calculate_offset(ctx).await.map(|r| r.offset()); +// // print!("PART RESULT: {:?}", offset); +// results.push(offset); +// } +// ensure_one(results.into_iter().flatten())? +// }); + +// define_pattern_resolver!(GetMotdTwo2, MultiCall, { +// EGS: [ +// Simple_signature("4C 89"), +// function_first_signature("4C 89") +// ], +// STEAM: [call_signature("4C 89 | ?? ?? ?? ?? BE EF")] +// }); + +// define_pattern_resolver!(GetMotdTwoS, { +// EGS: [ +// Simple_signature("4C 89"), +// function_first_signature("4C 89") +// ], +// STEAM: [call_signature("4C 89 | ?? ?? ?? ?? BE EF")] +// }); + +// define_pattern_resolver![GetMotdTwoS2, [ +// Simple_signature("4C 89"), +// function_first_signature("4C 89") +// ]]; + +// define_pattern_resolver![GetMotdTwoS3, MultiCall, [ +// Simple_signature("4C 89"), +// function_first_signature("4C 89") +// ]]; + + +// define_pattern_resolver!(GetMotdTwo3, MultiCall, [ +// function_signature("4C 89 4C 24 20 4C 89 44 24 18 48 89 4C 24 08 55 56 57 41 54 48 8D 6C 24 C1 48 81 EC D8 00 00 00 83 79 08 01 4C 8B E2 48 8B F9 7F 19 33 F6 48 8B C2 48 89 32 48 89 72 08 48 81 C4 D8 00 00 00 41 5C 5F 5E 5D C3 48 89 9C 24 08 01 00 00 48 8D 55 B7 4C 89 AC 24 D0 00 00 00 4C 89 B4 24 C8 00 00 00 4C 89 BC 24 C0 00 00 00 E8 ?? ?? ?? ?? 4C 8B 6D B7 48 8D 4D 97 33 F6 48 89 75 97 48 89 75 9F 49 8B 45 00 8D 56 09"), +// function_signature("4C 89 4C 24 20 4C 89 44 24 18 48 89 4C 24 08 55 56 57 41 54 48 8D 6C 24 C1 48 81 EC E8 00 00 00 83 79 08 01 4C 8B E2 48 8B F9 7F 19 33 F6 48 8B C2 48 89 32 48 89 72 08 48 81 C4 E8 00 00 00 41 5C 5F 5E 5D C3 48 89 9C 24 18 01 00 00 48 8D 55 B7 4C 89 AC 24 E0 00 00 00 4C 89 B4 24 D8 00 00 00 4C 89 BC 24 D0 00 00 00 E8 ?? ?? ?? ?? 4C 8B 6D B7 48 8D 4C 24 20 33 F6 BA 09") +// ]); + diff --git a/sleuth/src/resolvers/hook_retour.rs b/sleuth/src/resolvers/hook_retour.rs new file mode 100644 index 0000000..a22617d --- /dev/null +++ b/sleuth/src/resolvers/hook_retour.rs @@ -0,0 +1,61 @@ +// use retour::static_detour; +// use std::collections::HashMap; +// use std::error::Error; +// use std::os::raw::c_void; +// use std::mem; + + +// CREATE_HOOK!(TESTFKT, (engine:*mut c_void, delta:f32, state:u8), { +// println!("rust UGameEngineTick delta: {}", delta); +// }); + +// use once_cell::sync::Lazy; + +// fn get_base_address +// fn get_offsets() -> HashMap { +// HashMap() +// } + +// static _GAME_ENGINE_TICK_HOOK: Lazy>> = Lazy::new(|| { +// // Provide the actual base address and offsets here +// let base_address = get_base_address(); // <-- define this globally +// let offsets = get_offsets(); // <-- define this globally +// unsafe { attach_TESTFKT(base_address, offsets) } +// }); + +// // Force initialization at compile time (optional, ensures it runs) +// #[used] +// static _FORCE_GAME_ENGINE_TICK_HOOK: &Lazy>> = &_GAME_ENGINE_TICK_HOOK; + + +// CREATE_HOOK!(UGameEngineTick, HOOK_ATTACH, c_void, (engine:c_void, delta:f32, state:u8)); + +// unsafe fn attach_GameEngineTick(base_address: usize, offsets: HashMap) -> Result<(), Box>{ + +// let address = base_address + offsets["UGameEngineTick"] as usize; +// let target: FnUGameEngineTick = mem::transmute(address); + +// type FnUGameEngineTick = unsafe extern "C" fn(*mut c_void, f32, u8); + +// static_detour! { +// static UGameEngineTick: unsafe extern "C" fn(*mut c_void, f32, u8); +// } + +// fn detour_fkt(engine:*mut c_void, delta:f32, state:u8) { +// println!("rust UGameEngineTick delta: {}", delta); +// unsafe { UGameEngineTick.call( engine, delta, state) } +// } + +// UGameEngineTick +// .initialize(target, detour_fkt)? +// .enable()?; + +// Ok(()) +// } + +// pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) -> Result<(), Box> { + +// // attach_GameEngineTick(base_address, offsets).unwrap(); +// attach_TESTFKT(base_address, offsets).unwrap(); +// Ok(()) +// } diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 17114dd..80444c9 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -14,6 +14,118 @@ // #[macro_use] // extern crate paste; // concat strings + +/// ```rust +/// unsafe fn attach_GameEngineTick(base_address: usize, offsets: HashMap) -> Result<(), Box>{ +/// let address = base_address + offsets["UGameEngineTick"] as usize; +/// let target: FnUGameEngineTick = mem::transmute(address); +/// type FnUGameEngineTick = unsafe extern "C" fn(*mut c_void, f32, u8); +/// static_detour! { +/// static UGameEngineTick: unsafe extern "C" fn(*mut c_void, f32, u8); +/// } +/// fn detour_fkt(engine:*mut c_void, delta:f32, state:u8) { +/// println!("rust UGameEngineTick delta: {}", delta); +/// unsafe { UGameEngineTick.call( engine, delta, state) } +/// } +/// UGameEngineTick +/// .initialize(target, detour_fkt)? +/// .enable()?; +/// Ok(()) +/// } +/// ``` +/// + + +#[macro_export] +macro_rules! CREATE_HOOK { + + // ($name:ident, $mode:ident, $rettype:ident, ( $( $call_type:ident: $pattern:expr ),+ $(,)? )) => { + // ($name:ident, $mode:ident, $rettype:ident, ( $( $call_type:ident: $pattern:expr ),+ $(,)? )) => { + // println!($name); + // println!($mode); + // println!($rettype); + + // [ $( $call_type ($pattern) ),+ ]; + // }; + ($name:ident, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:block) => { + paste::paste! { + + static_detour! { + pub static []: unsafe extern "C" fn ($( $ty ),+ ); + } + + #[allow(non_snake_case)] + pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) { + // println!("rust $name delta: {}", delta); + $body + unsafe { [].call ( $( $arg ),+ ) } + } + + #[allow(non_snake_case)] + pub unsafe fn [](base_address: usize, offsets: HashMap) -> Result<(), Box>{ + + // ( $( $arg: $ty ),+ ); + let address = base_address + offsets[stringify![$name]] as usize; + let target: [] = mem::transmute(address); + + type [] = unsafe extern "C" fn ($( $ty ),+ ); + + [] + .initialize(target, [<$name _detour_fkt>])? + .enable()?; + + Ok(()) + } + } + + + }; +} + +// #[macro_export] +// macro_rules! CREATE_HOOK { + +// // ($name:ident, $mode:ident, $rettype:ident, ( $( $call_type:ident: $pattern:expr ),+ $(,)? )) => { +// // ($name:ident, $mode:ident, $rettype:ident, ( $( $call_type:ident: $pattern:expr ),+ $(,)? )) => { +// // println!($name); +// // println!($mode); +// // println!($rettype); + +// // [ $( $call_type ($pattern) ),+ ]; +// // }; +// ($name:ident, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:block) => { +// paste::paste! { +// #[allow(non_snake_case)] +// pub unsafe fn [](base_address: usize, offsets: HashMap) -> Result<(), Box>{ + +// // ( $( $arg: $ty ),+ ); +// let address = base_address + offsets[stringify![$name]] as usize; +// let target: [] = mem::transmute(address); + +// type [] = unsafe extern "C" fn ($( $ty ),+ ); + +// static_detour! { +// pub static []: unsafe extern "C" fn ($( $ty ),+ ); +// } + +// pub fn detour_fkt( $( $arg: $ty ),+ ) { +// // println!("rust $name delta: {}", delta); +// $body +// unsafe { [].call ( $( $arg ),+ ) } +// } + +// [] +// .initialize(target, detour_fkt)? +// .enable()?; + +// Ok(()) +// } +// } + + +// }; +// } + use std::{future::Future, pin::Pin}; use patternsleuth::resolvers::{AsyncContext, ResolveError}; @@ -259,8 +371,9 @@ macro_rules! define_process { } // custom handlers - define_pattern_resolver!(@emit_header DefaultResult); + +#[allow(dead_code)] impl DefaultResult { pub fn offset(&self) -> usize { self.0 // assuming it's something like `pub struct DefaultResult(pub usize)` @@ -284,6 +397,7 @@ pub struct Signature<'a> { pub signature_string: String, } +#[allow(dead_code)] impl<'a> Signature<'a> { pub async fn calculate_offset(&self, ctx: &'a AsyncContext<'a>) -> Result { (self.offset_calculator)(ctx).await diff --git a/sleuth/src/resolvers/mod.rs b/sleuth/src/resolvers/mod.rs index 144838d..a72ce44 100644 --- a/sleuth/src/resolvers/mod.rs +++ b/sleuth/src/resolvers/mod.rs @@ -1,5 +1,6 @@ #[macro_use] mod macros; +// use dll_hook::ue; use once_cell::sync::OnceCell; @@ -14,6 +15,7 @@ pub enum PlatformType { } pub static PLATFORM: OnceCell = OnceCell::new(); +pub static BASE_ADDR: OnceCell = OnceCell::new(); pub fn current_platform() -> PlatformType { *PLATFORM.get().expect("Platform not initialized") @@ -38,10 +40,86 @@ impl std::str::FromStr for PlatformType { } } } +use std::ffi::OsStr; +use std::os::raw::c_int; +use std::os::windows::ffi::OsStrExt; +use std::ptr::null_mut; +#[repr(C)] +pub struct FString { + pub str: *mut u16, // wchar_t* == *mut u16 on Windows + pub letter_count: c_int, + pub max_letters: c_int, +} + +impl FString { + /// Create an FString from a Rust `&str` (or `String`) + pub fn from_string(s: &str) -> (Self, Box<[u16]>) { + // Convert to UTF-16 and null-terminate + let wide: Vec = OsStr::new(s) + .encode_wide() + .chain(std::iter::once(0)) // null terminator + .collect(); + + let letter_count = wide.len() as c_int; + + // Box it to keep it alive (heap allocation) + let boxed = wide.into_boxed_slice(); + let ptr = boxed.as_ptr() as *mut u16; + + // Return both FString and the boxed data + ( + FString { + str: ptr, + letter_count, + max_letters: letter_count, + }, + boxed, + ) + } + /// Creates an `FString` from a Rust wide string slice (`&[u16]` or `Vec`) + pub fn new_from_wide_str(wide: &[u16]) -> Self { + let letter_count = (wide.len()+1) as c_int; + Self { + str: wide.as_ptr() as *mut u16, + letter_count, + max_letters: letter_count, + } + } + + /// Creates an `FString` from a wide string pointer (like `*const u16`) + /// WARNING: This assumes the pointer is null-terminated. + pub unsafe fn from_ptr(ptr: *const u16) -> Self { + if ptr.is_null() { + return Self { + str: null_mut(), + letter_count: 0, + max_letters: 0, + }; + } + + // Count the number of wide characters (including null terminator) + let mut len = 0; + while *ptr.add(len) != 0 { + len += 1; + } + let letter_count = len as c_int + 1; + + Self { + str: ptr as *mut u16, + letter_count, + max_letters: letter_count, + } + } +} + + +pub mod hook_retour; pub mod admin_control; pub mod asset_loading; pub mod backend_hooks; pub mod etc_hooks; pub mod ownership_overrides; -pub mod unchained_integration; \ No newline at end of file +pub mod unchained_integration; +pub mod rcon; + diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs new file mode 100644 index 0000000..6038c10 --- /dev/null +++ b/sleuth/src/resolvers/rcon.rs @@ -0,0 +1,46 @@ +use std::{ + io::{BufRead, BufReader}, + net::TcpListener, + sync::{Arc, Mutex}, + thread, +}; + +use once_cell::sync::Lazy; + +fn get_rcon_port() -> Option { + Some(9001) +} +pub static COMMAND_PENDING: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); +pub static LAST_COMMAND: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); + +pub fn handle_rcon() { + let port = match get_rcon_port() { + Some(p) => p, + None => return, + }; + + let listener = TcpListener::bind(("127.0.0.1", port)) + .expect("[Rust RCON]: Failed to bind to port"); + + println!("[Rust RCON]: Listening on 127.0.0.1:{}", port); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + let cmd_store = Arc::clone(&LAST_COMMAND); + let cmd_pending = Arc::clone(&COMMAND_PENDING); + thread::spawn(move || { + let reader = BufReader::new(stream); + for line in reader.lines().flatten() { + if !line.trim().is_empty() { + println!("[Rust RCON]: Received: {}", line.trim()); + *cmd_store.lock().unwrap() = Some(line.trim().to_string()); + *cmd_pending.lock().unwrap() = Some(true); + } + } + }); + } + Err(e) => eprintln!("[Rust RCON]: Connection failed: {}", e), + } + } +} diff --git a/sleuth/src/scan.rs b/sleuth/src/scan.rs index de6bd94..ab8b7c2 100644 --- a/sleuth/src/scan.rs +++ b/sleuth/src/scan.rs @@ -1,25 +1,66 @@ use std::collections::HashMap; -use patternsleuth::resolvers::resolvers; +use patternsleuth::resolvers::{resolvers, unreal::game_loop::UGameEngineTick, NamedResolver, Resolution}; +use serde::{Deserialize, Serialize}; use std::process; +// pub static RESOLUTION: +// Vec, +// patternsleuth::resolvers::ResolveError +// >> = Vec::new(); + +use once_cell::sync::{Lazy, OnceCell}; +use std::sync::Arc; +use patternsleuth::resolvers::{ResolveError}; + +use crate::resolvers; + + +pub static OFFSETS: OnceCell> = OnceCell::new(); +pub static RESOLUTION: OnceCell, ResolveError>>> = OnceCell::new(); +// pub static FN_OFFSETS: OnceCell> = OnceCell::new(); +// fn find_resolution(name: UGameEngineTick) -> Option> { +// RESOLUTION +// .get()? +// .iter() +// .filter_map(Result::as_ref) // Skip Err entries +// .find(|res| res == name) // Replace with your identifier logic +// .cloned() +// } +// fn find_resolution(name: &str) -> Option> { +// RESOLUTION +// .get()? +// .iter() +// .filter_map(Result::as_ref) // Skip Err +// .find(|res| res.name() == name) +// .cloned() +// } + + pub fn scan() -> Result, String> { let pid = Some(process::id() as i32); let resolvers = resolvers().collect::>(); let dyn_resolvers = resolvers.iter().map(|res| res.getter).collect::>(); - let name = format!("PID={}", pid.unwrap()); + // let name = format!("PID={}", pid.unwrap()); let game_name = format!("pid={}", pid.unwrap()); // fixme let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string())?; - println!("GAME '{:?}' '{:x?}'", name, exe.base_address); + // println!("GAME '{:?}' '{:x?}'", name, exe.base_address); + // RESOLUTION.set( + // tracing::info_span!("scan", game = game_name) + // .in_scope(|| exe.resolve_many(&dyn_resolvers)) + // ).unwrap(); // Or handle failure let resolution = tracing::info_span!("scan", game = game_name) .in_scope(|| exe.resolve_many(&dyn_resolvers)); // get Names and offsets from resolution - let mut offsets = HashMap::new(); + let mut offsets: HashMap = HashMap::new(); + let mut offsets_resolver: HashMap = HashMap::new(); + // let mut offsets_resolver: HashMap<*const (), usize> = HashMap::new(); for (resolver, resolution) in resolvers.iter().zip(&resolution) { if let Ok(r) = resolution { // FIXME: Less nasty way? @@ -32,13 +73,35 @@ pub fn scan() -> Result, String> { // sigs_json.insert(MyItem { id: resolver.name.to_string(), name: hex.to_string() }); let val = u64::from_str_radix(hex.trim_start_matches("0x"), 16).map_err(|e| e.to_string())?; let base = exe.base_address as u64; - println!("{} {} {} {:x?}", resolver.name, hex, val, (val-base) & 0xFFFFFFF); + // println!("{} {} {} {:x?}", resolver.name, hex, val, (val-base) & 0xFFFFFFF); offsets.insert(resolver.name.to_string(), (val-base) & 0xFFFFFFF); + offsets_resolver.insert(resolver.name.to_string(), val as usize); + // let ptr = Arc::as_ptr(resolver) as *const (); + // offsets_resolver.insert(resolver as *const (), val as usize); } } } // let res = dump_builds(offsets); + // let converted: HashMap = offsets_resolver + // .iter() + // .map(|(k, v)| (k.clone(), *v as usize)) + // .collect(); + + let _ = OFFSETS.set(offsets_resolver); // Safe, only allowed once + + Ok(offsets) // Return the original u64-based map +} + + +// patternsleuth::_impl_try_collector! { +// #[derive(Debug, PartialEq, Clone, Serialize)] +// struct DllHookResolution { +// game_tick: patternsleuth::resolvers::unreal::game_loop::UGameEngineTick, +// } +// } +// static mut GLOBALS: Option = None; - Ok(offsets) -} \ No newline at end of file +// pub struct Globals { +// resolution: DllHookResolution +// } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4b644e8..c3f0d71 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -214,7 +214,7 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { } GLOG_INFO("Continuing to RCON"); - handleRCON(); //this has an infinite loop for commands! Keep this at the end! + // handleRCON(); //this has an infinite loop for commands! Keep this at the end! ExitThread(0); } catch (const std::exception& e) { From ad4714221581a76886350daf085f02352fa40ce8 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Sun, 8 Jun 2025 01:54:42 +0200 Subject: [PATCH 02/18] add ue.rs from Patternsleuth --- sleuth/src/ue.rs | 702 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 702 insertions(+) create mode 100644 sleuth/src/ue.rs diff --git a/sleuth/src/ue.rs b/sleuth/src/ue.rs new file mode 100644 index 0000000..ce927b5 --- /dev/null +++ b/sleuth/src/ue.rs @@ -0,0 +1,702 @@ +// From trumank/patternsleuth +// https://github.com/trumank/patternsleuth/blob/master/examples/dll_hook/src/ue.rs +// FIXME: the file is unchanged. Find a way to get use it via git without submodules + +use std::{ + cell::UnsafeCell, + ffi::c_void, + fmt::Display, + ops::{Deref, DerefMut}, +}; + +use windows::Win32::System::Threading::{ + EnterCriticalSection, LeaveCriticalSection, CRITICAL_SECTION, +}; + +use crate::globals; + +pub type FnFFrameStep = + unsafe extern "system" fn(stack: &mut kismet::FFrame, *mut UObject, result: *mut c_void); +pub type FnFFrameStepExplicitProperty = unsafe extern "system" fn( + stack: &mut kismet::FFrame, + result: *mut c_void, + property: *const FProperty, +); + +pub type FnFNameToString = unsafe extern "system" fn(&FName, &mut FString); +impl Display for FName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut string = FString::new(); + unsafe { + (globals().fname_to_string())(self, &mut string); + }; + write!(f, "{string}") + } +} + +pub type FnUObjectBaseUtilityGetPathName = + unsafe extern "system" fn(&UObjectBase, Option<&UObject>, &mut FString); +impl UObjectBase { + pub fn get_path_name(&self, stop_outer: Option<&UObject>) -> String { + let mut string = FString::new(); + unsafe { + (globals().uobject_base_utility_get_path_name())(self, stop_outer, &mut string); + } + string.to_string() + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct FMalloc { + vtable: *const FMallocVTable, +} +unsafe impl Sync for FMalloc {} +unsafe impl Send for FMalloc {} +impl FMalloc { + pub fn malloc(&self, count: usize, alignment: u32) -> *mut c_void { + unsafe { ((*self.vtable).malloc)(self, count, alignment) } + } + pub fn realloc(&self, original: *mut c_void, count: usize, alignment: u32) -> *mut c_void { + unsafe { ((*self.vtable).realloc)(self, original, count, alignment) } + } + pub fn free(&self, original: *mut c_void) { + unsafe { ((*self.vtable).free)(self, original) } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct FMallocVTable { + pub __vec_del_dtor: *const (), + pub exec: *const (), + pub malloc: + unsafe extern "system" fn(this: &FMalloc, count: usize, alignment: u32) -> *mut c_void, + pub try_malloc: + unsafe extern "system" fn(this: &FMalloc, count: usize, alignment: u32) -> *mut c_void, + pub realloc: unsafe extern "system" fn( + this: &FMalloc, + original: *mut c_void, + count: usize, + alignment: u32, + ) -> *mut c_void, + pub try_realloc: unsafe extern "system" fn( + this: &FMalloc, + original: *mut c_void, + count: usize, + alignment: u32, + ) -> *mut c_void, + pub free: unsafe extern "system" fn(this: &FMalloc, original: *mut c_void), + pub quantize_size: *const (), + pub get_allocation_size: *const (), + pub trim: *const (), + pub setup_tls_caches_on_current_thread: *const (), + pub clear_and_disable_tlscaches_on_current_thread: *const (), + pub initialize_stats_metadata: *const (), + pub update_stats: *const (), + pub get_allocator_stats: *const (), + pub dump_allocator_stats: *const (), + pub is_internally_thread_safe: *const (), + pub validate_heap: *const (), + pub get_descriptive_name: *const (), +} + +#[derive(Debug)] +#[repr(C)] +pub struct FWindowsCriticalSection(UnsafeCell); +impl FWindowsCriticalSection { + fn crit_ptr_mut(&self) -> *mut CRITICAL_SECTION { + &self.0 as *const _ as *mut _ + } + unsafe fn lock(&self) { + simple_log::info!("LOCKING objects"); + EnterCriticalSection(self.crit_ptr_mut()); + } + unsafe fn unlock(&self) { + simple_log::info!("UNLOCKING objects"); + LeaveCriticalSection(self.crit_ptr_mut()); + } +} + +pub struct CriticalSectionGuard<'crit, 'data, T: ?Sized + 'data> { + critical_section: &'crit FWindowsCriticalSection, + data: &'data UnsafeCell, +} +impl<'crit, 'data, T: ?Sized> CriticalSectionGuard<'crit, 'data, T> { + fn lock(critical_section: &'crit FWindowsCriticalSection, data: &'data UnsafeCell) -> Self { + unsafe { + critical_section.lock(); + } + Self { + critical_section, + data, + } + } +} +impl Drop for CriticalSectionGuard<'_, '_, T> { + fn drop(&mut self) { + unsafe { self.critical_section.unlock() } + } +} +impl Deref for CriticalSectionGuard<'_, '_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.data.get() } + } +} +impl DerefMut for CriticalSectionGuard<'_, '_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.data.get() } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct FUObjectCreateListener; + +#[derive(Debug)] +#[repr(C)] +pub struct FUObjectDeleteListener; + +type ObjectIndex = i32; + +#[derive(Debug)] +#[repr(C)] +pub struct FUObjectArray { + obj_first_gcindex: i32, + obj_last_non_gcindex: i32, + max_objects_not_considered_by_gc: i32, + open_for_disregard_for_gc: bool, + + obj_objects: UnsafeCell, + obj_objects_critical: FWindowsCriticalSection, + obj_available_list: [u8; 0x88], + uobject_create_listeners: TArray<*const FUObjectCreateListener>, + uobject_delete_listeners: TArray<*const FUObjectDeleteListener>, + uobject_delete_listeners_critical: FWindowsCriticalSection, + master_serial_number: std::sync::atomic::AtomicI32, +} +impl FUObjectArray { + pub fn objects(&self) -> CriticalSectionGuard<'_, '_, FChunkedFixedUObjectArray> { + CriticalSectionGuard::lock(&self.obj_objects_critical, &self.obj_objects) + } + pub fn allocate_serial_number(&self, index: ObjectIndex) -> i32 { + use std::sync::atomic::Ordering; + + let objects = unsafe { &*self.obj_objects.get() }; + let item = objects.item(index); + + let current = item.serial_number.load(Ordering::SeqCst); + if current != 0 { + current + } else { + let new = self.master_serial_number.fetch_add(1, Ordering::SeqCst); + + let exchange = + item.serial_number + .compare_exchange(0, new, Ordering::SeqCst, Ordering::SeqCst); + match exchange { + Ok(_) => new, + Err(old) => old, + } + } + } +} + +pub struct ObjectIterator<'a> { + array: &'a FChunkedFixedUObjectArray, + index: i32, +} +impl<'a> Iterator for ObjectIterator<'a> { + type Item = Option<&'a UObjectBase>; + fn size_hint(&self) -> (usize, Option) { + let size = self.array.num_elements as usize; + (size, Some(size)) + } + fn nth(&mut self, n: usize) -> Option { + let n = n as i32; + if self.index < n { + self.index = n; + } + self.next() + } + fn next(&mut self) -> Option> { + if self.index >= self.array.num_elements { + None + } else { + let obj = unsafe { self.array.item(self.index).object.as_ref() }; + + self.index += 1; + Some(obj) + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct FChunkedFixedUObjectArray { + pub objects: *const *const FUObjectItem, + pub pre_allocated_objects: *const FUObjectItem, + pub max_elements: i32, + pub num_elements: i32, + pub max_chunks: i32, + pub num_chunks: i32, +} +impl FChunkedFixedUObjectArray { + pub fn iter(&self) -> ObjectIterator<'_> { + ObjectIterator { + array: self, + index: 0, + } + } + fn item_ptr(&self, index: ObjectIndex) -> *const FUObjectItem { + let per_chunk = self.max_elements / self.max_chunks; + + unsafe { + (*self.objects.add((index / per_chunk) as usize)).add((index % per_chunk) as usize) + } + } + fn item(&self, index: ObjectIndex) -> &FUObjectItem { + unsafe { &*self.item_ptr(index) } + } + fn item_mut(&mut self, index: ObjectIndex) -> &mut FUObjectItem { + unsafe { &mut *(self.item_ptr(index) as *mut FUObjectItem) } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct FUObjectItem { + pub object: *const UObjectBase, + pub flags: i32, + pub cluster_root_index: i32, + pub serial_number: std::sync::atomic::AtomicI32, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FWeakObjectPtr { + object_index: i32, + object_serial_number: i32, +} +impl FWeakObjectPtr { + pub fn new(object: &UObjectBase) -> Self { + Self::new_from_index(object.internal_index) + } + pub fn new_from_index(index: ObjectIndex) -> Self { + Self { + object_index: index, + // serial allocation performs only atomic operations + object_serial_number: unsafe { + globals() + .guobject_array_unchecked() + .allocate_serial_number(index) + }, + } + } + pub fn get(&self, object_array: &FUObjectArray) -> Option<&UObjectBase> { + // TODO check valid + unsafe { + let objects = &*object_array.obj_objects.get(); + let item = objects.item(self.object_index); + Some(&*item.object) + } + } +} + +bitflags::bitflags! { + #[derive(Debug, Clone)] + pub struct EObjectFlags: u32 { + const RF_NoFlags = 0x0000; + const RF_Public = 0x0001; + const RF_Standalone = 0x0002; + const RF_MarkAsNative = 0x0004; + const RF_Transactional = 0x0008; + const RF_ClassDefaultObject = 0x0010; + const RF_ArchetypeObject = 0x0020; + const RF_Transient = 0x0040; + const RF_MarkAsRootSet = 0x0080; + const RF_TagGarbageTemp = 0x0100; + const RF_NeedInitialization = 0x0200; + const RF_NeedLoad = 0x0400; + const RF_KeepForCooker = 0x0800; + const RF_NeedPostLoad = 0x1000; + const RF_NeedPostLoadSubobjects = 0x2000; + const RF_NewerVersionExists = 0x4000; + const RF_BeginDestroyed = 0x8000; + const RF_FinishDestroyed = 0x00010000; + const RF_BeingRegenerated = 0x00020000; + const RF_DefaultSubObject = 0x00040000; + const RF_WasLoaded = 0x00080000; + const RF_TextExportTransient = 0x00100000; + const RF_LoadCompleted = 0x00200000; + const RF_InheritableComponentTemplate = 0x00400000; + const RF_DuplicateTransient = 0x00800000; + const RF_StrongRefOnFrame = 0x01000000; + const RF_NonPIEDuplicateTransient = 0x02000000; + const RF_Dynamic = 0x04000000; + const RF_WillBeLoaded = 0x08000000; + } +} +bitflags::bitflags! { + #[derive(Debug, Clone)] + pub struct EFunctionFlags: u32 { + const FUNC_None = 0x0000; + const FUNC_Final = 0x0001; + const FUNC_RequiredAPI = 0x0002; + const FUNC_BlueprintAuthorityOnly = 0x0004; + const FUNC_BlueprintCosmetic = 0x0008; + const FUNC_Net = 0x0040; + const FUNC_NetReliable = 0x0080; + const FUNC_NetRequest = 0x0100; + const FUNC_Exec = 0x0200; + const FUNC_Native = 0x0400; + const FUNC_Event = 0x0800; + const FUNC_NetResponse = 0x1000; + const FUNC_Static = 0x2000; + const FUNC_NetMulticast = 0x4000; + const FUNC_UbergraphFunction = 0x8000; + const FUNC_MulticastDelegate = 0x00010000; + const FUNC_Public = 0x00020000; + const FUNC_Private = 0x00040000; + const FUNC_Protected = 0x00080000; + const FUNC_Delegate = 0x00100000; + const FUNC_NetServer = 0x00200000; + const FUNC_HasOutParms = 0x00400000; + const FUNC_HasDefaults = 0x00800000; + const FUNC_NetClient = 0x01000000; + const FUNC_DLLImport = 0x02000000; + const FUNC_BlueprintCallable = 0x04000000; + const FUNC_BlueprintEvent = 0x08000000; + const FUNC_BlueprintPure = 0x10000000; + const FUNC_EditorOnly = 0x20000000; + const FUNC_Const = 0x40000000; + const FUNC_NetValidate = 0x80000000; + const FUNC_AllFlags = 0xffffffff; + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct UObjectBase { + pub vtable: *const c_void, + pub object_flags: EObjectFlags, + pub internal_index: i32, + pub class_private: *const UClass, + pub name_private: FName, + pub outer_private: *const UObject, +} + +#[derive(Debug)] +#[repr(C)] +pub struct UObjectBaseUtility { + pub uobject_base: UObjectBase, +} + +#[derive(Debug)] +#[repr(C)] +pub struct UObject { + pub uobject_base_utility: UObjectBaseUtility, +} + +#[derive(Debug)] +#[repr(C)] +struct FOutputDevice { + vtable: *const c_void, + b_suppress_event_tag: bool, + b_auto_emit_line_terminator: bool, +} + +#[derive(Debug)] +#[repr(C)] +pub struct UField { + pub uobject: UObject, + pub next: *const UField, +} + +#[derive(Debug)] +#[repr(C)] +pub struct FStructBaseChain { + pub struct_base_chain_array: *const *const FStructBaseChain, + pub num_struct_bases_in_chain_minus_one: i32, +} + +#[derive(Debug)] +#[repr(C)] +struct FFieldClass { + // TODO + name: FName, +} + +#[derive(Debug)] +#[repr(C)] +struct FFieldVariant { + container: *const c_void, + b_is_uobject: bool, +} + +#[derive(Debug)] +#[repr(C)] +pub struct FField { + class_private: *const FFieldClass, + owner: FFieldVariant, + next: *const FField, + name_private: FName, + flags_private: EObjectFlags, +} + +pub struct FProperty { + // TODO +} + +#[derive(Debug)] +#[repr(C)] +pub struct UStruct { + pub ufield: UField, + pub fstruct_base_chain: FStructBaseChain, + pub super_struct: *const UStruct, + pub children: *const UField, + pub child_properties: *const FField, + pub properties_size: i32, + pub min_alignment: i32, + pub script: TArray, + pub property_link: *const FProperty, + pub ref_link: *const FProperty, + pub destructor_link: *const FProperty, + pub post_construct_link: *const FProperty, + pub script_and_property_object_references: TArray<*const UObject>, + pub unresolved_script_properties: *const (), //TODO pub TArray,int>,TSizedDefaultAllocator<32> >* + pub unversioned_schema: *const (), //TODO const FUnversionedStructSchema* +} + +#[derive(Debug)] +#[repr(C)] +pub struct UFunction { + pub ustruct: UStruct, + pub function_flags: EFunctionFlags, + pub num_parms: u8, + pub parms_size: u16, + pub return_value_offset: u16, + pub rpc_id: u16, + pub rpc_response_id: u16, + pub first_property_to_init: *const FProperty, + pub event_graph_function: *const UFunction, + pub event_graph_call_offset: i32, + pub func: unsafe extern "system" fn(*mut UObject, *mut kismet::FFrame, *mut c_void), +} + +#[derive(Debug)] +#[repr(C)] +pub struct UClass { + pub ustruct: UStruct, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FName { + pub comparison_index: FNameEntryId, + pub number: u32, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FNameEntryId { + pub value: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct TSharedPtr { + pub object: *const T, + pub reference_controller: *const FReferenceControllerBase, +} + +#[derive(Debug)] +#[repr(C)] +pub struct FReferenceControllerBase { + pub shared_reference_count: i32, + pub weak_reference_count: i32, +} + +pub type FString = TArray; + +#[derive(Debug)] +#[repr(C)] +pub struct TArray { + data: *const T, + num: i32, + max: i32, +} +impl TArray { + fn new() -> Self { + Self { + data: std::ptr::null(), + num: 0, + max: 0, + } + } +} +impl Drop for TArray { + fn drop(&mut self) { + unsafe { + std::ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut( + self.data.cast_mut(), + self.num as usize, + )) + } + globals().gmalloc().free(self.data as *mut c_void); + } +} +impl Default for TArray { + fn default() -> Self { + Self { + data: std::ptr::null(), + num: 0, + max: 0, + } + } +} +impl TArray { + pub fn with_capacity(capacity: usize) -> Self { + Self { + data: globals().gmalloc().malloc( + capacity * std::mem::size_of::(), + std::mem::align_of::() as u32, + ) as *const T, + num: 0, + max: capacity as i32, + } + } + pub fn len(&self) -> usize { + self.num as usize + } + pub fn capacity(&self) -> usize { + self.max as usize + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn as_slice(&self) -> &[T] { + if self.num == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(self.data, self.num as usize) } + } + } + pub fn as_mut_slice(&mut self) -> &mut [T] { + if self.num == 0 { + &mut [] + } else { + unsafe { std::slice::from_raw_parts_mut(self.data as *mut _, self.num as usize) } + } + } + pub fn clear(&mut self) { + let elems: *mut [T] = self.as_mut_slice(); + + unsafe { + self.num = 0; + std::ptr::drop_in_place(elems); + } + } + pub fn push(&mut self, new_value: T) { + if self.num >= self.max { + self.max = u32::next_power_of_two((self.max + 1) as u32) as i32; + let new = globals().gmalloc().realloc( + self.data as *mut c_void, + self.max as usize * std::mem::size_of::(), + std::mem::align_of::() as u32, + ) as *const T; + self.data = new; + } + unsafe { + std::ptr::write(self.data.add(self.num as usize).cast_mut(), new_value); + } + self.num += 1; + } +} + +impl From<&[T]> for TArray +where + T: Copy, +{ + fn from(value: &[T]) -> Self { + let mut new = Self::with_capacity(value.len()); + // TODO this is probably unsound + new.num = value.len() as i32; + new.as_mut_slice().copy_from_slice(value); + new + } +} + +impl Display for FString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let slice = self.as_slice(); + let last = slice.len() + - slice + .iter() + .cloned() + .rev() + .position(|c| c != 0) + .unwrap_or_default(); + write!( + f, + "{}", + widestring::U16Str::from_slice(&slice[..last]) + .to_string() + .unwrap() + ) + } +} + +#[derive(Debug, Default)] +#[repr(C)] +pub struct FVector { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Debug, Default)] +#[repr(C)] +pub struct FLinearColor { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +pub mod kismet { + use super::*; + + #[derive(Debug)] + #[repr(C)] + pub struct FFrame { + pub base: FOutputDevice, + pub node: *const c_void, + pub object: *mut UObject, + pub code: *const c_void, + pub locals: *const c_void, + pub most_recent_property: *const FProperty, + pub most_recent_property_address: *const c_void, + pub flow_stack: [u8; 0x30], + pub previous_frame: *const FFrame, + pub out_parms: *const c_void, + pub property_chain_for_compiled_in: *const FField, + pub current_native_function: *const c_void, + pub b_array_context_failed: bool, + } + + pub fn arg(stack: &mut FFrame, output: &mut T) { + let output = output as *const _ as *mut _; + unsafe { + if stack.code.is_null() { + let cur = stack.property_chain_for_compiled_in; + stack.property_chain_for_compiled_in = (*cur).next; + (globals().fframe_step_explicit_property())(stack, output, cur as *const FProperty); + } else { + (globals().fframe_step())(stack, stack.object, output); + } + } + } +} From b342f22e4706ecebcd7b482fb9bac6ed8f9e8a9f Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Sun, 8 Jun 2025 01:58:41 +0200 Subject: [PATCH 03/18] - use ue types - add globals for ue types - cleanup UGameEngineTick hook - drop old FString impl --- sleuth/CMakeLists.txt | 6 + sleuth/Cargo.toml | 36 +++--- sleuth/src/lib.rs | 142 +++++++++++++++++++-- sleuth/src/resolvers/admin_control.rs | 175 +++----------------------- sleuth/src/resolvers/mod.rs | 144 ++++++++++----------- sleuth/src/resolvers/rcon.rs | 3 +- sleuth/src/scan.rs | 50 +------- 7 files changed, 251 insertions(+), 305 deletions(-) diff --git a/sleuth/CMakeLists.txt b/sleuth/CMakeLists.txt index e400705..30932d1 100644 --- a/sleuth/CMakeLists.txt +++ b/sleuth/CMakeLists.txt @@ -41,4 +41,10 @@ target_link_libraries(${PROJECT_NAME}_interface INTERFACE winspool.lib ws2_32.lib psapi.lib + bcrypt.lib + crypt32.lib + cryptnet.lib + dbghelp.lib + legacy_stdio_definitions.lib + ncrypt.lib ) diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 1f342ca..dd6a43a 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] winapi = { version = "0.3.9", features = ["winnt", "wincrypt", "winuser"] } patternsleuth = { git = "https://github.com/Knutschbert/patternsleuth.git", rev = "644f980", features = ["process-internal", "serde-resolvers", "image-pe"] } -# dll_hook = { git = "https://github.com/Knutschbert/patternsleuth.git", rev = "644f980", package = "dll_hook" } +dll_hook = { git = "https://github.com/Knutschbert/patternsleuth.git", rev = "644f980", package = "dll_hook" } # patternsleuth = { path = "../../patternsleuth/patternsleuth", features = ["process-internal", "symbols", "serde-resolvers", "image-pe", "image-elf"] } # dll_hook = { path = "../../patternsleuth/examples/dll_hook"} anyhow = "1.0.79" @@ -28,27 +28,27 @@ bitflags = "2.4.1" # egui-winit = { version = "0.24.0", default-features = false } indexmap = "2.1.0" parking_lot = "0.12.1" -# simple-log = "1.6.0" +simple-log = "1.6.0" # thread_local = "1.1.7" retour = { version = "0.3.1", features = ["static-detour"]} -# windows = { version = "0.48.0", features = ["Win32_Foundation", -# "Win32_System_Console", -# "Win32_System_LibraryLoader", -# "Win32_System_SystemServices", -# "Win32_Graphics_Gdi", -# "Win32_Graphics_Direct3D", -# "Win32_Graphics_Direct3D11", -# "Win32_Graphics_Dxgi", -# "Win32_Graphics_Dxgi_Common", -# "Win32_UI_WindowsAndMessaging", -# "Win32_System_Threading", +windows = { version = "0.52.0", features = ["Win32_Foundation", + "Win32_System_Console", + "Win32_System_LibraryLoader", + "Win32_System_SystemServices", + "Win32_Graphics_Gdi", + "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D11", + "Win32_Graphics_Dxgi", + "Win32_Graphics_Dxgi_Common", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Threading", -# "Win32_Security", -# "Win32_System_Kernel", -# "Win32_System_Memory", -# "Win32_System_ProcessStatus", -# ]} + "Win32_Security", + "Win32_System_Kernel", + "Win32_System_Memory", + "Win32_System_ProcessStatus", + ]} # [target."cfg(windows)".dev-dependencies.windows] # version = "0.48" diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 8a55351..e3f9a7d 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -8,10 +8,23 @@ use std::io::{BufReader, Read}; use std::io::{BufWriter, Write}; use std::collections::HashMap; use std::path::PathBuf; +mod ue; use anyhow::Result; +use patternsleuth::resolvers::unreal::blueprint_library::UFunctionBind; +use patternsleuth::resolvers::unreal::*; +// use dll_hook::ue::*; +use patternsleuth::resolvers::unreal::game_loop::FEngineLoopInit; +use patternsleuth::resolvers::unreal::kismet::{FFrameStep, FFrameStepExplicitProperty, FFrameStepViaExec}; +use patternsleuth::resolvers::unreal::KismetSystemLibrary; +use patternsleuth::resolvers::unreal::{fname::FNameToString, + game_loop::UGameEngineTick, + gmalloc::GMalloc, + guobject_array::{FUObjectArrayAllocateUObjectIndex, FUObjectArrayFreeUObjectIndex, GUObjectArray} + }; use serde::Serialize; use serde_json::to_writer_pretty; +use winapi::um::winnt::FILE_APPEND_DATA; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; // IEEE @@ -46,7 +59,7 @@ struct BuildInfo { name: String, platform: String, path: String, - offsets: HashMap, + offsets: HashMap, } fn expand_env_path(path: &str) -> Option { @@ -58,23 +71,32 @@ fn expand_env_path(path: &str) -> Option { None } -pub fn dump_builds(offsets: HashMap) -> Result<()> { +pub fn dump_builds() -> Result<()> { let builds_path = expand_env_path(r"%LOCALAPPDATA%\Chivalry 2\Saved\Config\c2uc.builds.json").unwrap().to_path_buf(); println!("JSON PATH {}", builds_path.to_string_lossy()); let file = File::create(builds_path)?; let mut writer = BufWriter::new(file); let mut data = HashMap::new(); - let mut file_path = String::new(); + // let mut file_path = String::new(); - match env::current_exe() { - Ok(path) => file_path = path.to_string_lossy().into(), - Err(e) => eprintln!("Failed to get path: {}", e), - } + let offsets = crate::scan::OFFSETS.get().unwrap(); + let base_addr = BASE_ADDR.get().unwrap(); + + let mut file_path: String = env::current_exe().unwrap().to_string_lossy().into(); + + // match env::current_exe() { + // Ok(path) => file_path = path.to_string_lossy().into(), + // Err(e) => eprintln!("Failed to get path: {}", e), + // } println!("Current executable path: {:?}", file_path); let crc32 = unsafe { crc32_from_file(&file_path) }.expect("Failed to compute CRC"); + let base_offsets: HashMap = offsets + .iter() + .map(|(k, v)| (k.clone(), v - base_addr)) + .collect(); let build_info = BuildInfo { build: 0, @@ -82,7 +104,7 @@ pub fn dump_builds(offsets: HashMap) -> Result<()> { name: "".to_string(), platform: PLATFORM.get().ok_or("OTHER").unwrap().to_string(),//platform.to_string(), path: file_path.to_string(), - offsets, + offsets: base_offsets, }; data.insert(crc32.to_string(), build_info); @@ -97,10 +119,31 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - // attach_GameEngineTick(base_address, offsets).unwrap(); resolvers::admin_control::attach_UGameEngineTick(base_address, offsets.clone()).unwrap(); - resolvers::admin_control::attach_ExecuteConsoleCommand(base_address, offsets).unwrap(); + resolvers::admin_control::attach_ExecuteConsoleCommand(base_address, offsets.clone()).unwrap(); + resolvers::admin_control::attach_FEngineLoopInit(base_address, offsets).unwrap(); Ok(()) } +unsafe fn init_globals() { + + let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); + println!("starting scan"); + let resolution = exe.resolve(DllHookResolution::resolver()).unwrap(); + println!("finished scan"); + + println!("results: {:?}", resolution); + let guobject_array: &'static ue::FUObjectArray = + &*(resolution.guobject_array.0 as *const ue::FUObjectArray); + + GLOBALS = Some(Globals { + guobject_array: guobject_array.into(), + resolution, + main_thread_id: std::thread::current().id(), + last_command: None, + }); + +} + #[no_mangle] pub extern "C" fn generate_json() -> u8 { println!("test asd"); @@ -119,6 +162,8 @@ pub extern "C" fn generate_json() -> u8 { let exe = image; println!("GAME '{:x?}'", exe.base_address); BASE_ADDR.set(exe.base_address).expect("BASE_ADDR already set"); + + unsafe { init_globals() }; // let scan = scan::scan(); let offsets = scan::scan().expect("Failed to scan"); let len_u8 = offsets.len() as u8; @@ -128,6 +173,83 @@ pub extern "C" fn generate_json() -> u8 { unsafe { attach_hooks(exe.base_address, offsets).unwrap(); } - dump_builds(offset_copy).expect("Failed to dump builds JSON"); + dump_builds().expect("Failed to dump builds JSON"); len_u8 } + +patternsleuth::_impl_try_collector! { + #[derive(Debug, PartialEq, Clone)] + struct DllHookResolution { + gmalloc: GMalloc, + guobject_array: GUObjectArray, + fnametostring: FNameToString, + allocate_uobject: FUObjectArrayAllocateUObjectIndex, + free_uobject: FUObjectArrayFreeUObjectIndex, + game_tick: UGameEngineTick, + engine_loop_init: FEngineLoopInit, + kismet_system_library: KismetSystemLibrary, + fframe_step_via_exec: FFrameStepViaExec, + fframe_step: FFrameStep, + fframe_step_explicit_property: FFrameStepExplicitProperty, + // fframe_kismet_execution_message: FFrameKismetExecutionMessage, + ufunction_bind: UFunctionBind, + uobject_base_utility_get_path_name: UObjectBaseUtilityGetPathName, + } +} + + +use serde::{Serializer, Deserialize, Deserializer}; + +// Stubs to disable unimplemented err +impl Serialize for DllHookResolution { + fn serialize(&self, _serializer: S) -> Result + where S: Serializer, { todo!("Serialization not implemented") } +} +impl<'de> Deserialize<'de> for DllHookResolution { + fn deserialize(_deserializer: D) -> Result + where D: Deserializer<'de>, { todo!("Deserialization not implemented") } +} + +// Globals impl from dll_hook example +// used by ue.rs + +static mut GLOBALS: Option = None; + +pub struct Globals { + resolution: DllHookResolution, + guobject_array: parking_lot::FairMutex<&'static ue::FUObjectArray>, + main_thread_id: std::thread::ThreadId, + last_command: Option, +} + +impl Globals { + pub fn gmalloc(&self) -> &ue::FMalloc { + unsafe { &**(self.resolution.gmalloc.0 as *const *const ue::FMalloc) } + } + pub fn fframe_step(&self) -> ue::FnFFrameStep { + unsafe { std::mem::transmute(self.resolution.fframe_step.0) } + } + pub fn fframe_step_explicit_property(&self) -> ue::FnFFrameStepExplicitProperty { + unsafe { std::mem::transmute(self.resolution.fframe_step_explicit_property.0) } + } + pub fn fname_to_string(&self) -> ue::FnFNameToString { + unsafe { std::mem::transmute(self.resolution.fnametostring.0) } + } + pub fn uobject_base_utility_get_path_name(&self) -> ue::FnUObjectBaseUtilityGetPathName { + unsafe { std::mem::transmute(self.resolution.uobject_base_utility_get_path_name.0) } + } + pub fn guobject_array(&self) -> parking_lot::FairMutexGuard<'static, &ue::FUObjectArray> { + self.guobject_array.lock() + } + pub unsafe fn guobject_array_unchecked(&self) -> &ue::FUObjectArray { + *self.guobject_array.data_ptr() + } + + // pub fn last_command(&self) -> Option<&mut ue::FString> { + // self.last_command.as_mut() + // } +} + +pub fn globals() -> &'static Globals { + unsafe { GLOBALS.as_ref().unwrap() } +} \ No newline at end of file diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 54769a4..45970eb 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -1,14 +1,10 @@ -use patternsleuth::_impl_try_collector; -use patternsleuth::resolvers::Singleton; use retour::static_detour; use std::{collections::HashMap, sync::Arc}; use std::error::Error; use std::os::raw::c_void; use std::mem; -use crate::resolvers::BASE_ADDR; -// use crate::resolvers::ue::FString; -use crate::scan::{OFFSETS, RESOLUTION}; +use crate::ue::*; define_pattern_resolver![UTBLLocalPlayer_Exec, { // "75 18 ?? ?? ?? ?? 75 12 4d 85 f6 74 0d 41 38 be ?? ?? ?? ?? 74 04 32 db eb 9b 48 8b 5d 7f 49 8b d5 4c 8b 45 77 4c 8b cb 49 8b cf", // EGS - latest @@ -23,153 +19,29 @@ define_pattern_resolver!(ExecuteConsoleCommand, [ "40 53 48 83 EC 30 48 8B 05 ?? ?? ?? ?? 48 8B D9 48 8B 90 58 0C 00 00" ]); -CREATE_HOOK!(ExecuteConsoleCommand, (string:*mut super::FString), { - println!(">>>>>>>>ExecuteConsoleCommand_d"); +CREATE_HOOK!(ExecuteConsoleCommand, (string:*mut FString), { + println!("ExecuteConsoleCommand"); }); -// static_detour!{ -// pub static o_ExecuteConsoleCommand:unsafe extern "C" fn(*mut super::FString); -// }#[allow(non_snake_case)] -// pub fn ExecuteConsoleCommand_detour_fkt(string: *mut super::FString){ -// {} -// unsafe { -// println!(">>>>>>>>ExecuteConsoleCommand_detour_fkt {}", string.as_ref().unwrap().letter_count); -// o_ExecuteConsoleCommand.call(string) -// } -// } -// #[allow(non_snake_case)] -// pub unsafe fn attach_ExecuteConsoleCommand(base_address:usize,offsets:HashMap) -> Result<(),Box>{ -// let address = base_address+offsets[stringify![ExecuteConsoleCommand]]as usize; -// let target:FnExecuteConsoleCommand = mem::transmute(address); -// type FnExecuteConsoleCommand = unsafe extern "C" fn(*mut super::FString); -// println!("o_ExecuteConsoleCommand INITED: {:x?}", address); -// o_ExecuteConsoleCommand.initialize(target,ExecuteConsoleCommand_detour_fkt)? .enable()?; -// Ok(()) -// } - -// unsafe fn attach_ExecuteConsoleCommand(base_address: usize, offsets: HashMap) -> Result<(), Box>{ -// let address = base_address + offsets["ExecuteConsoleCommand"] as usize; -// let target: FnExecuteConsoleCommand = mem::transmute(address); -// type FnExecuteConsoleCommand = unsafe extern "C" fn(*mut c_void, f32, u8); -// static_detour! { -// static ExecuteConsoleCommand: unsafe extern "C" fn(*mut c_void, f32, u8); -// } -// fn detour_fkt(engine:*mut c_void, delta:f32, state:u8) { -// println!("rust ExecuteConsoleCommand delta: {}", delta); -// unsafe { ExecuteConsoleCommand.call( engine, delta, state) } -// } -// ExecuteConsoleCommand -// .initialize(target, detour_fkt)? -// .enable()?; -// Ok(()) - CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { - let asd = Arc::clone(&crate::resolvers::rcon::COMMAND_PENDING); - let pending = match asd.lock().unwrap().as_ref() { - Some(true) => { - println!("got pending"); - true - } - _ => false - }; - if pending { - - let cmd_buf = Arc::clone(&crate::resolvers::rcon::LAST_COMMAND); - if let Some(cmd) = crate::resolvers::rcon::LAST_COMMAND.lock().unwrap().as_ref() { - if let Some(offset_map) = OFFSETS.get() { - println!("offsets: {}", offset_map.len()); - if let Some(val) = offset_map.get("ExecuteConsoleCommand") { - println!("Offset for ExecuteConsoleCommand is: 0x{:X}", val); - let u16pat = widestring::U16CString::from_str(cmd.as_str()).unwrap(); - let mut sstring = crate::resolvers::FString::new_from_wide_str(u16pat.as_slice()); - unsafe { - // let str_teeemp = widestring::U16CString::from_ptr(sstring.str, sstring.letter_count as usize).unwrap(); - // println!("[Rust RCOM] asdasdasdasdasd o_ExecuteConsoleCommand: {:x?} {}", val, str_teeemp.to_string_lossy()); - *asd.lock().unwrap() = Some(false); - unsafe { o_ExecuteConsoleCommand.call(&mut sstring); } - println!("executed command"); - } - } - } + let lock = Arc::clone(&crate::resolvers::rcon::LAST_COMMAND); + if let Some(cmd) = crate::resolvers::rcon::LAST_COMMAND.lock().unwrap().as_ref() { + let mut fstring = FString::from( + widestring::U16CString::from_str(cmd.as_str()) + .unwrap() + .as_slice_with_nul()); + { + println!("executing command"); + *lock.lock().unwrap() = Some("".to_string()); + unsafe { o_ExecuteConsoleCommand.call(&mut fstring); } + println!("executed command"); } } }); -// CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { -// // println!("rust UGameEngineTick delta: {}", delta); -// // ExecuteConsoleCommand::get(&self).ok_or("Cant get addr"); -// // ExecuteConsoleCommand::resolver() -// // RESOLUTION -// // ExecuteConsoleCommand.get() -// // ExecuteConsoleCommand::get(ExecuteConsoleCommand.clone()); - -// let asd = Arc::clone(&crate::resolvers::rcon::COMMAND_PENDING); -// let pending = match asd.lock().unwrap().as_ref() { -// Some(true) => { -// println!("got pending"); -// true -// } -// _ => false -// }; -// // if let Some(pending) = crate::resolvers::rcon::COMMAND_PENDING.lock().unwrap().as_ref() { -// if (pending == true) { - -// let cmd_buf = Arc::clone(&crate::resolvers::rcon::LAST_COMMAND); -// println!("pending: {pending}", ); -// if let Some(cmd) = crate::resolvers::rcon::LAST_COMMAND.lock().unwrap().as_ref() { -// println!("cmd: {}", cmd); - -// if let Some(offset_map) = OFFSETS.get() { -// println!("offsets: {}", offset_map.len()); -// if let Some(val) = offset_map.get("ExecuteConsoleCommand") { -// println!("Offset for ExecuteConsoleCommand is: 0x{:X}", val); - -// println!("[Rust RCOM] Got command: {}", cmd); - -// let u16pat = widestring::U16CString::from_str( -// cmd.as_str() -// ).unwrap(); -// let mut sstring = crate::resolvers::FString::new_from_wide_str(u16pat.as_slice()); -// let base_address = BASE_ADDR.get().unwrap(); -// unsafe { -// let target:FnExecuteConsoleCommand = mem::transmute(val); -// type FnExecuteConsoleCommand = unsafe extern "C" fn(*mut super::FString); -// let str_teeemp = widestring::U16CString::from_ptr(sstring.str, sstring.letter_count as usize).unwrap(); -// println!("[Rust RCOM] asdasdasdasdasd o_ExecuteConsoleCommand: {:x?} {}", val, str_teeemp.to_string_lossy()); -// // o_ExecuteConsoleCommand.initialize(target,ExecuteConsoleCommand_detour_fkt).unwrap().enable(); -// *asd.lock().unwrap() = Some(false); -// // unsafe { ExecuteConsoleCommand_detour_fkt(&mut sstring); } -// unsafe { o_ExecuteConsoleCommand.call(&mut sstring); } -// println!("executed command"); -// } -// } else { -// println!("Key not found"); -// } -// } else { -// println!("OFFSETS not initialized yet"); -// } - -// println!("hello down here"); -// // let mut str_test = FString::from_string(cmd.as_str()).0; -// // let boxed = Box::new(str_test); -// // // Get *mut FString -// // let fstring_ptr: *mut FString = Box::into_raw(boxed); -// // unsafe { o_ExecuteConsoleCommand.call(fstring_ptr); } - - -// // attach_ExecuteConsoleCommand::detour_fkt(); - - -// // unsafe { ExecuteConsoleCommand_detour_fkt(&mut sstring); } -// // if let Some(addr) = OFFSETS.get().and_then(|m| m.get("ExecuteConsoleCommand")) { -// // println!("Address FOUND: 0x{:X}", addr); -// // } -// } -// *cmd_buf.lock().unwrap() = Some("\0".to_string()); -// } -// } -// // println!("hello down hereeeeee"); -// ); +CREATE_HOOK!(FEngineLoopInit, (engine_loop:*mut c_void), { +println!("Engine Loop initialized!!"); +}); // FText* __cdecl FText::AsCultureInvariant(FText* __return_storage_ptr__, FString* param_1) define_pattern_resolver![FText_AsCultureInvariant, First, { @@ -202,15 +74,4 @@ define_pattern_resolver![GetTBLGameMode, { define_pattern_resolver!(ClientMessage, [ "4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 73 18 49 8B D8 49 89 43 C8 48 8B F1 49 89 43 D0 49 89 43 D8 49 8D 43" -]); - -// use patternsleuth::resolvers::impl_try_collector; -// impl_try_collector! { -// #[derive(Debug, PartialEq, Clone)] -// struct DllHookResolution2 { -// guobject_array: patternsleuth::resolvers::unreal::guobject_array::GUObjectArray, - -// free_uobject: patternsleuth::resolvers::unreal::guobject_array::FUObjectArrayFreeUObjectIndex, - -// } -// } \ No newline at end of file +]); \ No newline at end of file diff --git a/sleuth/src/resolvers/mod.rs b/sleuth/src/resolvers/mod.rs index a72ce44..66f7f93 100644 --- a/sleuth/src/resolvers/mod.rs +++ b/sleuth/src/resolvers/mod.rs @@ -40,78 +40,78 @@ impl std::str::FromStr for PlatformType { } } } -use std::ffi::OsStr; -use std::os::raw::c_int; -use std::os::windows::ffi::OsStrExt; -use std::ptr::null_mut; - -#[repr(C)] -pub struct FString { - pub str: *mut u16, // wchar_t* == *mut u16 on Windows - pub letter_count: c_int, - pub max_letters: c_int, -} - -impl FString { - /// Create an FString from a Rust `&str` (or `String`) - pub fn from_string(s: &str) -> (Self, Box<[u16]>) { - // Convert to UTF-16 and null-terminate - let wide: Vec = OsStr::new(s) - .encode_wide() - .chain(std::iter::once(0)) // null terminator - .collect(); - - let letter_count = wide.len() as c_int; - - // Box it to keep it alive (heap allocation) - let boxed = wide.into_boxed_slice(); - let ptr = boxed.as_ptr() as *mut u16; - - // Return both FString and the boxed data - ( - FString { - str: ptr, - letter_count, - max_letters: letter_count, - }, - boxed, - ) - } - /// Creates an `FString` from a Rust wide string slice (`&[u16]` or `Vec`) - pub fn new_from_wide_str(wide: &[u16]) -> Self { - let letter_count = (wide.len()+1) as c_int; - Self { - str: wide.as_ptr() as *mut u16, - letter_count, - max_letters: letter_count, - } - } - - /// Creates an `FString` from a wide string pointer (like `*const u16`) - /// WARNING: This assumes the pointer is null-terminated. - pub unsafe fn from_ptr(ptr: *const u16) -> Self { - if ptr.is_null() { - return Self { - str: null_mut(), - letter_count: 0, - max_letters: 0, - }; - } - - // Count the number of wide characters (including null terminator) - let mut len = 0; - while *ptr.add(len) != 0 { - len += 1; - } - let letter_count = len as c_int + 1; - - Self { - str: ptr as *mut u16, - letter_count, - max_letters: letter_count, - } - } -} +// use std::ffi::OsStr; +// use std::os::raw::c_int; +// use std::os::windows::ffi::OsStrExt; +// use std::ptr::null_mut; + +// #[repr(C)] +// pub struct FString { +// pub str: *mut u16, // wchar_t* == *mut u16 on Windows +// pub letter_count: c_int, +// pub max_letters: c_int, +// } + +// impl FString { +// /// Create an FString from a Rust `&str` (or `String`) +// pub fn from_string(s: &str) -> (Self, Box<[u16]>) { +// // Convert to UTF-16 and null-terminate +// let wide: Vec = OsStr::new(s) +// .encode_wide() +// .chain(std::iter::once(0)) // null terminator +// .collect(); + +// let letter_count = wide.len() as c_int; + +// // Box it to keep it alive (heap allocation) +// let boxed = wide.into_boxed_slice(); +// let ptr = boxed.as_ptr() as *mut u16; + +// // Return both FString and the boxed data +// ( +// FString { +// str: ptr, +// letter_count, +// max_letters: letter_count, +// }, +// boxed, +// ) +// } +// /// Creates an `FString` from a Rust wide string slice (`&[u16]` or `Vec`) +// pub fn new_from_wide_str(wide: &[u16]) -> Self { +// let letter_count = (wide.len()+1) as c_int; +// Self { +// str: wide.as_ptr() as *mut u16, +// letter_count, +// max_letters: letter_count, +// } +// } + +// /// Creates an `FString` from a wide string pointer (like `*const u16`) +// /// WARNING: This assumes the pointer is null-terminated. +// pub unsafe fn from_ptr(ptr: *const u16) -> Self { +// if ptr.is_null() { +// return Self { +// str: null_mut(), +// letter_count: 0, +// max_letters: 0, +// }; +// } + +// // Count the number of wide characters (including null terminator) +// let mut len = 0; +// while *ptr.add(len) != 0 { +// len += 1; +// } +// let letter_count = len as c_int + 1; + +// Self { +// str: ptr as *mut u16, +// letter_count, +// max_letters: letter_count, +// } +// } +// } pub mod hook_retour; diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs index 6038c10..ca4875c 100644 --- a/sleuth/src/resolvers/rcon.rs +++ b/sleuth/src/resolvers/rcon.rs @@ -12,6 +12,7 @@ fn get_rcon_port() -> Option { } pub static COMMAND_PENDING: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); pub static LAST_COMMAND: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); +// pub static FLAST_COMMAND: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); pub fn handle_rcon() { let port = match get_rcon_port() { @@ -27,7 +28,7 @@ pub fn handle_rcon() { for stream in listener.incoming() { match stream { Ok(stream) => { - let cmd_store = Arc::clone(&LAST_COMMAND); + let cmd_store: Arc>> = Arc::clone(&LAST_COMMAND); let cmd_pending = Arc::clone(&COMMAND_PENDING); thread::spawn(move || { let reader = BufReader::new(stream); diff --git a/sleuth/src/scan.rs b/sleuth/src/scan.rs index ab8b7c2..d6bdc4a 100644 --- a/sleuth/src/scan.rs +++ b/sleuth/src/scan.rs @@ -15,28 +15,8 @@ use once_cell::sync::{Lazy, OnceCell}; use std::sync::Arc; use patternsleuth::resolvers::{ResolveError}; -use crate::resolvers; - pub static OFFSETS: OnceCell> = OnceCell::new(); -pub static RESOLUTION: OnceCell, ResolveError>>> = OnceCell::new(); -// pub static FN_OFFSETS: OnceCell> = OnceCell::new(); -// fn find_resolution(name: UGameEngineTick) -> Option> { -// RESOLUTION -// .get()? -// .iter() -// .filter_map(Result::as_ref) // Skip Err entries -// .find(|res| res == name) // Replace with your identifier logic -// .cloned() -// } -// fn find_resolution(name: &str) -> Option> { -// RESOLUTION -// .get()? -// .iter() -// .filter_map(Result::as_ref) // Skip Err -// .find(|res| res.name() == name) -// .cloned() -// } pub fn scan() -> Result, String> { @@ -48,19 +28,14 @@ pub fn scan() -> Result, String> { // let name = format!("PID={}", pid.unwrap()); let game_name = format!("pid={}", pid.unwrap()); // fixme let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string())?; - // println!("GAME '{:?}' '{:x?}'", name, exe.base_address); - - // RESOLUTION.set( - // tracing::info_span!("scan", game = game_name) - // .in_scope(|| exe.resolve_many(&dyn_resolvers)) - // ).unwrap(); // Or handle failure + let resolution = tracing::info_span!("scan", game = game_name) .in_scope(|| exe.resolve_many(&dyn_resolvers)); - // get Names and offsets from resolution let mut offsets: HashMap = HashMap::new(); let mut offsets_resolver: HashMap = HashMap::new(); - // let mut offsets_resolver: HashMap<*const (), usize> = HashMap::new(); + + // FIXME: ugh for (resolver, resolution) in resolvers.iter().zip(&resolution) { if let Ok(r) = resolution { // FIXME: Less nasty way? @@ -82,26 +57,7 @@ pub fn scan() -> Result, String> { } } - // let res = dump_builds(offsets); - // let converted: HashMap = offsets_resolver - // .iter() - // .map(|(k, v)| (k.clone(), *v as usize)) - // .collect(); - let _ = OFFSETS.set(offsets_resolver); // Safe, only allowed once Ok(offsets) // Return the original u64-based map } - - -// patternsleuth::_impl_try_collector! { -// #[derive(Debug, PartialEq, Clone, Serialize)] -// struct DllHookResolution { -// game_tick: patternsleuth::resolvers::unreal::game_loop::UGameEngineTick, -// } -// } -// static mut GLOBALS: Option = None; - -// pub struct Globals { -// resolution: DllHookResolution -// } \ No newline at end of file From 8d7a59e16c4b6bbe94e73ebe77055eddfa5e5558 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Sun, 8 Jun 2025 05:46:01 +0200 Subject: [PATCH 04/18] ClientMessage: chat commands, stubs for logging ExecuteConsoleCommand: mutex behavior --- .gitignore | 1 + sleuth/Cargo.lock | 4167 ++++++++++++++++++++++--- sleuth/Cargo.toml | 1 + sleuth/src/lib.rs | 10 +- sleuth/src/resolvers/admin_control.rs | 97 +- sleuth/src/resolvers/backend_hooks.rs | 2 +- sleuth/src/resolvers/macros.rs | 10 +- sleuth/src/scan.rs | 4 +- sleuth/src/ue.rs | 2 +- src/hooks/admin_control.hpp | 2 +- 10 files changed, 3793 insertions(+), 503 deletions(-) diff --git a/.gitignore b/.gitignore index c0b9891..5e7f336 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ *.sln.docstates sleuth/target/ +sleuth/old/ CMakeFiles Output diff --git a/sleuth/Cargo.lock b/sleuth/Cargo.lock index 5aa0705..b5f2be7 100644 --- a/sleuth/Cargo.lock +++ b/sleuth/Cargo.lock @@ -3,925 +3,4132 @@ version = 4 [[package]] -name = "adler2" -version = "2.0.0" +name = "ab_glyph" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] [[package]] -name = "anyhow" -version = "1.0.98" +name = "ab_glyph_rasterizer" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] -name = "autocfg" -version = "1.4.0" +name = "accesskit" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" [[package]] -name = "bitflags" -version = "1.3.2" +name = "accesskit_consumer" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" +dependencies = [ + "accesskit", +] [[package]] -name = "bitflags" -version = "2.9.1" +name = "accesskit_macos" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2 0.3.0-beta.3.patch-leaks.3", + "once_cell", +] [[package]] -name = "byteorder" -version = "1.5.0" +name = "accesskit_unix" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" +dependencies = [ + "accesskit", + "accesskit_consumer", + "async-channel", + "async-once-cell", + "atspi", + "futures-lite 1.13.0", + "once_cell", + "serde", + "zbus", +] [[package]] -name = "cc" -version = "1.2.26" +name = "accesskit_windows" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" dependencies = [ - "shlex", + "accesskit", + "accesskit_consumer", + "once_cell", + "paste", + "static_assertions", + "windows 0.48.0", ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "accesskit_winit" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "88e39fcec2e10971e188730b7a76bab60647dacc973d4591855ebebcadfaa738" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "winit", +] [[package]] -name = "crc32fast" -version = "1.4.2" +name = "addr2line" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ - "cfg-if", + "gimli", ] [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "crossbeam-utils", + "cfg-if", + "once_cell", + "version_check", + "zerocopy", ] [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "aho-corasick" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "memchr", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "android-activity" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" dependencies = [ - "crossbeam-utils", + "android-properties", + "bitflags 1.3.2", + "cc", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum 0.6.1", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "android-properties" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] -name = "dashmap" -version = "5.5.3" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "libc", ] [[package]] -name = "derive_more" -version = "0.99.20" +name = "anyhow" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" dependencies = [ - "proc-macro2", - "quote", - "syn", + "clipboard-win", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "x11rb", ] [[package]] -name = "either" -version = "1.15.0" +name = "arc-swap" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] -name = "equivalent" -version = "1.0.2" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] -name = "erased-serde" -version = "0.4.6" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" -dependencies = [ - "serde", - "typeid", -] +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "flate2" -version = "1.1.1" +name = "async-broadcast" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ - "crc32fast", - "miniz_oxide", + "event-listener 2.5.3", + "futures-core", ] [[package]] -name = "futures" -version = "0.3.31" +name = "async-channel" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ - "futures-channel", + "concurrent-queue", + "event-listener-strategy", "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "pin-project-lite", ] [[package]] -name = "futures-channel" -version = "0.3.31" +name = "async-executor" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ - "futures-core", - "futures-sink", + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.0", + "pin-project-lite", + "slab", ] [[package]] -name = "futures-core" -version = "0.3.31" +name = "async-fs" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] [[package]] -name = "futures-executor" -version = "0.3.31" +name = "async-io" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2", + "waker-fn", ] [[package]] -name = "futures-io" -version = "0.3.31" +name = "async-io" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.0", + "parking", + "polling 3.7.2", + "rustix 0.38.44", + "slab", + "tracing", + "windows-sys 0.52.0", +] [[package]] -name = "futures-macro" -version = "0.3.31" +name = "async-lock" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "event-listener 2.5.3", ] [[package]] -name = "futures-scopes" -version = "0.2.0" +name = "async-lock" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcf32827e803f1a3cd04c4319feb99156cb5968a3b393f8541efefa1e3b24c" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "crossbeam-channel", - "dashmap", - "futures", - "pin-project", + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", ] [[package]] -name = "futures-sink" -version = "0.3.31" +name = "async-once-cell" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" [[package]] -name = "futures-task" -version = "0.3.31" +name = "async-process" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.44", + "windows-sys 0.48.0", +] [[package]] -name = "futures-util" -version = "0.3.31" +name = "async-recursion" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "async-signal" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" dependencies = [ - "typenum", - "version_check", + "async-io 2.3.3", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.44", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "async-task" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] -name = "hashbrown" -version = "0.15.4" +name = "async-trait" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] [[package]] -name = "heck" -version = "0.4.1" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "iced-x86" -version = "1.21.0" +name = "atspi" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" dependencies = [ - "lazy_static", + "atspi-common", + "atspi-connection", + "atspi-proxies", ] [[package]] -name = "indexmap" -version = "2.9.0" +name = "atspi-common" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" dependencies = [ - "equivalent", - "hashbrown 0.15.4", + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus_names", + "zvariant", ] [[package]] -name = "inventory" -version = "0.3.20" +name = "atspi-connection" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" dependencies = [ - "rustversion", + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus", ] [[package]] -name = "itertools" -version = "0.12.1" +name = "atspi-proxies" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" dependencies = [ - "either", + "atspi-common", + "serde", + "zbus", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "autocfg" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "lazy_static" -version = "1.5.0" +name = "backtrace" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libudis86-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ + "addr2line", "cc", + "cfg-if", "libc", + "miniz_oxide 0.7.4", + "object 0.36.7", + "rustc-demangle", ] [[package]] -name = "lock_api" -version = "0.4.13" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "autocfg", - "scopeguard", + "generic-array", ] [[package]] -name = "mach2" -version = "0.4.2" +name = "block-sys" +version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" dependencies = [ - "libc", + "objc-sys", ] [[package]] -name = "memchr" -version = "2.7.4" +name = "block2" +version = "0.2.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys", + "objc2-encode 2.0.0-pre.2", +] [[package]] -name = "miniz_oxide" -version = "0.8.8" +name = "blocking" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "adler2", + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.6.0", + "piper", ] [[package]] -name = "mmap-fixed-fixed" -version = "0.1.3" +name = "bumpalo" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" + +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ - "libc", - "winapi", + "bytemuck_derive", ] [[package]] -name = "object" -version = "0.32.2" +name = "bytemuck_derive" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ - "flate2", - "memchr", - "ruzstd", + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "parking_lot" -version = "0.12.4" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" dependencies = [ - "lock_api", - "parking_lot_core", + "bitflags 1.3.2", + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", ] [[package]] -name = "parking_lot_core" -version = "0.9.11" +name = "cc" +version = "1.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" dependencies = [ - "cfg-if", + "jobserver", "libc", - "redox_syscall", - "smallvec", - "windows-targets", + "shlex", ] [[package]] -name = "paste" -version = "1.0.15" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "patternsleuth" -version = "0.1.0" -source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" dependencies = [ - "anyhow", - "futures", - "futures-scopes", - "iced-x86", - "inventory", - "itertools", "libc", - "memchr", - "object", - "paste", - "patternsleuth_scanner", - "rayon", - "serde", - "strum", - "tracing", - "typetag", - "windows", ] [[package]] -name = "patternsleuth_scanner" -version = "0.1.0" -source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ - "anyhow", - "memchr", - "rayon", + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-link", ] [[package]] -name = "pin-project" -version = "1.1.10" +name = "clipboard-win" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ - "pin-project-internal", + "error-code", ] [[package]] -name = "pin-project-internal" -version = "1.1.10" +name = "cocoa" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ - "proc-macro2", - "quote", - "syn", + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "cocoa-foundation" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "proc-macro2" -version = "1.0.95" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "unicode-ident", + "bytes", + "memchr", ] [[package]] -name = "quote" -version = "1.0.40" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "proc-macro2", + "crossbeam-utils", ] [[package]] -name = "rayon" -version = "1.10.0" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "either", - "rayon-core", + "core-foundation-sys", + "libc", ] [[package]] -name = "rayon-core" -version = "1.12.1" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", ] [[package]] -name = "redox_syscall" -version = "0.5.12" +name = "core-graphics-types" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ - "bitflags 2.9.1", + "bitflags 1.3.2", + "core-foundation", + "libc", ] [[package]] -name = "region" -version = "3.0.2" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "bitflags 1.3.2", "libc", - "mach2", - "windows-sys", ] [[package]] -name = "retour" -version = "0.3.1" +name = "crc32fast" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9af44d40e2400b44d491bfaf8eae111b09f23ac4de6e92728e79d93e699c527" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", - "generic-array", - "libc", - "libudis86-sys", - "mmap-fixed-fixed", - "once_cell", - "region", - "slice-pool2", ] [[package]] -name = "rustversion" -version = "1.0.21" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "ruzstd" -version = "0.5.0" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "byteorder", - "derive_more", - "twox-hash", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "ryu" -version = "1.0.20" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "serde" -version = "1.0.219" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "serde_derive", + "generic-array", + "typenum", ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] -name = "serde_json" -version = "1.0.140" +name = "derive_more" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] -name = "slab" -version = "0.4.9" +name = "dispatch" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "autocfg", + "bitflags 2.9.1", + "objc2 0.6.1", ] [[package]] -name = "sleuthlib" +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.8", +] + +[[package]] +name = "dll_hook" version = "0.1.0" +source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" dependencies = [ "anyhow", + "backtrace", "bitflags 2.9.1", - "futures", + "eframe", + "egui-winit", "indexmap", - "once_cell", + "itertools", "parking_lot", - "paste", "patternsleuth", - "retour", + "regex", + "retour 0.4.0-alpha.3", "serde", - "serde_json", - "tracing", + "simple-log", + "thread_local", "widestring", - "winapi", + "windows 0.52.0", ] [[package]] -name = "slice-pool2" -version = "0.4.3" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "smallvec" -version = "1.15.0" +name = "ecolor" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "4b7637fc2e74d17e52931bac90ff4fc061ac776ada9c7fa272f24cdca5991972" +dependencies = [ + "bytemuck", +] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "eframe" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "cdd73918a828c35a7efb4d7188ea973df4bffc589178ed95f521c917b03ddcfa" +dependencies = [ + "bytemuck", + "cocoa", + "egui", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "static_assertions", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winapi", + "winit", +] [[package]] -name = "strum" -version = "0.25.0" +name = "egui" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "c55bcb864b764eb889515a38b8924757657a250738ad15126637ee2df291ee6b" dependencies = [ - "strum_macros", + "accesskit", + "ahash", + "epaint", + "log", + "nohash-hasher", ] [[package]] -name = "strum_macros" -version = "0.25.3" +name = "egui-winit" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "3b673606b6606b12b95e3a3194d7882bf5cff302db36a520b8144c7c342e4e84" dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", + "accesskit_winit", + "arboard", + "egui", + "log", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", ] [[package]] -name = "syn" -version = "2.0.101" +name = "egui_glow" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "262151f9d57c557c02a40a46f27b9e050a6eb0b006b94dced9c6f4519a04d489" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "bytemuck", + "egui", + "glow", + "log", + "memoffset 0.7.1", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "tracing" -version = "0.1.41" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "emath" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a045c6c0b44b35e98513fc1e9d183ab42881ac27caccb9fa345465601f56cce4" dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", + "bytemuck", ] [[package]] -name = "tracing-attributes" -version = "0.1.28" +name = "enumflags2" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] -name = "tracing-core" -version = "0.1.33" +name = "epaint" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "7d1b9e000d21bab9b535ce78f9f7745be28b3f777f6c7223936561c5c7fefab8" dependencies = [ - "once_cell", + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "log", + "nohash-hasher", + "parking_lot", ] [[package]] -name = "twox-hash" -version = "1.6.3" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" dependencies = [ - "cfg-if", - "static_assertions", + "serde", + "typeid", ] [[package]] -name = "typeid" -version = "1.0.3" +name = "errno" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] -name = "typenum" -version = "1.18.0" +name = "error-code" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] -name = "typetag" -version = "0.2.20" +name = "event-listener" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" dependencies = [ - "erased-serde", - "inventory", - "once_cell", - "serde", - "typetag-impl", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "typetag-impl" -version = "0.2.20" +name = "event-listener" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ - "proc-macro2", - "quote", - "syn", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "unicode-ident" -version = "1.0.18" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] [[package]] -name = "version_check" -version = "0.9.5" +name = "fastrand" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] [[package]] -name = "widestring" -version = "1.2.0" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "winapi" -version = "0.3.9" +name = "fdeflate" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "simd-adler32", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "flate2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.8", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "windows" -version = "0.52.0" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "windows-core", - "windows-targets", + "foreign-types-shared", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "windows-targets", + "percent-encoding", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "futures" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "windows-targets", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "windows-targets" -version = "0.52.0" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "futures-core", + "futures-sink", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" +name = "futures-executor" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] -name = "windows_i686_gnu" -version = "0.52.6" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "futures-lite" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "futures-lite" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "futures-scopes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcf32827e803f1a3cd04c4319feb99156cb5968a3b393f8541efefa1e3b24c" +dependencies = [ + "crossbeam-channel", + "dashmap", + "futures", + "pin-project", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc93b03242719b8ad39fb26ed2b01737144ce7bd4bfc7adadcef806596760fe" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading 0.7.4", + "objc2 0.3.0-beta.3.patch-leaks.3", + "once_cell", + "raw-window-handle", + "wayland-sys 0.30.1", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af784eb26c5a68ec85391268e074f0aa618c096eadb5d6330b0911cf34fe57c5" +dependencies = [ + "gl_generator", + "windows-sys 0.45.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b53cb5fe568964aa066a3ba91eac5ecbac869fb0842cd0dc9e412434f1a1494" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced-x86" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.52.0", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall 0.5.12", +] + +[[package]] +name = "libudis86-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "serde", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "flate2", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "once_cell", + "parking_lot", + "rand", + "serde", + "serde-value", + "serde_json", + "serde_yaml", + "thiserror", + "thread-id", + "winapi", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "mmap-fixed-fixed" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum 0.5.11", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2", + "objc-sys", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode 4.1.0", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.12", + "smallvec", + "windows-targets 0.52.0", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "patternsleuth" +version = "0.1.0" +source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" +dependencies = [ + "anyhow", + "futures", + "futures-scopes", + "iced-x86", + "inventory", + "itertools", + "libc", + "memchr", + "object 0.32.2", + "paste", + "patternsleuth_scanner", + "rayon", + "serde", + "strum", + "tracing", + "typetag", + "windows 0.52.0", +] + +[[package]] +name = "patternsleuth_scanner" +version = "0.1.0" +source = "git+https://github.com/Knutschbert/patternsleuth.git?rev=644f980#644f9804e5247fe40e4d7a1e470639601254cd70" +dependencies = [ + "anyhow", + "memchr", + "rayon", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.8", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.44", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + +[[package]] +name = "retour" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9af44d40e2400b44d491bfaf8eae111b09f23ac4de6e92728e79d93e699c527" +dependencies = [ + "cfg-if", + "generic-array", + "libc", + "libudis86-sys", + "mmap-fixed-fixed", + "once_cell", + "region", + "slice-pool2", +] + +[[package]] +name = "retour" +version = "0.4.0-alpha.3" +source = "git+https://github.com/Hpmason/retour-rs#02c7c4c18cf87651b30b684ac9944dd47d2593ec" +dependencies = [ + "cfg-if", + "generic-array", + "iced-x86", + "libc", + "mmap-fixed-fixed", + "once_cell", + "region", + "slice-pool2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ruzstd" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +dependencies = [ + "byteorder", + "derive_more", + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simple-log" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94263a9019c994147d7dc572aa1b3bce100bbdf5b5e3a8a446a025c80ecba169" +dependencies = [ + "log", + "log4rs", + "once_cell", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sleuthlib" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitflags 2.9.1", + "dll_hook", + "futures", + "indexmap", + "once_cell", + "parking_lot", + "paste", + "patternsleuth", + "regex", + "retour 0.3.1", + "serde", + "serde_json", + "simple-log", + "tracing", + "widestring", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "slice-pool2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.101", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand 2.3.0", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thread-id" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "typetag" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "lazy_static", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc", + "raw-window-handle", + "url", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement 0.48.0", + "windows-interface 0.48.0", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winit" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +dependencies = [ + "android-activity", + "bitflags 1.3.2", + "cfg_aliases", + "core-foundation", + "core-graphics", + "dispatch", + "instant", + "libc", + "log", + "mio", + "ndk", + "objc2 0.3.0-beta.3.patch-leaks.3", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall 0.3.5", + "sctk-adwaita", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-commons", + "wayland-protocols", + "wayland-scanner", + "web-sys", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xdg-home" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "xml-rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.4", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index dd6a43a..6c91043 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -30,6 +30,7 @@ indexmap = "2.1.0" parking_lot = "0.12.1" simple-log = "1.6.0" # thread_local = "1.1.7" +regex = "1.11" retour = { version = "0.3.1", features = ["static-detour"]} windows = { version = "0.52.0", features = ["Win32_Foundation", diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index e3f9a7d..51650ac 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -2,6 +2,7 @@ mod resolvers; mod scan; +use core::fmt; use std::env; use std::fs::File; use std::io::{BufReader, Read}; @@ -120,7 +121,8 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - // attach_GameEngineTick(base_address, offsets).unwrap(); resolvers::admin_control::attach_UGameEngineTick(base_address, offsets.clone()).unwrap(); resolvers::admin_control::attach_ExecuteConsoleCommand(base_address, offsets.clone()).unwrap(); - resolvers::admin_control::attach_FEngineLoopInit(base_address, offsets).unwrap(); + resolvers::admin_control::attach_FEngineLoopInit(base_address, offsets.clone()).unwrap(); + resolvers::admin_control::attach_ClientMessage(base_address, offsets.clone()).unwrap(); Ok(()) } @@ -128,9 +130,10 @@ unsafe fn init_globals() { let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); println!("starting scan"); + // FIXME: Nihi: use the results from scan function let resolution = exe.resolve(DllHookResolution::resolver()).unwrap(); println!("finished scan"); - + println!("results: {:?}", resolution); let guobject_array: &'static ue::FUObjectArray = &*(resolution.guobject_array.0 as *const ue::FUObjectArray); @@ -167,7 +170,7 @@ pub extern "C" fn generate_json() -> u8 { // let scan = scan::scan(); let offsets = scan::scan().expect("Failed to scan"); let len_u8 = offsets.len() as u8; - // FIXME: ? + // FIXME: Nihi: ? let offset_copy = offsets.clone(); // let base_addr = scan::scan().1; unsafe { @@ -210,6 +213,7 @@ impl<'de> Deserialize<'de> for DllHookResolution { where D: Deserializer<'de>, { todo!("Deserialization not implemented") } } + // Globals impl from dll_hook example // used by ue.rs diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 45970eb..8cc50ab 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -1,10 +1,12 @@ + use retour::static_detour; use std::{collections::HashMap, sync::Arc}; use std::error::Error; use std::os::raw::c_void; use std::mem; -use crate::ue::*; +use crate::resolvers::rcon::LAST_COMMAND; +use crate::{globals, ue::*}; define_pattern_resolver![UTBLLocalPlayer_Exec, { // "75 18 ?? ?? ?? ?? 75 12 4d 85 f6 74 0d 41 38 be ?? ?? ?? ?? 74 04 32 db eb 9b 48 8b 5d 7f 49 8b d5 4c 8b 45 77 4c 8b cb 49 8b cf", // EGS - latest @@ -19,26 +21,31 @@ define_pattern_resolver!(ExecuteConsoleCommand, [ "40 53 48 83 EC 30 48 8B 05 ?? ?? ?? ?? 48 8B D9 48 8B 90 58 0C 00 00" ]); +// FIXME: Nihi: stub CREATE_HOOK!(ExecuteConsoleCommand, (string:*mut FString), { - println!("ExecuteConsoleCommand"); + println!("ExecuteConsoleCommand: {}", unsafe { &*string }); }); +// Executes pending RCON command CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { let lock = Arc::clone(&crate::resolvers::rcon::LAST_COMMAND); + let mut fstring = FString::default(); if let Some(cmd) = crate::resolvers::rcon::LAST_COMMAND.lock().unwrap().as_ref() { - let mut fstring = FString::from( + fstring = FString::from( widestring::U16CString::from_str(cmd.as_str()) .unwrap() .as_slice_with_nul()); - { - println!("executing command"); - *lock.lock().unwrap() = Some("".to_string()); - unsafe { o_ExecuteConsoleCommand.call(&mut fstring); } - println!("executed command"); - } + } + + if fstring.len() > 1 { + println!("executing command"); + *lock.lock().unwrap() = None; + unsafe { o_ExecuteConsoleCommand.call(&mut fstring); } + println!("executed command"); } }); +// FIXME: Nihi: stub CREATE_HOOK!(FEngineLoopInit, (engine_loop:*mut c_void), { println!("Engine Loop initialized!!"); }); @@ -72,6 +79,76 @@ define_pattern_resolver![GetTBLGameMode, { OTHER : ["40 53 48 83 EC 20 48 8B D9 48 85 C9 74 60 48 8B 01 FF 90 ?? ?? ?? ?? 48 85 C0 75 23 0F 1F 40 00 48 8B 5B 20 48 85 DB 74 11 48 8B 03 48 8B CB FF 90 ?? ?? ?? ?? 48 85 C0 74 E6 48 85 C0 74 2F 48 8B 98 28"] }]; + + + +// Handle Game chat: commands, chat log, system messages define_pattern_resolver!(ClientMessage, [ "4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 73 18 49 8B D8 49 89 43 C8 48 8B F1 49 89 43 D0 49 89 43 D8 49 8D 43" -]); \ No newline at end of file +]); + +mod client_message { + use regex::Regex; + + #[derive(Debug)] + pub struct ChatMessage<'a> { + pub name: &'a str, + pub channel: u32, + pub message: &'a str, + } + + pub fn parse_chat_line(line: &str) -> Option { + static RE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { + Regex::new(r"^(\w+)\s+\w+\s+<(\d+)>:\s+(.*)$").unwrap() + }); + + RE.captures(line).map(|caps| ChatMessage { + name: caps.get(1).unwrap().as_str(), + channel: caps.get(2).unwrap().as_str().parse().ok().unwrap(), + message: caps.get(3).unwrap().as_str(), + }) + } +} + +// void __thiscall APlayerController::ClientMessage(APlayerController *this,FString *S,FName Type,float MsgLifeTime) +CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLifeTime: f32), { + let cmd_store = Arc::clone(&LAST_COMMAND); + // FIXME: Nihi: better way to access it? + let string_ref: &FString = unsafe{ &*S }; + let message2 = string_ref.to_string(); + println!("[ClientMessages] S: \'{}\', Type: \'{}\'({}), MsgLifeTime: \'{}\' ", message2, Type, Type.number, MsgLifeTime); + + // Chat commands + // TODO: handle commands tranformed by ChatHooks + // e.g. ".hello" -> '<7>: Console Command: hello', Type: 'None'(0), MsgLifeTime: '0' + // this needs to provide the playfabid somehow + unsafe { + let cmd_filter = |c| ['/', '.'].contains(&c); + match client_message::parse_chat_line(message2.as_str()) { + Some(chat) => { + match chat.message.starts_with(cmd_filter) { + true => { + let cmd_trimmed = chat.message.trim_start_matches(cmd_filter); + println!("-> Got console command from \'{}\' ch{}: {}", + chat.name, + chat.channel, + cmd_trimmed); + // Set pending console command + // FIXME: Nihi: Add auth or allow only offline + *cmd_store.lock().unwrap() = Some(cmd_trimmed.trim().to_string()); + println!("Pending: {:?}", Some(cmd_trimmed.trim().to_string())); + // TODO: save command to log + } + false => { + println!("-> User message: {:?}", chat); + // TODO: save user message + } + }; + } + _ => { + println!("System message"); + // TODO: Write message to logfile + } + } + } +}); \ No newline at end of file diff --git a/sleuth/src/resolvers/backend_hooks.rs b/sleuth/src/resolvers/backend_hooks.rs index 709c9f4..8754d83 100644 --- a/sleuth/src/resolvers/backend_hooks.rs +++ b/sleuth/src/resolvers/backend_hooks.rs @@ -60,7 +60,7 @@ define_pattern_resolver!(SendRequest, [ // }, // |ctx, patterns| { // let mut results = Vec::new(); -// // FIXME: group sigs by type, run Signature func on multiple +// // FIXME: Nihi: group sigs by type, run Signature func on multiple // for pat in patterns { // // match pat.kind { // // SignatureKind::Call => println!("call"), diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 80444c9..8e08611 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -177,7 +177,7 @@ macro_rules! define_pocess { // use patternsleuth::MemoryTrait; define_pocess!(@emit_process_inline $name, |$ctx, $patterns| { let mut results = Vec::new(); - // FIXME: group sigs by type, run Signature func on multiple + // FIXME: Nihi: group sigs by type, run Signature func on multiple for pat in $patterns { // match pat.kind { // SignatureKind::Call => println!("call"), @@ -193,8 +193,8 @@ macro_rules! define_pocess { }}; // Scan for Xrefs, return last - // FIXME: expects max. 2 results - // FIXME: handles only one pattern + // FIXME: Nihi: expects max. 2 results + // FIXME: Nihi: handles only one pattern (@emit_body $name:ident, XrefLast, $ctx:ident, $patterns:ident) => {{ use patternsleuth::resolvers::unreal::util; use patternsleuth::resolvers::ensure_one; @@ -204,7 +204,7 @@ macro_rules! define_pocess { let refs = util::scan_xrefs($ctx, &strings).await; let mut fns = util::root_functions($ctx, &refs)?; if fns.len() == 2 { - fns[0] = fns[1]; // FIXME: deque? last? + fns[0] = fns[1]; // FIXME: Nihi: deque? last? fns.pop(); } ensure_one(fns)? @@ -516,7 +516,7 @@ macro_rules! wrap_process_macro { }; } -// FIXME: move somewhere so those are not buried? +// FIXME: Nihi: move somewhere so those are not buried? // Possible to auto generate from macro? wrap_process_macro!(Simple); wrap_process_macro!(Call); diff --git a/sleuth/src/scan.rs b/sleuth/src/scan.rs index d6bdc4a..892ec9e 100644 --- a/sleuth/src/scan.rs +++ b/sleuth/src/scan.rs @@ -35,10 +35,10 @@ pub fn scan() -> Result, String> { let mut offsets: HashMap = HashMap::new(); let mut offsets_resolver: HashMap = HashMap::new(); - // FIXME: ugh + // FIXME: Nihi: ugh for (resolver, resolution) in resolvers.iter().zip(&resolution) { if let Ok(r) = resolution { - // FIXME: Less nasty way? + // FIXME: Nihi: Less nasty way? if let Some(hex) = format!("{r:?}") .split(['(', ')']) .nth(1) diff --git a/sleuth/src/ue.rs b/sleuth/src/ue.rs index ce927b5..f80bae7 100644 --- a/sleuth/src/ue.rs +++ b/sleuth/src/ue.rs @@ -1,6 +1,6 @@ // From trumank/patternsleuth // https://github.com/trumank/patternsleuth/blob/master/examples/dll_hook/src/ue.rs -// FIXME: the file is unchanged. Find a way to get use it via git without submodules +// FIXME: Nihi: the file is unchanged. Find a way to get use it via git without submodules use std::{ cell::UnsafeCell, diff --git a/src/hooks/admin_control.hpp b/src/hooks/admin_control.hpp index f6fa29f..678d6cc 100644 --- a/src/hooks/admin_control.hpp +++ b/src/hooks/admin_control.hpp @@ -180,4 +180,4 @@ CREATE_HOOK( } o_ClientMessage(this_ptr, param_1, param_2, param_3); } -AUTO_HOOK(ClientMessage); \ No newline at end of file +// AUTO_HOOK(ClientMessage); \ No newline at end of file From 13e868455d23d5de2b434e592412bb97790cb275 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Mon, 9 Jun 2025 19:40:11 +0200 Subject: [PATCH 05/18] CLI args, log4rs logs, slog macros, console cmd stdin, syslog --- sleuth/Cargo.toml | 7 + sleuth/src/chiv2.rs | 49 +++ sleuth/src/lib.rs | 421 ++++++++++++++++++++++++-- sleuth/src/resolvers/admin_control.rs | 46 ++- sleuth/src/resolvers/macros.rs | 51 +++- sleuth/src/resolvers/mod.rs | 2 +- sleuth/src/resolvers/rcon.rs | 25 +- sleuth/src/scan.rs | 2 + sleuth/src/tools/log_macros.rs | 164 ++++++++++ sleuth/src/tools/mod.rs | 5 + sleuth/src/tools/syslog.rs | 308 +++++++++++++++++++ 11 files changed, 1021 insertions(+), 59 deletions(-) create mode 100644 sleuth/src/chiv2.rs create mode 100644 sleuth/src/tools/log_macros.rs create mode 100644 sleuth/src/tools/mod.rs create mode 100644 sleuth/src/tools/syslog.rs diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 6c91043..21a1829 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -31,6 +31,11 @@ parking_lot = "0.12.1" simple-log = "1.6.0" # thread_local = "1.1.7" regex = "1.11" +chrono = "0.4.0" +log = "0.4" +log4rs = "1.3.0" +clap = { version = "4.5.0", features = ["derive"] } # cli arg parsing +shell-words = "1.1" retour = { version = "0.3.1", features = ["static-detour"]} windows = { version = "0.52.0", features = ["Win32_Foundation", @@ -50,6 +55,8 @@ windows = { version = "0.52.0", features = ["Win32_Foundation", "Win32_System_Memory", "Win32_System_ProcessStatus", ]} +rand = "0.9.1" +async-std = "1.13.1" # [target."cfg(windows)".dev-dependencies.windows] # version = "0.48" diff --git a/sleuth/src/chiv2.rs b/sleuth/src/chiv2.rs new file mode 100644 index 0000000..64513c5 --- /dev/null +++ b/sleuth/src/chiv2.rs @@ -0,0 +1,49 @@ + + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EChatType { + AllSay, + TeamSay, + Whisper, + Admin, + Objective, + System, + ServerSay, + Debug, + CrosshairMsg, + Backend, + Party, + Spectator, + ClosedCaption, + ClosedCaptionMason, + ClosedCaptionAgatha, + MAX, +} + +use std::str::FromStr; + +impl FromStr for EChatType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "AllSay" => Ok(EChatType::AllSay), + "TeamSay" => Ok(EChatType::TeamSay), + "Whisper" => Ok(EChatType::Whisper), + "Admin" => Ok(EChatType::Admin), + "Objective" => Ok(EChatType::Objective), + "System" => Ok(EChatType::System), + "ServerSay" => Ok(EChatType::ServerSay), + "Debug" => Ok(EChatType::Debug), + "CrosshairMsg" => Ok(EChatType::CrosshairMsg), + "Backend" => Ok(EChatType::Backend), + "Party" => Ok(EChatType::Party), + "Spectator" => Ok(EChatType::Spectator), + "ClosedCaption" => Ok(EChatType::ClosedCaption), + "ClosedCaptionMason" => Ok(EChatType::ClosedCaptionMason), + "ClosedCaptionAgatha" => Ok(EChatType::ClosedCaptionAgatha), + "MAX" => Ok(EChatType::MAX), + _ => Err(()), + } + } +} diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 51650ac..f4ccd13 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -3,15 +3,19 @@ mod resolvers; mod scan; use core::fmt; -use std::env; +use std::time::Duration; +use std::{env, thread}; use std::fs::File; use std::io::{BufReader, Read}; use std::io::{BufWriter, Write}; use std::collections::HashMap; use std::path::PathBuf; mod ue; +mod tools; +mod chiv2; use anyhow::Result; +use chrono::offset; use patternsleuth::resolvers::unreal::blueprint_library::UFunctionBind; use patternsleuth::resolvers::unreal::*; // use dll_hook::ue::*; @@ -23,11 +27,146 @@ use patternsleuth::resolvers::unreal::{fname::FNameToString, gmalloc::GMalloc, guobject_array::{FUObjectArrayAllocateUObjectIndex, FUObjectArrayFreeUObjectIndex, GUObjectArray} }; +use resolvers::macros; use serde::Serialize; use serde_json::to_writer_pretty; +use tools::syslog::init_syslog; use winapi::um::winnt::FILE_APPEND_DATA; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; +use log::{debug, error, info, warn}; +use clap::{command, CommandFactory, FromArgMatches, Parser, Subcommand}; +use async_std::io; + +pub static test_intro: &str = "\x1b[38;5;228m\ +\ +▄████████ ▄█ █▄ ▄█ ▄█ █▄ ▄████████ ▄█ ▄████████ ▄██ ▄ ▄█ ▄█ \r\n\ +███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ██▄ ███ ███ \r\n\ +███ █▀ ███ ███ ███▌ ███ ███ ███ ███ ███ ███ ███ ███▄▄▄███ ███▌ ███▌\r\n\ +███ ▄███▄▄▄▄███▄▄ ███▌ ███ ███ ███ ███ ███ ▄███▄▄▄▄██▀ ▀▀▀▀▀▀███ ███▌ ███▌\r\n\ +███ ▀▀███▀▀▀▀███▀ ███▌ ███ ███ ▀███████████ ███ ▀▀███▀▀▀▀▀ ▄██ ███ ███▌ ███▌\r\n\ +███ █▄ ███ ███ ███ ███ ███ ███ ███ ███ ▀███████████ ███ ███ ███ ███ \r\n\ +███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ▄ ███ ███ ███ ███ ███ ███ \r\n\ +████████▀ ███ █▀ █▀ ▀██████▀ ███ █▀ █████▄▄██ ███ ███ ▀█████▀ █▀ █▀ \r\n\ + ▀ ███ ███ \r\n\ +\x1b[38;5;1m███ █▄ ███▄▄▄▄ ▄████████ ▄█ █▄ ▄████████ ▄█ ███▄▄▄▄ ▄████████ ████████▄ \r\n\ +███ ███ ███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ███ ▀███ \r\n\ +███ ███ ███ ███ ███ █▀ ███ ███ ███ ███ ███▌ ███ ███ ███ █▀ ███ ███ \r\n\ +███ ███ ███ ███ ███ ▄███▄▄▄▄███▄▄ ███ ███ ███▌ ███ ███ ▄███▄▄▄ ███ ███ \r\n\ +███ ███ ███ ███ ███ ▀▀███▀▀▀▀███▀ ▀███████████ ███▌ ███ ███ ▀▀███▀▀▀ ███ ███ \r\n\ +███ ███ ███ ███ ███ █▄ ███ ███ ███ ███ ███ ███ ███ ███ █▄ ███ ███ \r\n\ +███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄███ \r\n\ +████████▀ ▀█ █▀ ████████▀ ███ █▀ ███ █▀ █▀ ▀█ █▀ ██████████ ████████▀ \r\n\ +\x1b[38;5;255m \r\n\ + "; + +pub static test_intro3: &str = "\ +| █ \r\n\ +| ███████ \r\n\ +| ██ ███ \r\n\ +| ███ █████ ██ \r\n\ +| █████ █ █████ \r\n\ +| ████████ ███████ \r\n\ +| ███████████ ███ ██ \r\n\ +| █████████████ ███ ███\r\n\ +| ██ ███ █████ \r\n\ +| ██ ████ █ \r\n\ +| ███ ████████ ██ \r\n\ +| ██████████ █████████ \r\n\ +| ██████ ████████ \r\n\ +| █████ █████ \r\n\ +| ███████████ \r\n\ +| ███████ ██ \r\n\ +| ██ █ █ \r\n\ +"; + +// pub static test: &str = "\x1b[38;5;196m▄\x1b[0m\x1b[38;5;202m█\x1b[0m\x1b[38;5;226m█\x1b[0m\x1b[38;5;46m█\x1b[0m\x1b[38;5;21m█\x1b[0m\x1b[38;5;93m█\x1b[0m\x1b[38;5;201m█\x1b[0m\x1b[38;5;196m█\x1b[0m\x1b[38;5;202m█\x1b[0m \x1b[38;5;226m▄\x1b[0m\x1b[38;5;46m█\x1b[0m \x1b[38;5;21m█\x1b[0m\x1b[38;5;93m▄\x1b[0m \x1b[38;5;201m▄\x1b[0m\x1b[38;5;196m█\x1b[0m \x1b[38;5;202m▄\x1b[0m\x1b[38;5;226m█\x1b[0m \x1b[38;5;46m█\x1b[0m\x1b[38;5;21m▄\x1b[0m \x1b[38;5;93m▄\x1b[0m\x1b[38;5;201m█\x1b[0m\x1b[38;5;196m█\x1b[0m\x1b[38;5;202m█\x1b[0m\x1b[38;5;226m█\x1b[0m\x1b[38;5;46m█\x1b[0m\x1b[38;5;21m█\x1b[0m\x1b[38;5;93m█\x1b[0m\x1b[38;5;201m█\x1b[0m \x1b[38;5;196m▄\x1b[0m\x1b[38;5;202m█\x1b[0m \x1b[38;5;226m▄\x1b[0m\x1b[38;5;46m█\x1b[0m\x1b[38;5;21m█\x1b[0m\x1b[38;5;93m█\x1b[0m\x1b[38;5;201m█\x1b[0m\x1b[38;5;196m█\x1b[0m\x1b[38;5;202m█\x1b[0m\x1b[38;5;226m█\x1b[0m\x1b[38;5;46m█\x1b[0m \x1b[38;5;21m▄\x1b[0m\x1b[38;5;93m█\x1b[0m\x1b[38;5;201m█\x1b[0m \x1b[38;5;196m▄\x1b[0m \x1b[38;5;202m▄\x1b[0m\x1b[38;5;226m█\x1b[0m \x1b[38;5;46m▄\x1b[0m\x1b[38;5;21m█\x1b[0m\n\x1b[38;5;93m█\x1b[0m\x1b[38;5;201m█\x1b[0m\x1b[38;5;196m█\x1b[0m \x1b[38;5;202m█\x1b[0m\x1b[38;5;226m█\x1b[0m\x1b[38;5;46m█\x1b[0m \x1b[38;5;21m█\x1b[0m\x1b[38;5;93m█\x1b[0m\x1b[38;5;201m█\x1b[0m \x1b[38;5;196m█\x1b[0m\x1b[38;5;202m█\x1b[0m\x1b[38;5;226m█\x1b[0m \x1b[38;5;46m█\x1b"; + + +// CLI args +#[derive(Debug, Subcommand)] +enum Commands { + TBL, + NONE, +} +// #[derive(Parser, Debug)] +// #[command(name = "Chivalry 2 Unchained", version = "1.0", author = "Unchained Team")] +#[derive(Parser, Debug)] +#[command(name = "Chivalry 2 Unchained", author = "Unchained Team", version, about, long_about = None)] +// FIXME: possible to skip unhandled args? +struct CLIArgs { + // #[arg()] + // positional_args: Vec, + // #[arg()] + // game_id: String, + + #[arg(long = "next-map-mod-actors")] + next_mod_actors: Option>, + + #[arg(long = "all-mod-actors")] + mod_paks: Option>, + + #[arg(long = "unchained")] + is_unchained: bool, + // + #[arg(long = "rcon")] + rcon_port: Option, + // // + #[arg(long = "desync-patch")] + apply_desync_patch: bool, + // + #[arg(long = "use-backend-banlist")] + use_backend_banlist: bool, + // + #[arg(long = "nullrhi")] + is_headless: bool, + // + #[arg(long = "next-map-name")] + next_map: Option, + // + #[arg(long = "playable-listen")] + playable_listen: bool, + // // + #[arg(long = "server-browser-backend")] + server_browser_backend: Option, + // // + #[arg(long = "server-password")] + server_password: Option, + // s + #[arg(long = "platform")] + platform: Option, + + #[arg(long = "saveddirsuffix")] + saveddirsuffix: Option, + + + // UNHANDLED START + #[arg(long = "AUTH_LOGIN")] + AUTH_LOGIN: Option, + #[arg(long = "AUTH_PASSWORD")] + AUTH_PASSWORD: Option, + #[arg(long = "AUTH_TYPE")] + AUTH_TYPE: Option, + #[arg(long = "epicapp")] + epicapp: Option, + #[arg(long = "epicenv")] + epicenv: Option, + #[arg(long = "EpicPortal")] + EpicPortal: bool, + #[arg(long = "epicusername")] + epicusername: Option, + #[arg(long = "epicuserid")] + epicuserid: Option, + #[arg(long = "epiclocale")] + epiclocale: Option, + #[arg(long = "epicsandboxid")] + epicsandboxid: Option, + // UNHANDLED END + + #[arg(trailing_var_arg = true)] + pub extra_args: Vec, +} + // IEEE use std::arch::x86_64::_mm_crc32_u8; #[target_feature(enable = "sse4.2")] @@ -74,7 +213,7 @@ fn expand_env_path(path: &str) -> Option { pub fn dump_builds() -> Result<()> { let builds_path = expand_env_path(r"%LOCALAPPDATA%\Chivalry 2\Saved\Config\c2uc.builds.json").unwrap().to_path_buf(); - println!("JSON PATH {}", builds_path.to_string_lossy()); + // println!("JSON PATH {}", builds_path.to_string_lossy()); let file = File::create(builds_path)?; let mut writer = BufWriter::new(file); let mut data = HashMap::new(); @@ -90,7 +229,7 @@ pub fn dump_builds() -> Result<()> { // Ok(path) => file_path = path.to_string_lossy().into(), // Err(e) => eprintln!("Failed to get path: {}", e), // } - println!("Current executable path: {:?}", file_path); + // println!("Current executable path: {:?}", file_path); let crc32 = unsafe { crc32_from_file(&file_path) }.expect("Failed to compute CRC"); @@ -116,67 +255,271 @@ pub fn dump_builds() -> Result<()> { } +// TODO: replace this with registration similar to resolvers +type HookFn = unsafe fn(usize, HashMap) -> Result<(), Box>; +// macro_rules! attach_hooks_list { +// ( [ $( $pattern:ident ),+ $(,)? ]) => { +// paste::paste!{ +// { +// let hooks: HashMap= [ $( ( stringify![$pattern], [] ) ),+ ] +// .into_iter().map(|(name, func)| (name.to_string(), func)).collect(); +// Ok(hooks) +// } +// } +// }; +// } + +macro_rules! attach_hooks_list { + ( [ $( $pattern:ident ),+ $(,)? ]) => {{ + use std::collections::HashMap; + paste::paste! { + let hooks: HashMap<&'static str, HookFn> = [ + $((stringify!($pattern), [] as HookFn)),+ + ] + .into_iter() + .collect(); + + hooks + } + }}; +} + + pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) -> Result<(), Box> { // attach_GameEngineTick(base_address, offsets).unwrap(); - resolvers::admin_control::attach_UGameEngineTick(base_address, offsets.clone()).unwrap(); - resolvers::admin_control::attach_ExecuteConsoleCommand(base_address, offsets.clone()).unwrap(); - resolvers::admin_control::attach_FEngineLoopInit(base_address, offsets.clone()).unwrap(); - resolvers::admin_control::attach_ClientMessage(base_address, offsets.clone()).unwrap(); + info!("Attaching hooks:"); + + let hooks_new = attach_hooks_list![[ + UGameEngineTick, + ExecuteConsoleCommand, + FEngineLoopInit, + ClientMessage, + SomeRandomFunction, + ]]; + + use resolvers::admin_control::*; + // use crate::resolvers::macros; + hooks_new.iter().for_each(|(s, f)| { + match (f)(base_address, offsets.clone()) { + Ok(_) => info!["☑ {} ", s], + Err(e) => { + serror!(func, file;"☐ {}: {}", s.to_uppercase(), e); + serror!(func, file, line;"☐ {}: {}", s.to_uppercase(), e); + serror!(func, file, line, column;"☐ {}: {}", s.to_uppercase(), e); + serror!(func, file, line, mod;"☐ {}: {}", s.to_uppercase(), e); + serror!("☐ {}: {}", s.to_uppercase(), e); + }, + } + }); + + // resolvers::admin_control::attach_UGameEngineTick(base_address, offsets.clone()).unwrap(); + // resolvers::admin_control::attach_ExecuteConsoleCommand(base_address, offsets.clone()).unwrap(); + // resolvers::admin_control::attach_FEngineLoopInit(base_address, offsets.clone()).unwrap(); + // resolvers::admin_control::attach_ClientMessage(base_address, offsets.clone()).unwrap(); Ok(()) } -unsafe fn init_globals() { +// We're using a mix of cli arg types, normalize them to --key value(s) +// e.g. -rcon 9001, -epicsomething=blablabla, Port=7777 +// This function converts all of those to --convention. It also drops game_identifier +// To parse it all with clap, it checks against entries in CLIArgs and filters out unhandled ones +fn normalize_and_filter_args>(args: I) -> Vec { + let mut args = args.into_iter(); + let bin_name = args.next().unwrap_or_else(|| "app".to_string()); + + let known_flags: Vec = CLIArgs::command() + .get_arguments() + .filter_map(|a| a.get_long().map(|s| format!("--{}", s))) + .collect(); + + let mut result = vec![bin_name]; + let mut args = args.peekable(); + let mut last_flag: Option = None; + let mut last_opt: Option = None; + + + while let Some(arg) = args.next() { + // println!("-- LINE: {arg}"); + // Normalize `key=value` and `-flag` → `--flag` + let (flag, value_opt): (String, Option) = + if let Some((k, v)) = arg.split_once('=') { + (format!("--{}", k.trim_start_matches('-')), Some(v.to_string())) + } else if arg.starts_with('-') && !arg.starts_with("--") && arg.len() > 2 { + (format!("--{}", &arg[1..]), None) + } else { + (arg.clone(), None) + }; + + // println!("cur: {flag}"); + let cur_flag = flag.clone(); + if known_flags.contains(&flag) { + result.push(flag); + if let Some(v) = value_opt { + // println!("option: {v}"); + last_opt = Some(v.clone()); + result.push(v); + } + else if let Some(peek) = args.peek() { + if !peek.starts_with('-') { + let var = args.next().unwrap(); + // print!("pushing {var}"); + result.push(var); + } + } + } + // args can split an option (e.g. --name Not Sure) + else if result.len() > 0 && !flag.starts_with('-') { + let last_valid = result.last().unwrap(); + if let Some(last) = last_flag { + // println!("Last '{last}' last valid '{last_valid}'"); + if let Some(o) = &last_opt { + // println!("Last '{}' last valid {} last option '{}' equal: {}", last, last_valid, o, o == last_valid); + // println!("Res: {} Trailing string {}, last flag {}, last result {}",result.len(), flag, last, last_valid); + if o == last_valid { + if let Some(last_mut) = result.last_mut() { + last_mut.push_str(" "); + last_mut.push_str(&cur_flag); + } + } + } + } + } + last_flag = Some(cur_flag); + } + + result +} + +unsafe fn load_cli() -> Result<(CLIArgs), clap::error::Error> { + let args = std::env::args(); + let parsed = normalize_and_filter_args(args); + let cli = CLIArgs::try_parse_from(parsed).expect("Failed to parse CLI atgs"); + // println!("Parsed CLI: {:#?}", cli); + Ok(cli) +} +unsafe fn init_globals() -> Result<(), clap::error::Error>{ + + let platform = match env::args().any(|arg| arg == "-epicapp=Peppermint") { + true => PlatformType::EGS, + false => PlatformType::STEAM + }; let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); - println!("starting scan"); - // FIXME: Nihi: use the results from scan function + // FIXME: replace old references + PLATFORM.set(platform).expect("Platform already set"); + BASE_ADDR.set(exe.base_address).expect("BASE_ADDR already set"); + // debug!("Platform: {} base_addr: '0x{:x?}'", platform, exe.base_address); + + // Load CLI ARGS + let args = load_cli().expect("Failed to load CLI ARGS"); let resolution = exe.resolve(DllHookResolution::resolver()).unwrap(); - println!("finished scan"); - println!("results: {:?}", resolution); + // println!("results: {:?}", resolution); let guobject_array: &'static ue::FUObjectArray = &*(resolution.guobject_array.0 as *const ue::FUObjectArray); + GLOBALS = Some(Globals { guobject_array: guobject_array.into(), resolution, main_thread_id: std::thread::current().id(), last_command: None, + base_address: exe.base_address, + is_server: false, + cli_args: args, + platform }); + Ok(()) +} + + + +// █: 743 +// ▀: 67 +// ▄: 66 +// r: 18 +// n: 18 +// ▌: 13 +fn intro() { + let mut color_index = 16; + let max_color = 231; + for line in test_intro.lines() { + for ch in line.chars() { + let color = format!("\x1b[38;5;{}m", color_index); + print!("{}{}\x1b[0m", color, ch); + + color_index += 1; + if color_index > max_color { + color_index = 16; + } + thread::sleep(Duration::from_micros(20)); + } + println!(); // new line + } +} +// https://stackoverflow.com/questions/38088067/equivalent-of-func-or-function-in-rust +// https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=df5975cd589ae7286a769e1c70e7715d +macro_rules! function { + () => {{ + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + &name[..name.len() - 3] + }} } +// define_pocess! #[no_mangle] -pub extern "C" fn generate_json() -> u8 { - println!("test asd"); - - let platform = match env::args().any(|arg| arg == "-epicapp=Peppermint") { - true => PlatformType::EGS, - false => PlatformType::STEAM - }; +pub extern "C" fn generate_json() -> u8 { + // intro(); + // thread::sleep(Duration::from_secs(10)); + print!("{test_intro}"); + print!("\n"); + init_syslog().expect("Failed to init syslog"); + unsafe { init_globals() }; std::thread::spawn(|| { resolvers::rcon::handle_rcon(); }); - PLATFORM.set(platform).expect("Platform already set"); - let image = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); - let exe = image; - println!("GAME '{:x?}'", exe.base_address); - BASE_ADDR.set(exe.base_address).expect("BASE_ADDR already set"); + (|| { + mod module { + pub trait Trait { + fn function(&self) { + println!("{} (in {} [{}:{}:{}])", + function!(), module_path!(), file!(), line!(), column!() + ); + } + } + impl Trait for () {} + } + module::Trait::function(&()); + })(); + + // Init syslog + // info!("Info blabla"); + // warn!("Warning! ‼"); + // debug!("DEBUG MESSAGE"); + // error!("ERRROR "); + + // PLATFORM.set(platform).expect("Platform already set"); + let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); - unsafe { init_globals() }; - // let scan = scan::scan(); let offsets = scan::scan().expect("Failed to scan"); let len_u8 = offsets.len() as u8; // FIXME: Nihi: ? - let offset_copy = offsets.clone(); - // let base_addr = scan::scan().1; unsafe { attach_hooks(exe.base_address, offsets).unwrap(); } dump_builds().expect("Failed to dump builds JSON"); + + std::thread::spawn(|| { + resolvers::rcon::handle_cmd(); + }); len_u8 } @@ -224,6 +567,10 @@ pub struct Globals { guobject_array: parking_lot::FairMutex<&'static ue::FUObjectArray>, main_thread_id: std::thread::ThreadId, last_command: Option, + platform: PlatformType, + base_address: usize, + is_server: bool, + cli_args: CLIArgs, } impl Globals { @@ -249,9 +596,21 @@ impl Globals { *self.guobject_array.data_ptr() } - // pub fn last_command(&self) -> Option<&mut ue::FString> { - // self.last_command.as_mut() - // } + pub fn get_platform(&self) -> PlatformType { + self.platform + } + + pub fn get_base_address(&self) -> usize { + self.base_address + } + + pub fn is_server(&self) -> bool { + self.is_server + } + + fn args(&self) -> &CLIArgs { + &self.cli_args + } } pub fn globals() -> &'static Globals { diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 8cc50ab..b7aa7cc 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -1,11 +1,14 @@ +use client_message::{parse_chat_line, ChatMessage}; +use log::{debug, info, warn}; use retour::static_detour; use std::{collections::HashMap, sync::Arc}; use std::error::Error; use std::os::raw::c_void; use std::mem; use crate::resolvers::rcon::LAST_COMMAND; +use crate::chiv2::*; use crate::{globals, ue::*}; define_pattern_resolver![UTBLLocalPlayer_Exec, { @@ -25,6 +28,9 @@ define_pattern_resolver!(ExecuteConsoleCommand, [ CREATE_HOOK!(ExecuteConsoleCommand, (string:*mut FString), { println!("ExecuteConsoleCommand: {}", unsafe { &*string }); }); +CREATE_HOOK!(SomeRandomFunction, (string:*mut FString), { + println!("SomeRandomFunction: {}", unsafe { &*string }); +}); // Executes pending RCON command CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { @@ -38,16 +44,15 @@ CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { } if fstring.len() > 1 { - println!("executing command"); + warn!("Execuing Command: {}", fstring); *lock.lock().unwrap() = None; unsafe { o_ExecuteConsoleCommand.call(&mut fstring); } - println!("executed command"); } }); // FIXME: Nihi: stub CREATE_HOOK!(FEngineLoopInit, (engine_loop:*mut c_void), { -println!("Engine Loop initialized!!"); +// println!("Engine Loop initialized!!"); }); // FText* __cdecl FText::AsCultureInvariant(FText* __return_storage_ptr__, FString* param_1) @@ -88,8 +93,12 @@ define_pattern_resolver!(ClientMessage, [ ]); mod client_message { + use log::warn; use regex::Regex; + use crate::chiv2::EChatType; + use std::str::FromStr; + #[derive(Debug)] pub struct ChatMessage<'a> { pub name: &'a str, @@ -108,6 +117,15 @@ mod client_message { message: caps.get(3).unwrap().as_str(), }) } + + pub fn parse_msg_line(line: &str) -> Option<(EChatType, &str)> { + let mut parts = line.splitn(2, ": "); + warn!["parts: {:?}", parts]; + let msg_type_str = parts.next()?; + let msg = parts.next()?; + let msg_type = EChatType::from_str(msg_type_str).ok()?; + Some((msg_type, msg)) + } } // void __thiscall APlayerController::ClientMessage(APlayerController *this,FString *S,FName Type,float MsgLifeTime) @@ -115,8 +133,10 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi let cmd_store = Arc::clone(&LAST_COMMAND); // FIXME: Nihi: better way to access it? let string_ref: &FString = unsafe{ &*S }; - let message2 = string_ref.to_string(); - println!("[ClientMessages] S: \'{}\', Type: \'{}\'({}), MsgLifeTime: \'{}\' ", message2, Type, Type.number, MsgLifeTime); + let message = string_ref.to_string(); + // TODO: Does this need to handle lines separately? + // info!("[ClientMessages] S: \'{:?}\', Type: \'{}\'({}), MsgLifeTime: \'{}\' ", message.replace("\r\n", " "), Type, Type.number, MsgLifeTime); + info!("[client] \'{:?}\'", message.replace("\r\n", " ")); // Chat commands // TODO: handle commands tranformed by ChatHooks @@ -124,29 +144,35 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi // this needs to provide the playfabid somehow unsafe { let cmd_filter = |c| ['/', '.'].contains(&c); - match client_message::parse_chat_line(message2.as_str()) { + match client_message::parse_chat_line(message.as_str()) { Some(chat) => { match chat.message.starts_with(cmd_filter) { true => { let cmd_trimmed = chat.message.trim_start_matches(cmd_filter); - println!("-> Got console command from \'{}\' ch{}: {}", + debug!("-> Got console command from \'{}\' ch{}: {}", chat.name, chat.channel, cmd_trimmed); // Set pending console command // FIXME: Nihi: Add auth or allow only offline *cmd_store.lock().unwrap() = Some(cmd_trimmed.trim().to_string()); - println!("Pending: {:?}", Some(cmd_trimmed.trim().to_string())); + debug!("Pending: {:?}", Some(cmd_trimmed.trim().to_string())); // TODO: save command to log } false => { - println!("-> User message: {:?}", chat); + debug!("-> User message: {:?}", chat); // TODO: save user message } }; } _ => { - println!("System message"); + debug!("System message"); + if let Some(msg) = parse_chat_line(message.as_str()) { + warn!("Chat: channel {}, name {}, message {}, ", msg.channel, msg.name, msg.message); + if let Some(chat_mnsg) = client_message::parse_msg_line(msg.message) { + warn!("Text: {:?} {}", chat_mnsg.0, chat_mnsg.1); + } + } // TODO: Write message to logfile } } diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 8e08611..a45ed75 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -14,7 +14,22 @@ // #[macro_use] // extern crate paste; // concat strings - +// unsafe fn attach_TestFunction(base_address: usize, offsets: HashMap) -> Result<(), Box>{ +// let address = base_address + offsets["TestFunction"] as usize; +// let target: FnTestFunction = mem::transmute(address); +// type FnTestFunction = unsafe extern "C" fn(*mut c_void, f32, u8); +// static_detour! { +// static TestFunction: unsafe extern "C" fn(*mut c_void, f32, u8); +// } +// fn detour_fkt(engine:*mut c_void, delta:f32, state:u8) { +// println!("rust TestFunction delta: {}", delta); +// unsafe { TestFunction.call( engine, delta, state) } +// } +// TestFunction +// .initialize(target, detour_fkt)? +// .enable()?; +// Ok(()) +// } /// ```rust /// unsafe fn attach_GameEngineTick(base_address: usize, offsets: HashMap) -> Result<(), Box>{ /// let address = base_address + offsets["UGameEngineTick"] as usize; @@ -63,18 +78,28 @@ macro_rules! CREATE_HOOK { #[allow(non_snake_case)] pub unsafe fn [](base_address: usize, offsets: HashMap) -> Result<(), Box>{ + // pub unsafe fn [](base_address: usize, offsets: HashMap) -> Result<(), Box { - // ( $( $arg: $ty ),+ ); - let address = base_address + offsets[stringify![$name]] as usize; - let target: [] = mem::transmute(address); - - type [] = unsafe extern "C" fn ($( $ty ),+ ); - - [] - .initialize(target, [<$name _detour_fkt>])? - .enable()?; - - Ok(()) + // TODO: propagate error? why panic + match offsets.get(stringify![$name]) { + None => { + Err("No address found.".into())// Err("No Address found."),//log::error!["Failed to attach: {}", stringify![$name]], + }, + Some(_) => { + // log::info!["attached"]; + // ( $( $arg: $ty ),+ ); + let address = base_address + offsets[stringify![$name]] as usize; + let target: [] = mem::transmute(address); + + type [] = unsafe extern "C" fn ($( $ty ),+ ); + + [] + .initialize(target, [<$name _detour_fkt>])? + .enable()?;; + + Ok(()) + }, + } } } @@ -126,6 +151,7 @@ macro_rules! CREATE_HOOK { // }; // } + use std::{future::Future, pin::Pin}; use patternsleuth::resolvers::{AsyncContext, ResolveError}; @@ -214,6 +240,7 @@ macro_rules! define_pocess { // Wrap code and define_pattern_resolver (@emit_process_inline $name:ident, |$ctx:ident, $patterns:ident| $body:block) => {{ let result = $body; + log::debug![target:"sig_scan", "[ 0x{:#x?} ]: {}", result, stringify![$name]]; Ok($name(result)) }}; } diff --git a/sleuth/src/resolvers/mod.rs b/sleuth/src/resolvers/mod.rs index 66f7f93..ebe58b1 100644 --- a/sleuth/src/resolvers/mod.rs +++ b/sleuth/src/resolvers/mod.rs @@ -1,5 +1,5 @@ #[macro_use] -mod macros; +pub mod macros; // use dll_hook::ue; use once_cell::sync::OnceCell; diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs index ca4875c..ecb5dbc 100644 --- a/sleuth/src/resolvers/rcon.rs +++ b/sleuth/src/resolvers/rcon.rs @@ -1,10 +1,11 @@ use std::{ - io::{BufRead, BufReader}, + io::{stdin, stdout, BufRead, BufReader, Write}, net::TcpListener, sync::{Arc, Mutex}, thread, }; +use log::{error, info, warn}; use once_cell::sync::Lazy; fn get_rcon_port() -> Option { @@ -21,9 +22,9 @@ pub fn handle_rcon() { }; let listener = TcpListener::bind(("127.0.0.1", port)) - .expect("[Rust RCON]: Failed to bind to port"); + .expect("[RCON] Failed to bind to port"); - println!("[Rust RCON]: Listening on 127.0.0.1:{}", port); + info!("[RCON] Listening on 127.0.0.1:{}", port); for stream in listener.incoming() { match stream { @@ -34,14 +35,28 @@ pub fn handle_rcon() { let reader = BufReader::new(stream); for line in reader.lines().flatten() { if !line.trim().is_empty() { - println!("[Rust RCON]: Received: {}", line.trim()); + warn!("[RCON] Received: {}", line.trim()); *cmd_store.lock().unwrap() = Some(line.trim().to_string()); *cmd_pending.lock().unwrap() = Some(true); } } }); } - Err(e) => eprintln!("[Rust RCON]: Connection failed: {}", e), + Err(e) => error!("[RCON] Connection failed: {}", e), } } } + + +// FIME: Nihi: this need some validation +// maybe a proper prompt etc +pub fn handle_cmd() { + let mut line = String::new(); + loop { + let mut input = String::new(); + stdin().read_line(&mut input) + .expect("UTF-8 unsupported"); + let cmd_store: Arc>> = Arc::clone(&LAST_COMMAND); + *cmd_store.lock().unwrap() = Some(input.trim().to_string()); + } +} \ No newline at end of file diff --git a/sleuth/src/scan.rs b/sleuth/src/scan.rs index 892ec9e..034a39a 100644 --- a/sleuth/src/scan.rs +++ b/sleuth/src/scan.rs @@ -19,7 +19,9 @@ use patternsleuth::resolvers::{ResolveError}; pub static OFFSETS: OnceCell> = OnceCell::new(); + pub fn scan() -> Result, String> { + let pid = Some(process::id() as i32); let resolvers = resolvers().collect::>(); diff --git a/sleuth/src/tools/log_macros.rs b/sleuth/src/tools/log_macros.rs new file mode 100644 index 0000000..7d2f423 --- /dev/null +++ b/sleuth/src/tools/log_macros.rs @@ -0,0 +1,164 @@ +pub mod slog_flags { + pub const FN: &str = "fn"; + pub const FILE: &str = "file"; + pub const LINE: &str = "line"; + pub const COLUMN: &str = "column"; + pub const MOD: &str = "mod"; +} + +#[macro_export] +macro_rules! function { + () => {{ + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + &name[..name.len() - 3] + }}; +} + +#[macro_export] +macro_rules! __slog_internal { + // With flags + ( $level:ident, $( $flag:ident ),+ ; $($arg:tt)* ) => {{ + // use std::io::Write; + + let mut context_parts = vec![]; + + $( + match stringify!($flag) { + "func" => context_parts.push(format!("{}", $crate::function!())), + "file" => context_parts.push(file!().to_string()), + "line" => context_parts.push(format!("line {}", line!())), + "column" => context_parts.push(format!("col {}", std::column!())), + "mod" => context_parts.push(format!("{}", std::module_path!())), + _ => {} + } + )+ + + if !context_parts.is_empty() { + log::$level!("[{}]", context_parts.join(" | ")); + } + + log::$level!($($arg)*); + }}; + + // No flags + ( $level:ident, $($arg:tt)* ) => {{ + // log::$level!( + // "{} (in {} [{}:{}:{}])", + // $crate::function!(), + // std::module_path!(), + // std::file!(), + // std::line!(), + // std::column!() + // ); + log::$level!( + "{}:{}:{}", + $crate::function!(), + std::line!(), + std::column!() + ); + log::$level!($($arg)*); + }}; +} + +// #[macro_export] +// macro_rules! sinfo { +// ( $(fn|file|line|column|mod)+ ; $($arg:tt)* ) => { +// $crate::__slog_internal!(info, $($arg)*); +// }; +// ( $( $flag:ident ),+ ; $($arg:tt)* ) => { +// $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); +// }; +// ($($arg:tt)*) => { +// $crate::__slog_internal!(info, $($arg)*); +// }; +// } + + + + +// #[macro_export] +// macro_rules! generate_slog_macro { +// ($name:ident, $level:ident) => { +// #[macro_export] +// macro_rules! $name { +// ( $sublevel:ident ; $( $flag:ident ),+ ; $($arg:tt)* ) => { +// println![stringify!(sublevel)]; +// $crate::__slog_internal!($level, $( $flag ),+ ; $($arg)*); +// }; +// ( $( $flag:ident ),+ ; $($arg:tt)* ) => { +// $crate::__slog_internal!($level, $( $flag ),+ ; $($arg)*); +// }; +// ($($arg:tt)*) => { +// $crate::__slog_internal!($level, $($arg)*); +// }; +// } +// }; +// } + +// FIXME: Nihi: couldn't figure out how to generalize it +// not possible to make nested macros with repeating patterns? +// Helper macro to define one macro named $fun that prints with format + args +// macro_rules! nested { +// (($($f:ident),*) $args:tt) => { +// println!["asdf"]; +// $(nested!(@call $f $args);)* +// println!["asdf over"]; +// }; +// (@call $f:ident ($($arg:expr),*)) => { +// println![stringify![$f]]; +// println![$($arg),*]; +// println!["asdfg over"]; +// }; +// () => { +// println!["no match"]; +// } +// } + +#[macro_export] +macro_rules! sinfo { + // ( $(fn|file|line|column|mod)+ ; $($arg:tt)* ) => { + // $crate::__slog_internal!(info, $($arg)*); + // }; + ( $sublevel:ident; $( $flag:ident ),+ + $(;)? $($arg:tt)* ) => { + println![stringify!(sublevel)]; + $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); + }; + ( $( $flag:ident ),+ ; $($arg:tt)* ) => { + $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); + }; + ($($arg:tt)*) => { + $crate::__slog_internal!(info, $($arg)*); + }; +} + +#[macro_export] +macro_rules! sdebug { + ( $( $flag:ident ),+ ; $($arg:tt)* ) => { + $crate::__slog_internal!(debug, $( $flag ),+ ; $($arg)*); + }; + ($($arg:tt)*) => { + $crate::__slog_internal!(debug, $($arg)*); + }; +} +#[macro_export] +macro_rules! strace { + ( $( $flag:ident ),+ ; $($arg:tt)* ) => { + $crate::__slog_internal!(trace, $( $flag ),+ ; $($arg)*); + }; + ($($arg:tt)*) => { + $crate::__slog_internal!(trace, $($arg)*); + }; +} +#[macro_export] +macro_rules! serror { + ( $( $flag:ident ),+ ; $($arg:tt)* ) => { + $crate::__slog_internal!(error, $( $flag ),+ ; $($arg)*); + }; + ($($arg:tt)*) => { + $crate::__slog_internal!(error, $($arg)*); + }; +} \ No newline at end of file diff --git a/sleuth/src/tools/mod.rs b/sleuth/src/tools/mod.rs new file mode 100644 index 0000000..d06ec76 --- /dev/null +++ b/sleuth/src/tools/mod.rs @@ -0,0 +1,5 @@ +pub mod syslog; +#[macro_use] +// pub mod log_macros_squashed; +// pub mod log_macros_kv; +pub mod log_macros; \ No newline at end of file diff --git a/sleuth/src/tools/syslog.rs b/sleuth/src/tools/syslog.rs new file mode 100644 index 0000000..1f78504 --- /dev/null +++ b/sleuth/src/tools/syslog.rs @@ -0,0 +1,308 @@ +// use anyhow::Result; +// use chrono::Local; +// use std::net::UdpSocket; +// use std::sync::mpsc::{self, Sender}; +// use std::thread; +// use std::time::Duration; + +// fn format_syslog_message(hostname: &str, tag: &str, message: &str) -> String { +// let stamp = Local::now().format("%b %d %H:%M:%S"); +// let pri = 13; +// format!("<{pri}>{stamp} {hostname} {tag}: {message}") +// } + +// pub fn spawn_syslog_sender(syslog_addr: &'static str) -> Sender { +// let (tx, rx) = mpsc::channel::(); +// thread::spawn(move || { +// let socket = UdpSocket::bind("0.0.0.0:0") +// .expect("Failed to bind udp socket"); + +// for line in rx { +// let msg = format_syslog_message("rust-client", "rustapp", &line); +// if let Err(e) = socket.send_to(msg.as_bytes(), syslog_addr) { +// eprintln!("Failed to send syslog msg: {e}"); +// } +// } +// }); +// tx +// } + +// pub fn start_log_generator(tx: Sender) { +// thread::spawn(move || { +// let messages = [ +// "Some test", +// "Blabliblub", +// ]; +// for msg in messages.iter().cycle() { +// if tx.send(msg.to_string()).is_err() { // recv fail +// break; +// } +// thread::sleep(Duration::from_secs(2)); +// } +// }); +// } + +use std::cell::RefCell; +use std::io::{self, Cursor, Write}; +use std::sync::{Arc, Mutex}; +use log4rs::append::file::FileAppender; +use log4rs::filter::threshold::ThresholdFilter; +use once_cell::sync::Lazy; + +const DEFAULT_BUF_SIZE: usize = 4096; +type PersistentBuf = Cursor>; + +thread_local! { + static PERSISTENT_BUF: RefCell = + RefCell::new(Cursor::new(Vec::with_capacity(DEFAULT_BUF_SIZE))); +} + +// Optional: for some kind of global shared output string +pub static SYSLOG_BUF: Lazy>>> = + Lazy::new(|| Arc::new(Mutex::new(None))); + +pub struct BufWriter; + +impl BufWriter { + pub fn new() -> Self { + PERSISTENT_BUF.with(|buf| { + buf.borrow_mut().set_position(0); // Reset position + }); + BufWriter + } + + /// Returns the current buffer as a UTF-8 string and resets buffer. + pub fn flush_to_string(&self) -> String { + PERSISTENT_BUF.with(|buf| { + let mut buf = buf.borrow_mut(); + let pos = buf.position() as usize; + let slice = &buf.get_ref()[..pos]; + match std::str::from_utf8(slice) { + Ok(s) => s.to_string(), + Err(_) => "".to_string(), + } + }) + } +} + +impl Write for BufWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + PERSISTENT_BUF.with(|pbuf| pbuf.borrow_mut().write(buf)) + } + + fn flush(&mut self) -> io::Result<()> { + // In case you want to trigger network send from here + let log_line = self.flush_to_string(); + // println!("Sending syslog: {}", log_line); + // send_to_syslog(log_line); // You can call your network send function here + Ok(()) + } +} +impl log4rs::encode::Write for BufWriter {} + +// https://en.wikipedia.org/wiki/Syslog + +use chrono::Local; +use std::{fmt}; +use std::net::UdpSocket; + +use log::{info, Level, Record}; +use log4rs::{ + append::{console::{ConsoleAppender, Target}, Append}, + config::{Appender, Config, Root}, + encode::{pattern::PatternEncoder, Encode}, + init_config, + // ConfigBuilder, +}; + +// #[derive(Debug)] +pub struct SyslogAppender { + // writer: Writer, + encoder: Box, + do_write: bool, + socket: Arc, + target_addr: String, + hostname: String, + tag: String, +} + +impl Append for SyslogAppender { + fn append(&self, record: &Record) -> anyhow::Result<()> { + // TODO: pre-process record (long paths etc) + let mut buf = BufWriter::new(); + self.encoder.encode(&mut buf, record)?; + // println!("SYSLOG {}", buf.flush_to_string()); + // let msg = format!("{}", record.args()); + // let full_msg = self.format_syslog_message(record.level(), &msg); + // println!("{:#?}", record); + let _ = self.socket.send_to(buf.flush_to_string().as_bytes(), &self.target_addr); + // let _ = self.socket.send_to(buf.as_bytes(), &self.target_addr); + Ok(()) + } + + fn flush(&self) {} +} + +impl SyslogAppender { + /// Create new builder for `SyslogAppender`. + pub fn builder() -> SyslogAppenderBuilder { + SyslogAppenderBuilder { + encoder: None, + target: Target::Stdout, + } + } +} +// TODO: syslog hostname (cli?) +// impl SyslogAppender { +// /// Creates a new `SyslogAppender` builder. +// pub fn builder() -> SyslogAppenderBuilder { +// SyslogAppenderBuilder { +// encoder: None, +// target: Target::Stdout, +// tty_only: false, +// } +// } +// } +/// A builder for `SyslogAppender`s. +pub struct SyslogAppenderBuilder { + encoder: Option>, + target: Target, +} + +impl SyslogAppenderBuilder { + /// Sets the output encoder for the `SyslogAppender`. + pub fn encoder(mut self, encoder: Box) -> SyslogAppenderBuilder { + self.encoder = Some(encoder); + self + } + + /// Consumes the `SyslogAppenderBuilder`, producing a `SyslogAppender`. + pub fn build(self) -> SyslogAppender { + // let writer = match self.target { + // Target::Stderr => match SyslogWriter::stderr() { + // Some(writer) => Writer::Tty(writer), + // None => Writer::Raw(StdWriter::stderr()), + // }, + // Target::Stdout => match SyslogWriter::stdout() { + // Some(writer) => Writer::Tty(writer), + // None => Writer::Raw(StdWriter::stdout()), + // }, + // }; + + // let do_write = writer.is_tty() || !self.tty_only; + + let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + SyslogAppender { + // writer, + encoder: self + .encoder + .unwrap_or_else(|| Box::::default()), + do_write: true, + socket: Arc::new(socket), + target_addr: "127.0.0.1:514".to_string(), + hostname: "unchained".to_string(), + tag: "some_tag".to_string(), + } + } +} + +impl SyslogAppender { + pub fn new(syslog_addr: &str, hostname: &str, tag: &str) -> std::io::Result { + let socket = UdpSocket::bind("0.0.0.0:0")?; + Ok(Self { + socket: Arc::new(socket), + target_addr: syslog_addr.to_string(), + hostname: hostname.to_string(), + tag: tag.to_string(), + encoder: todo!(), + do_write: todo!(), + }) + } + + fn format_syslog_message(&self, level: Level, msg: &str) -> String { + let pri = match level { + Level::Error => 3, + Level::Warn => 4, + Level::Info => 6, + Level::Debug | Level::Trace => 7, + } + 8; // facility 1 (user) + // TODO: Extend facility, let user provide it. Enum? + // Also syslog it supports more severity levels + + let timestamp = Local::now().format("%b %d %H:%M:%S"); + format!( + "<{}>{} {} {}: {}", + pri, timestamp, self.hostname, self.tag, msg + ) + } +} +impl fmt::Debug for SyslogAppender { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SyslogAppender") + .field("target_addr", &self.target_addr) + .field("hostname", &self.hostname) + .field("tag", &self.tag) + .finish() + } +} + +pub fn init_syslog() -> anyhow::Result<()> { + // let console = ConsoleAppender::builder() + // .encoder(Box::new(PatternEncoder::new( + // "[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] {m}{n}", + // ))) + // .build(); + info!(target:"syslog_init", "hello"); + info!(target:"syslog", "hello syslog"); + + // use function name + let console = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new( + // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", + // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", + + // TODO: make this configurable opt? Maybe only for syslog or only file log + // "[{d(%Y-%m-%d %H:%M:%S)} {h({l:5})}] [{f}:{L}] {m}{n}", // Log file name and line + "{h([{d(%H:%M:%S)} {l:5}])} {m}{n}", + ))) + .build(); + + let syslog = SyslogAppender::builder() + .encoder(Box::new(PatternEncoder::new( + // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", + // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", + + // TODO: make this configurable opt? Maybe only for syslog or only file log + "[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] {m}{n}", // Log file name and line + ))) + .build(); + let file = FileAppender::builder() + // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] [{M}] [{f}:{L}] {m}{n}\n"))) + .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] {P} [{l:6}] [{t}] {m}{n}"))) + // .build("my_log_file.log")?; + .build(r"U:\Unchained\UnchainedSleuth\unchained.log")?; + // Set up our custom syslog UDP appender + // let syslog = SyslogAppender::new( + // "127.0.0.1:514", // TODO: config option + // "rustlib", // Build from launch type? + // "unchained", + // )?; + let console_filter = ThresholdFilter::new(log::LevelFilter::Info); + + // Build the config programmatically + let config = Config::builder() + .appender(Appender::builder().filter(Box::new(console_filter)).build("console", Box::new(console))) + .appender(Appender::builder().build("syslog", Box::new(syslog))) + .appender(Appender::builder().build("file", Box::new(file))) + .build( + Root::builder() + .appender("console") + .appender("syslog") + .appender("file") + .build(log::LevelFilter::Debug), + )?; + + init_config(config)?; + + Ok(()) +} \ No newline at end of file From e3b80b0197f768314813ae85cd351503234c0bfc Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 17:12:51 +0200 Subject: [PATCH 06/18] =?UTF-8?q?local=20changes:=20-=20add=20features=20t?= =?UTF-8?q?o=20hide=20functionality=20-=20add=20server=20ports=20to=20cli?= =?UTF-8?q?=20args=20-=20stub=20for=20StaticFindObjectSafe=20-=20add=20gen?= =?UTF-8?q?erate=5Fstubs=20macro=20(can=20be=20inlined=20to=20get=20hook?= =?UTF-8?q?=20structure)=20-=20add=20kismet=20execution=20logging=20-=20(W?= =?UTF-8?q?IP)=20add=20server=20registration=20process=20-=20CREATE=5FHOOK?= =?UTF-8?q?=20will=20accept=20output=20type=20=F0=9F=98=85=20-=20add=20deb?= =?UTF-8?q?ug=5Fwhere=20macro=20and=20support=20short=20func=20names=20in?= =?UTF-8?q?=20s*=20log=20macros=20-=20add=20file=20logger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sleuth/CMakeLists.txt | 16 +- sleuth/Cargo.lock | 899 +++++++++++++++++++++++- sleuth/Cargo.toml | 49 +- sleuth/src/chiv2.rs | 26 + sleuth/src/lib.rs | 131 ++-- sleuth/src/resolvers/admin_control.rs | 155 +++- sleuth/src/resolvers/kismet_dev.rs | 44 ++ sleuth/src/resolvers/macros.rs | 108 ++- sleuth/src/resolvers/mod.rs | 2 + sleuth/src/resolvers/rcon.rs | 11 + sleuth/src/tools/log_macros.rs | 134 ++-- sleuth/src/tools/logger.rs | 81 +++ sleuth/src/tools/mod.rs | 7 +- sleuth/src/tools/server_registration.rs | 389 ++++++++++ sleuth/src/tools/syslog.rs | 128 ++-- 15 files changed, 1950 insertions(+), 230 deletions(-) create mode 100644 sleuth/src/resolvers/kismet_dev.rs create mode 100644 sleuth/src/tools/logger.rs create mode 100644 sleuth/src/tools/server_registration.rs diff --git a/sleuth/CMakeLists.txt b/sleuth/CMakeLists.txt index 30932d1..e33eccd 100644 --- a/sleuth/CMakeLists.txt +++ b/sleuth/CMakeLists.txt @@ -27,24 +27,26 @@ target_link_libraries(${PROJECT_NAME}_interface INTERFACE # Native system libs required by Rust advapi32.lib + bcrypt.lib + secur32.lib cfgmgr32.lib + crypt32.lib + cryptnet.lib + dbghelp.lib gdi32.lib kernel32.lib + legacy_stdio_definitions.lib msimg32.lib msvcrtd.lib + ncrypt.lib ntdll.lib ole32.lib opengl32.lib + psapi.lib shell32.lib user32.lib userenv.lib winspool.lib + # windows.0.52.0.lib ws2_32.lib - psapi.lib - bcrypt.lib - crypt32.lib - cryptnet.lib - dbghelp.lib - legacy_stdio_definitions.lib - ncrypt.lib ) diff --git a/sleuth/Cargo.lock b/sleuth/Cargo.lock index b5f2be7..63f64b9 100644 --- a/sleuth/Cargo.lock +++ b/sleuth/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "a2s" +version = "0.5.2" +dependencies = [ + "byteorder", + "bzip2", + "crc", + "thiserror", +] + [[package]] name = "ab_glyph" version = "0.2.29" @@ -53,7 +63,7 @@ checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" dependencies = [ "accesskit", "accesskit_consumer", - "async-channel", + "async-channel 2.3.1", "async-once-cell", "atspi", "futures-lite 1.13.0", @@ -170,6 +180,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -220,6 +279,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -258,6 +328,21 @@ dependencies = [ "futures-lite 1.13.0", ] +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io 2.3.3", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.6.0", + "once_cell", +] + [[package]] name = "async-io" version = "1.13.0" @@ -274,7 +359,7 @@ dependencies = [ "polling 2.8.0", "rustix 0.37.28", "slab", - "socket2", + "socket2 0.4.10", "waker-fn", ] @@ -369,6 +454,32 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.3.3", + "async-lock 3.4.0", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-task" version = "4.7.1" @@ -461,6 +572,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -513,13 +630,19 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-task", "futures-io", "futures-lite 2.6.0", "piper", ] +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + [[package]] name = "bumpalo" version = "3.18.1" @@ -558,6 +681,26 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "calloop" version = "0.10.6" @@ -618,10 +761,52 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-link", ] +[[package]] +name = "clap" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -667,6 +852,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.7" @@ -735,6 +926,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -823,6 +1023,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + [[package]] name = "digest" version = "0.10.7" @@ -997,6 +1203,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enumflags2" version = "0.7.11" @@ -1357,6 +1572,18 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "glow" version = "0.12.3" @@ -1433,6 +1660,25 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1451,6 +1697,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1478,12 +1730,130 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "humantime" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.5.10", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1679,6 +2049,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.12.1" @@ -1742,6 +2134,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1836,6 +2237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "serde", + "value-bag", ] [[package]] @@ -1862,13 +2264,14 @@ dependencies = [ "log-mdc", "once_cell", "parking_lot", - "rand", + "rand 0.8.5", "serde", "serde-value", "serde_json", "serde_yaml", "thiserror", "thread-id", + "typemap-ors", "winapi", ] @@ -1932,6 +2335,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1963,6 +2372,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "mmap-fixed-fixed" version = "0.1.3" @@ -1973,6 +2393,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.7.0" @@ -2230,6 +2667,50 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.48" @@ -2494,8 +2975,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2505,7 +2996,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2517,6 +3018,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -2602,6 +3112,50 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "reqwest" +version = "0.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "retour" version = "0.3.1" @@ -2633,6 +3187,20 @@ dependencies = [ "slice-pool2", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2679,6 +3247,39 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -2711,6 +3312,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2736,6 +3346,29 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.219" @@ -2789,6 +3422,18 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -2813,6 +3458,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" @@ -2859,19 +3510,28 @@ dependencies = [ name = "sleuthlib" version = "0.1.0" dependencies = [ + "a2s", "anyhow", + "async-std", "bitflags 2.9.1", + "chrono", + "clap", "dll_hook", "futures", "indexmap", + "log", + "log4rs", "once_cell", "parking_lot", "paste", "patternsleuth", + "rand 0.9.1", "regex", + "reqwest", "retour 0.3.1", "serde", "serde_json", + "shell-words", "simple-log", "tracing", "widestring", @@ -2939,6 +3599,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2957,6 +3627,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.25.0" @@ -2972,13 +3648,19 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", "syn 2.0.101", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -3001,6 +3683,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -3012,6 +3703,27 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.20.0" @@ -3100,6 +3812,54 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio 1.0.3", + "pin-project-lite", + "socket2 0.5.10", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -3117,6 +3877,51 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -3148,6 +3953,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -3170,6 +3981,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + [[package]] name = "typenum" version = "1.18.0" @@ -3217,12 +4037,27 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -3240,6 +4075,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -3268,6 +4121,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3606,6 +4468,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-registry" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -3837,7 +4710,7 @@ dependencies = [ "instant", "libc", "log", - "mio", + "mio 0.8.11", "ndk", "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", @@ -3982,7 +4855,7 @@ dependencies = [ "nix 0.26.4", "once_cell", "ordered-stream", - "rand", + "rand 0.8.5", "serde", "serde_repr", "sha1", @@ -4062,6 +4935,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerotrie" version = "0.2.2" diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 21a1829..6c617c5 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -58,6 +58,12 @@ windows = { version = "0.52.0", features = ["Win32_Foundation", rand = "0.9.1" async-std = "1.13.1" +# Server registration +# a2s = { version = "0.5.2", optional = true } +a2s = { path = "../../a2s-rs", optional = true } +reqwest = { version = "0.12.19", optional = true, features = ["blocking", "json"] } + + # [target."cfg(windows)".dev-dependencies.windows] # version = "0.48" # features = ["Win32_Foundation", @@ -70,9 +76,46 @@ async-std = "1.13.1" # [target.'cfg(windows)'.dependencies] # winapi = { version = "0.3.9", features = ["winnt", "wincrypt", "winuser"] } - - [features] -default = ["serde-resolvers"] +default = [ + "serde-resolvers", + "chat-commands", + "cli-commands", + "kismet-log", + "chat-log", + "chiv2", + "mirage", + "demo", + "syslog-client", + "rcon", + "dev", + "server-registration", + ] serde-resolvers = [] image-elf = [] +dev = [] + +# UE generic +cli-commands = [] +rcon = [] +syslog-client = [] +kismet-log = [] +demo = [] + +# Client specific +server-registration = ["dep:a2s", "dep:reqwest"] +chiv2 = [] +chat-commands = [] +chat-log = [] +mirage = [] + +# WIP +object-lookup = [] + +server = [ +# "chat-commands", + "cli-commands", +# "kismet-log", + "chat-log", + "chiv2", +] diff --git a/sleuth/src/chiv2.rs b/sleuth/src/chiv2.rs index 64513c5..2ee3ad6 100644 --- a/sleuth/src/chiv2.rs +++ b/sleuth/src/chiv2.rs @@ -47,3 +47,29 @@ impl FromStr for EChatType { } } } + +impl TryFrom for EChatType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(EChatType::AllSay), + 1 => Ok(EChatType::TeamSay), + 2 => Ok(EChatType::Whisper), + 3 => Ok(EChatType::Admin), + 4 => Ok(EChatType::Objective), + 5 => Ok(EChatType::System), + 6 => Ok(EChatType::ServerSay), + 7 => Ok(EChatType::Debug), + 8 => Ok(EChatType::CrosshairMsg), + 9 => Ok(EChatType::Backend), + 10 => Ok(EChatType::Party), + 11 => Ok(EChatType::Spectator), + 12 => Ok(EChatType::ClosedCaption), + 13 => Ok(EChatType::ClosedCaptionMason), + 14 => Ok(EChatType::ClosedCaptionAgatha), + 15 => Ok(EChatType::MAX), + _ => Err(()), + } + } +} \ No newline at end of file diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index f4ccd13..09c2f30 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -8,8 +8,7 @@ use std::{env, thread}; use std::fs::File; use std::io::{BufReader, Read}; use std::io::{BufWriter, Write}; -use std::collections::HashMap; -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; mod ue; mod tools; mod chiv2; @@ -28,9 +27,13 @@ use patternsleuth::resolvers::unreal::{fname::FNameToString, guobject_array::{FUObjectArrayAllocateUObjectIndex, FUObjectArrayFreeUObjectIndex, GUObjectArray} }; use resolvers::macros; +use resolvers::admin_control::*; +#[cfg(feature="kismet-log")] +use resolvers::kismet_dev::*; use serde::Serialize; use serde_json::to_writer_pretty; -use tools::syslog::init_syslog; +use tools::logger::init_syslog; +use tools::server_registration::Registration; use winapi::um::winnt::FILE_APPEND_DATA; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; @@ -136,8 +139,14 @@ struct CLIArgs { #[arg(long = "platform")] platform: Option, - #[arg(long = "saveddirsuffix")] - saveddirsuffix: Option, + #[arg(long = "GameServerPingPort", default_value="3075")] + game_server_ping_port: Option, + + #[arg(long = "GameServerQueryPort", default_value="7071")] + game_server_query_port: Option, + + #[arg(long = "Port", default_value="7777")] + game_port: Option, // UNHANDLED START @@ -255,34 +264,7 @@ pub fn dump_builds() -> Result<()> { } -// TODO: replace this with registration similar to resolvers -type HookFn = unsafe fn(usize, HashMap) -> Result<(), Box>; -// macro_rules! attach_hooks_list { -// ( [ $( $pattern:ident ),+ $(,)? ]) => { -// paste::paste!{ -// { -// let hooks: HashMap= [ $( ( stringify![$pattern], [] ) ),+ ] -// .into_iter().map(|(name, func)| (name.to_string(), func)).collect(); -// Ok(hooks) -// } -// } -// }; -// } - -macro_rules! attach_hooks_list { - ( [ $( $pattern:ident ),+ $(,)? ]) => {{ - use std::collections::HashMap; - paste::paste! { - let hooks: HashMap<&'static str, HookFn> = [ - $((stringify!($pattern), [] as HookFn)),+ - ] - .into_iter() - .collect(); - - hooks - } - }}; -} + pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) -> Result<(), Box> { @@ -295,20 +277,39 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - ExecuteConsoleCommand, FEngineLoopInit, ClientMessage, + #[cfg(feature="demo")] SomeRandomFunction, + // StaticFindObjectSafe, + #[cfg(feature="kismet-log")] + KismetExecutionMessage, + #[cfg(feature="dev")] + LogReliableRPC, + #[cfg(feature="dev")] + LogReliableRPCFailed, ]]; - use resolvers::admin_control::*; // use crate::resolvers::macros; hooks_new.iter().for_each(|(s, f)| { match (f)(base_address, offsets.clone()) { - Ok(_) => info!["☑ {} ", s], + Ok(addr) => { + sinfo![f; "☑ {} ", s] + }, Err(e) => { - serror!(func, file;"☐ {}: {}", s.to_uppercase(), e); - serror!(func, file, line;"☐ {}: {}", s.to_uppercase(), e); - serror!(func, file, line, column;"☐ {}: {}", s.to_uppercase(), e); - serror!(func, file, line, mod;"☐ {}: {}", s.to_uppercase(), e); - serror!("☐ {}: {}", s.to_uppercase(), e); + // sdebug!(file, f;"☐ {}: {}", s.to_uppercase(), e); + // strace!(file, f, line;"☐ {}: {}", s.to_uppercase(), e); + // swarn!(file, f, line;"☐ {}: {}", s.to_uppercase(), e); + // sinfo!(file, func, line, mod;"☐ {}: {}", s.to_uppercase(), e); + // serror!(file, func, line, column;"☐ {}: {}", s.to_uppercase(), e); + // debug_where!(); +// console -> [23:54:45 ERROR] [attach_hooks] ☐ SOMERANDOMFUNCTION: No address found. +// file -> [2025-06-09 23:54:45 13116 ERROR | function ] [attach_hooks] ☐ SOMERANDOMFUNCTION: No address found. + +// console -> [23:54:45 ERROR] [src\lib.rs|sleuthlib::attach_hooks::{{closure}}|L317|C17] ☐ SOMERANDOMFUNCTION: No address found. +// file -> [2025-06-09 23:54:45 13116 ERROR | function ] [src\lib.rs|sleuthlib::attach_hooks::{{closure}}|L317|C17] ☐ SOMERANDOMFUNCTION: No address found. + + // error!("☐ {}: {}", s.to_uppercase(), e); + serror!(f; "☐ {}: {}", s.to_uppercase(), e); + // serror!(file, func, line, column;"☐ {}: {}", s.to_uppercase(), e); }, } }); @@ -482,23 +483,24 @@ pub extern "C" fn generate_json() -> u8 { init_syslog().expect("Failed to init syslog"); unsafe { init_globals() }; + #[cfg(feature="rcon")] std::thread::spawn(|| { resolvers::rcon::handle_rcon(); }); - (|| { - mod module { - pub trait Trait { - fn function(&self) { - println!("{} (in {} [{}:{}:{}])", - function!(), module_path!(), file!(), line!(), column!() - ); - } - } - impl Trait for () {} - } - module::Trait::function(&()); - })(); + // (|| { + // mod module { + // pub trait Trait { + // fn function(&self) { + // println!("{} (in {} [{}:{}:{}])", + // function!(), module_path!(), file!(), line!(), column!() + // ); + // } + // } + // impl Trait for () {} + // } + // module::Trait::function(&()); + // })(); // Init syslog // info!("Info blabla"); @@ -517,9 +519,32 @@ pub extern "C" fn generate_json() -> u8 { } dump_builds().expect("Failed to dump builds JSON"); + #[cfg(feature="cli-commands")] std::thread::spawn(|| { resolvers::rcon::handle_cmd(); }); + + + #[cfg(feature="server-registration")] + { + let cli = globals().args(); + + let backend = cli.server_browser_backend.clone().unwrap(); + let reg = Registration::new( + "127.0.0.1", + 7071 + ); + + // let last_info = std::sync::Arc::clone(&tools::server_registration::REGISTRATION); + // *last_info.lock().unwrap() = Some(reg); + reg.start(&backend, "Chivalry 2 Local Server", ""); + let registration = std::sync::Arc::new(reg); + registration.start_heartbeat(&backend, "Some Server", ""); + info!("Backend: {backend}"); + // std::thread::spawn(|| { + // }); + + } len_u8 } diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index b7aa7cc..fb6de70 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -8,7 +8,7 @@ use std::error::Error; use std::os::raw::c_void; use std::mem; use crate::resolvers::rcon::LAST_COMMAND; -use crate::chiv2::*; +use crate::{chiv2::*, sdebug}; use crate::{globals, ue::*}; define_pattern_resolver![UTBLLocalPlayer_Exec, { @@ -28,7 +28,9 @@ define_pattern_resolver!(ExecuteConsoleCommand, [ CREATE_HOOK!(ExecuteConsoleCommand, (string:*mut FString), { println!("ExecuteConsoleCommand: {}", unsafe { &*string }); }); -CREATE_HOOK!(SomeRandomFunction, (string:*mut FString), { + +#[cfg(feature="demo")] +CREATE_HOOK!(SomeRandomFunction, c_void, (string:*mut FString), { println!("SomeRandomFunction: {}", unsafe { &*string }); }); @@ -85,12 +87,32 @@ define_pattern_resolver![GetTBLGameMode, { }]; - - +// https://github.com/trumank/unrealsdk/blob/d121ba258e6751d5fa522aa9b803aaa0ea59fec7/src/unrealsdk/game/bl3/object.cpp#L122 +// "48 89 5C 24 ??" // mov [rsp+08], rbx +// "48 89 6C 24 ??" // mov [rsp+10], rbp +// "48 89 74 24 ??" // mov [rsp+18], rsi +// "57" // push rdi +// "48 83 EC 30" // sub rsp, 30 +// "80 3D ???????? 00" // cmp byte ptr [Borderlands3.exe+69EAA10], 00 // Handle Game chat: commands, chat log, system messages -define_pattern_resolver!(ClientMessage, [ - "4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 73 18 49 8B D8 49 89 43 C8 48 8B F1 49 89 43 D0 49 89 43 D8 49 8D 43" +#[cfg(feature="object-lookup")] +define_pattern_resolver!(StaticFindObjectSafe, [ + "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 80 3D ?? ?? ?? ?? 00 41 0F B6 D9 49 8B F8 48 8B", + // "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 80 3D ?? ?? ?? ?? 00" ]); +// UObject * __cdecl StaticFindObjectSafe(UClass.conflict *param_1,UObject *param_2,wchar_t *param_3,bool param_4) +// UObject* StaticFindObjectSafe( UClass* ObjectClass, UObject* ObjectParent, const TCHAR* InName, bool bExactClass ) +#[cfg(feature="object-lookup")] +CREATE_HOOK!(StaticFindObjectSafe, *mut UObject, (ObjectClass:*mut UClass, ObjectParent:*mut UObject, InName:*mut FString, bExactClass: bool), { + if !InName.is_null() { + unsafe { + let reference = &*InName; + // Now you can use `reference` safely + // crate::sdebug!(f; "{:?}", reference); + } + } +}); + mod client_message { use log::warn; @@ -119,24 +141,51 @@ mod client_message { } pub fn parse_msg_line(line: &str) -> Option<(EChatType, &str)> { - let mut parts = line.splitn(2, ": "); - warn!["parts: {:?}", parts]; - let msg_type_str = parts.next()?; - let msg = parts.next()?; - let msg_type = EChatType::from_str(msg_type_str).ok()?; - Some((msg_type, msg)) + // "<8>: FACTION NOT VALID" + // warn!("LINE: '{line}'"); + let re = Regex::new(r#"<(\d+)>: (.+)"#).unwrap(); + let test = r#"""<4>: Carryable_Peasant_Raid_Child2_C_2147471851 : X=-3256.829 Y=-1015.456 Z=-145.546"""#; + // warn!("TEST: '{test}'"); + + if let Some(caps) = re.captures(line) { + let msg_type_str: u8 = caps.get(1)?.as_str().parse().ok()?; + let msg_type = EChatType::try_from(msg_type_str).expect("Failed to parse EChatType"); + let message = caps.get(2)?.as_str(); + Some((msg_type, message)) + } else { + None + } + // let mut parts = line.splitn(2, ": "); + // warn!["parts: {:?}", parts]; + // let msg_type_str = parts.next()?; + // let msg = parts.next()?; + // let msg_type = EChatType::from_str(msg_type_str).ok()?; + // Some((msg_type, msg)) } } + + +// Handle Game chat: commands, chat log, system messages +define_pattern_resolver!(ClientMessage, [ + "4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 73 18 49 8B D8 49 89 43 C8 48 8B F1 49 89 43 D0 49 89 43 D8 49 8D 43" +]); + // void __thiscall APlayerController::ClientMessage(APlayerController *this,FString *S,FName Type,float MsgLifeTime) CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLifeTime: f32), { + #[cfg(feature="chat-commands")] let cmd_store = Arc::clone(&LAST_COMMAND); // FIXME: Nihi: better way to access it? let string_ref: &FString = unsafe{ &*S }; let message = string_ref.to_string(); // TODO: Does this need to handle lines separately? // info!("[ClientMessages] S: \'{:?}\', Type: \'{}\'({}), MsgLifeTime: \'{}\' ", message.replace("\r\n", " "), Type, Type.number, MsgLifeTime); - info!("[client] \'{:?}\'", message.replace("\r\n", " ")); + let message_repl = match message.contains('\n') { + true => format!("\n{message}"),//.replace("\r\n", " "), + false => message, + }; + + // info!(target: "client", " \'{:?}\'", message_repl); // Chat commands // TODO: handle commands tranformed by ChatHooks @@ -144,37 +193,83 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi // this needs to provide the playfabid somehow unsafe { let cmd_filter = |c| ['/', '.'].contains(&c); - match client_message::parse_chat_line(message.as_str()) { + match client_message::parse_chat_line(message_repl.as_str()) { Some(chat) => { match chat.message.starts_with(cmd_filter) { true => { - let cmd_trimmed = chat.message.trim_start_matches(cmd_filter); - debug!("-> Got console command from \'{}\' ch{}: {}", - chat.name, - chat.channel, - cmd_trimmed); - // Set pending console command - // FIXME: Nihi: Add auth or allow only offline - *cmd_store.lock().unwrap() = Some(cmd_trimmed.trim().to_string()); - debug!("Pending: {:?}", Some(cmd_trimmed.trim().to_string())); - // TODO: save command to log + #[cfg(feature="chat-commands")] + { + let cmd_trimmed = chat.message.trim_start_matches(cmd_filter); + debug!("-> Got console command from \'{}\' ch{}: {}", + chat.name, + chat.channel, + cmd_trimmed); + // Set pending console command + // FIXME: Nihi: Add auth or allow only offline + *cmd_store.lock().unwrap() = Some(cmd_trimmed.trim().to_string()); + debug!("Pending: {:?}", Some(cmd_trimmed.trim().to_string())); + // TODO: save command to log + } } false => { - debug!("-> User message: {:?}", chat); + // debug!("-> User message: {:?}", chat); + let msg_type = EChatType::try_from(chat.channel as u8).expect("Failed to parse EChatType"); + info!(target: "game_chat", "\x1b[38;5;214m[ {:10?} ] \x1b[38;5;251m[ {} ]\x1b[38;5;255m: \x1b[38;5;251m{}\x1b[38;5;255m", msg_type, chat.name, chat.message); // TODO: save user message } }; } _ => { - debug!("System message"); - if let Some(msg) = parse_chat_line(message.as_str()) { - warn!("Chat: channel {}, name {}, message {}, ", msg.channel, msg.name, msg.message); - if let Some(chat_mnsg) = client_message::parse_msg_line(msg.message) { - warn!("Text: {:?} {}", chat_mnsg.0, chat_mnsg.1); + // debug!("System message"); + // "<8>: FACTION NOT VALID" + if let Some((chat_type, message)) = client_message::parse_msg_line(message_repl.as_str()) { + if let Some(msg) = parse_chat_line(message_repl.as_str()) { + info!("Chat: channel {}, name {}, message {}, ", msg.channel, msg.name, msg.message); } + else { + info!(target: "system_chat", "\x1b[38;5;214m[ {:10?} ] \x1b[38;5;251m{}\x1b[38;5;255m", chat_type, message); + } + } + else { + println!("something went wrong"); } + // if let Some(msg) = parse_chat_line(message_repl.as_str()) { + // warn!("Chat: channel {}, name {}, message {}, ", msg.channel, msg.name, msg.message); + // if let Some(chat_mnsg) = client_message::parse_msg_line(msg.message) { + // warn!("Text: {:?} {}", chat_mnsg.0, chat_mnsg.1); + // } + // } // TODO: Write message to logfile } } } +}); + + +#[cfg(feature = "dev")] +define_pattern_resolver!(LogReliableRPCFailed,[ + "48 89 5C 24 08 57 48 83 EC 40 41 83 78 08 00 49 8B F8 48 8B D9 ?? ?? 49 8B 00 ?? ?? 48" + ]); + +// void __thiscall +// UNetConnection::LogReliableRPCFailed +// (UNetConnection* this, FInBunch* param_1, FString* param_2, int param_3) +#[cfg(feature = "dev")] +CREATE_HOOK!(LogReliableRPCFailed, c_void, (this_ptr: *mut c_void, arg1: *mut FString, arg2: u32), { + println!("LogReliableRPCFailed"); + // crate::sinfo![f; "Triggered!"]; +}); + + + +#[cfg(feature = "dev")] +define_pattern_resolver!(LogReliableRPC,[ + "48 89 5C 24 10 48 89 6C 24 18 48 89 74 24 20 41 56 48 83 EC 20 48 8B 01 41 8B E8 48 8B DA 4C 8B F1 ?? ?? ?? ?? ?? ?? 48 8B C8 ?? ?? ?? ?? ?? 48 8B F0 48 85 C0 ?? ?? ?? ?? ?? ?? ?? 48 8B 4E 10 48" + ]); + +//void __thiscall ATBLCharacter::LogReliableRPC(ATBLCharacter *this,FName param_1,int param_2) +#[cfg(feature = "dev")] +CREATE_HOOK!(LogReliableRPC, c_void, (this_ptr: *mut c_void, arg1: *mut FString, arg2: u32), { + println!("LogReliableRPC"); + // crate::sinfo![f; "Triggered!"]; }); \ No newline at end of file diff --git a/sleuth/src/resolvers/kismet_dev.rs b/sleuth/src/resolvers/kismet_dev.rs new file mode 100644 index 0000000..9cc7475 --- /dev/null +++ b/sleuth/src/resolvers/kismet_dev.rs @@ -0,0 +1,44 @@ + + use crate::sdebug; +use crate::swarn; +/*KismetExecutionMessage*/ + +use crate::ue::*; +static LIST_OF_SHAME: [&str; 4] = [ + "/Game/Maps/Frontend/CIT/FE_Citadel_Atmospherics.FE_Citadel_Atmospherics_C", + "Divide by zero: ProjectVectorOnToVector with zero Target vector", + "A null object was passed as a world context object to UEngine::GetWorldFromContextObject().", + "/Game/Maps/Frontend/Blueprints/Customization_Rotation.Customization_Rotation_C", +]; + +define_pattern_resolver!(KismetExecutionMessage, [ + "48 89 5C 24 08 57 48 83 EC 30 0F B6 DA 48 8B F9 80 FA 01 ?? ?? ?? ?? ?? ?? ?? ?? ?? BA", +]); + +// void __cdecl FFrame::KismetExecutionMessage(wchar_t *param_1,Type param_2,FName param_3) + +#[cfg(feature="kismet-log")] +CREATE_HOOK!(KismetExecutionMessage, *mut UObject, (Message:*const u16, Type: u8, fname: FName), { + + if !Message.is_null() { + unsafe { + let msg = widestring::U16CStr::from_ptr_str(Message as *const u16); + match FString::try_from(msg.as_slice_with_nul()) { + Ok(str) => { + let mut message = msg.to_string_lossy(); + message = match message.contains('\n') { + true => format!("\n{message}"),//.replace("\r\n", " "), + false => message, + }; + + match LIST_OF_SHAME.iter().any(|x| message.contains(x)) { + true => {} // filtered out + false => log::debug!(target: "kismet", "{message}"), + } + }, + Err(e) => swarn!(f; "{e}") + } + } + } + +}); diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index a45ed75..6e32373 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -49,8 +49,70 @@ /// } /// ``` /// +/// +// TODO: replace this with registration similar to resolvers +pub type HookFn = unsafe fn(usize, HashMap) -> Result, Box>; +// macro_rules! attach_hooks_list { +// ( [ $( $pattern:ident ),+ $(,)? ]) => { +// paste::paste!{ +// { +// let hooks: HashMap= [ $( ( stringify![$pattern], [] ) ),+ ] +// .into_iter().map(|(name, func)| (name.to_string(), func)).collect(); +// Ok(hooks) +// } +// } +// }; +// } + +// #[macro_export] +// macro_rules! attach_hooks_list { +// ( [ $( $pattern:ident ),+ $(,)? ]) => {{ +// use std::collections::HashMap; +// use crate::resolvers::macros::HookFn; +// paste::paste! { +// let hooks: HashMap<&'static str, HookFn> = [ +// $((stringify!($pattern), [] as HookFn)),+ +// ] +// .into_iter() +// .collect(); + +// hooks +// } +// }}; +// } +#[macro_export] +macro_rules! attach_hooks_list { + ( [ $( + $(#[$attr:meta])* + $pattern:ident + ),+ $(,)? ]) => {{ + use std::collections::HashMap; + use crate::resolvers::macros::HookFn; + paste::paste! { + #[allow(dead_code)] + enum ActiveHooks { + $( + $(#[$attr])* + $pattern + ),+ + } + let hooks: HashMap<&'static str, HookFn> = [ + $( + $(#[$attr])* + (stringify!($pattern), [] as HookFn) + ),+ + ] + .into_iter() + .collect(); + hooks + } + }}; +} + + +// generate_stub!(StubName); #[macro_export] macro_rules! CREATE_HOOK { @@ -62,22 +124,28 @@ macro_rules! CREATE_HOOK { // [ $( $call_type ($pattern) ),+ ]; // }; + ($name:ident, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:block) => { + CREATE_HOOK!($name, ::std::ffi::c_void, ( $( $arg: $ty ),+ ), $body); + }; + + ($name:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:block) => { + paste::paste! { - static_detour! { - pub static []: unsafe extern "C" fn ($( $ty ),+ ); + ::retour::static_detour! { + pub static []: unsafe extern "C" fn ($( $ty ),+ ) -> $out_type; } #[allow(non_snake_case)] - pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) { + pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) -> $out_type { // println!("rust $name delta: {}", delta); $body unsafe { [].call ( $( $arg ),+ ) } } #[allow(non_snake_case)] - pub unsafe fn [](base_address: usize, offsets: HashMap) -> Result<(), Box>{ + pub unsafe fn [](base_address: usize, offsets: std::collections::HashMap) -> Result, Box>{ // pub unsafe fn [](base_address: usize, offsets: HashMap) -> Result<(), Box { // TODO: propagate error? why panic @@ -88,22 +156,22 @@ macro_rules! CREATE_HOOK { Some(_) => { // log::info!["attached"]; // ( $( $arg: $ty ),+ ); - let address = base_address + offsets[stringify![$name]] as usize; - let target: [] = mem::transmute(address); + let rel_address = offsets[stringify![$name]] as usize; + let target: [] = std::mem::transmute(base_address + rel_address); - type [] = unsafe extern "C" fn ($( $ty ),+ ); + type [] = unsafe extern "C" fn ($( $ty ),+ ) -> $out_type; [] .initialize(target, [<$name _detour_fkt>])? .enable()?;; - Ok(()) + // crate::debug_where!("Attached [ 0x{:#x?} ]", rel_address); + crate::sdebug!(f; "Attached [ 0x{:#x?} ]", rel_address); + Ok(Some(rel_address as usize)) }, } } } - - }; } @@ -152,7 +220,7 @@ macro_rules! CREATE_HOOK { // } -use std::{future::Future, pin::Pin}; +use std::{collections::HashMap, future::Future, pin::Pin}; use patternsleuth::resolvers::{AsyncContext, ResolveError}; @@ -245,6 +313,24 @@ macro_rules! define_pocess { }}; } +#[allow(unused_macros)] +macro_rules! generate_stub { + ( $name:ident ) => { + // $name + #[cfg(feature="dev")] + define_pattern_resolver!($name, [ + "4C" + ]); + + #[cfg(feature="dev")] + CREATE_HOOK!($name, (arg0: *mut c_void), { + crate::sinfo![f; "Triggered!"]; + }); + }; +} + +// inline this +// generate_stub!(StubName); #[macro_export] macro_rules! define_pattern_resolver { diff --git a/sleuth/src/resolvers/mod.rs b/sleuth/src/resolvers/mod.rs index ebe58b1..505783f 100644 --- a/sleuth/src/resolvers/mod.rs +++ b/sleuth/src/resolvers/mod.rs @@ -122,4 +122,6 @@ pub mod etc_hooks; pub mod ownership_overrides; pub mod unchained_integration; pub mod rcon; +#[cfg(feature="kismet-log")] +pub mod kismet_dev; diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs index ecb5dbc..08d43c4 100644 --- a/sleuth/src/resolvers/rcon.rs +++ b/sleuth/src/resolvers/rcon.rs @@ -8,6 +8,9 @@ use std::{ use log::{error, info, warn}; use once_cell::sync::Lazy; +use crate::{sdebug, sinfo}; + +#[cfg(feature="rcon")] fn get_rcon_port() -> Option { Some(9001) } @@ -15,6 +18,7 @@ pub static COMMAND_PENDING: Lazy>>> = Lazy::new(|| Arc: pub static LAST_COMMAND: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); // pub static FLAST_COMMAND: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); +#[cfg(feature="rcon")] pub fn handle_rcon() { let port = match get_rcon_port() { Some(p) => p, @@ -50,6 +54,7 @@ pub fn handle_rcon() { // FIME: Nihi: this need some validation // maybe a proper prompt etc +#[cfg(feature="cli-commands")] pub fn handle_cmd() { let mut line = String::new(); loop { @@ -58,5 +63,11 @@ pub fn handle_cmd() { .expect("UTF-8 unsupported"); let cmd_store: Arc>> = Arc::clone(&LAST_COMMAND); *cmd_store.lock().unwrap() = Some(input.trim().to_string()); + match input.as_str() { + _ => {}, + "findobj" => { + crate::sdebug!(f; "findobj {:?}", 123); + } + } } } \ No newline at end of file diff --git a/sleuth/src/tools/log_macros.rs b/sleuth/src/tools/log_macros.rs index 7d2f423..eaab906 100644 --- a/sleuth/src/tools/log_macros.rs +++ b/sleuth/src/tools/log_macros.rs @@ -1,10 +1,3 @@ -pub mod slog_flags { - pub const FN: &str = "fn"; - pub const FILE: &str = "file"; - pub const LINE: &str = "line"; - pub const COLUMN: &str = "column"; - pub const MOD: &str = "mod"; -} #[macro_export] macro_rules! function { @@ -18,6 +11,8 @@ macro_rules! function { }}; } + + #[macro_export] macro_rules! __slog_internal { // With flags @@ -25,23 +20,53 @@ macro_rules! __slog_internal { // use std::io::Write; let mut context_parts = vec![]; + let mut target: Option = None; $( match stringify!($flag) { - "func" => context_parts.push(format!("{}", $crate::function!())), + "f" => { + fn extract_segment(s: &str) -> Option { + let mut end = s; + while let Some(stripped) = end.strip_suffix("::{{closure}}") { + end = stripped; + } + end.rsplit("::").next().map(|s| s.to_string()) + } + let short_name = extract_segment($crate::function!()).unwrap(); + context_parts.push(format!("{}", short_name)); + // target = Some(short_name); + target = Some("function".to_string()); + } + "func" => { + target = Some("function".to_string()); + context_parts.push(format!("{}", $crate::function!())) + }, "file" => context_parts.push(file!().to_string()), - "line" => context_parts.push(format!("line {}", line!())), - "column" => context_parts.push(format!("col {}", std::column!())), - "mod" => context_parts.push(format!("{}", std::module_path!())), + "line" => context_parts.push(format!("L{}", line!())), + "column" => context_parts.push(format!("C{}", std::column!())), + "mod" => context_parts.push(format!("M{}", std::module_path!())), _ => {} } )+ - - if !context_parts.is_empty() { - log::$level!("[{}]", context_parts.join(" | ")); + if let Some(tgt) = target { + log::$level!(target: &tgt, "[{}] {}", context_parts.join("|"), format_args!($($arg)*)); + } + else { + log::$level!("[{}]", context_parts.join("|")); } + // if let Some(tgt) = target { + // if !context_parts.is_empty() { + // log::$level!(target: &tgt, "[{}]", context_parts.join(":")); + // } + // log::$level!(target: &tgt, $($arg)*); + // } + // else { + // if !context_parts.is_empty() { + // log::$level!("[{}]", context_parts.join(":")); + // } + // log::$level!($($arg)*); + // } - log::$level!($($arg)*); }}; // No flags @@ -64,22 +89,8 @@ macro_rules! __slog_internal { }}; } -// #[macro_export] -// macro_rules! sinfo { -// ( $(fn|file|line|column|mod)+ ; $($arg:tt)* ) => { -// $crate::__slog_internal!(info, $($arg)*); -// }; -// ( $( $flag:ident ),+ ; $($arg:tt)* ) => { -// $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); -// }; -// ($($arg:tt)*) => { -// $crate::__slog_internal!(info, $($arg)*); -// }; -// } - - - - +// FIXME: Nihi: couldn't figure out how to generalize it +// not possible to make nested macros with repeating patterns? // #[macro_export] // macro_rules! generate_slog_macro { // ($name:ident, $level:ident) => { @@ -99,34 +110,15 @@ macro_rules! __slog_internal { // }; // } -// FIXME: Nihi: couldn't figure out how to generalize it -// not possible to make nested macros with repeating patterns? -// Helper macro to define one macro named $fun that prints with format + args -// macro_rules! nested { -// (($($f:ident),*) $args:tt) => { -// println!["asdf"]; -// $(nested!(@call $f $args);)* -// println!["asdf over"]; -// }; -// (@call $f:ident ($($arg:expr),*)) => { -// println![stringify![$f]]; -// println![$($arg),*]; -// println!["asdfg over"]; -// }; -// () => { -// println!["no match"]; -// } -// } - #[macro_export] macro_rules! sinfo { // ( $(fn|file|line|column|mod)+ ; $($arg:tt)* ) => { // $crate::__slog_internal!(info, $($arg)*); // }; - ( $sublevel:ident; $( $flag:ident ),+ + $(;)? $($arg:tt)* ) => { - println![stringify!(sublevel)]; - $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); - }; + // ( $sublevel:ident; $( $flag:ident ),+ ; $($arg:tt)* ) => { + // println![stringify!(sublevel)]; + // $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); + // }; ( $( $flag:ident ),+ ; $($arg:tt)* ) => { $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); }; @@ -135,6 +127,16 @@ macro_rules! sinfo { }; } +#[macro_export] +macro_rules! swarn { + ( $( $flag:ident ),+ ; $($arg:tt)* ) => { + $crate::__slog_internal!(warn, $( $flag ),+ ; $($arg)*); + }; + ($($arg:tt)*) => { + $crate::__slog_internal!(warn, $($arg)*); + }; +} + #[macro_export] macro_rules! sdebug { ( $( $flag:ident ),+ ; $($arg:tt)* ) => { @@ -144,6 +146,7 @@ macro_rules! sdebug { $crate::__slog_internal!(debug, $($arg)*); }; } + #[macro_export] macro_rules! strace { ( $( $flag:ident ),+ ; $($arg:tt)* ) => { @@ -153,6 +156,7 @@ macro_rules! strace { $crate::__slog_internal!(trace, $($arg)*); }; } + #[macro_export] macro_rules! serror { ( $( $flag:ident ),+ ; $($arg:tt)* ) => { @@ -161,4 +165,26 @@ macro_rules! serror { ($($arg:tt)*) => { $crate::__slog_internal!(error, $($arg)*); }; +} + +/// ## Example usage +/// ```rust +/// debug_where!(); +/// ``` +/// ```rust +/// debug_where!("Entering important state {}", state); +/// ``` +#[macro_export] +macro_rules! debug_where { + () => { + log::debug!(target: "function", "From: {}", $crate::function!()); + }; + ($($arg:tt)*) => { + log::debug!( + target: "function", + "[{}] - {}", + $crate::function!(), + format!($($arg)*) + ); + }; } \ No newline at end of file diff --git a/sleuth/src/tools/logger.rs b/sleuth/src/tools/logger.rs new file mode 100644 index 0000000..5e790c5 --- /dev/null +++ b/sleuth/src/tools/logger.rs @@ -0,0 +1,81 @@ + + +use log4rs::{append::{console::ConsoleAppender, file::FileAppender}, config::{Appender, Root}, encode::pattern::PatternEncoder, filter::threshold::ThresholdFilter, init_config, Config}; + +use super::syslog::SyslogAppender; + +use log::LevelFilter; +use log4rs::config::{ Logger}; + +fn main() { + let stdout = ConsoleAppender::builder().build(); + + let requests = FileAppender::builder() + .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) + .build("log/requests.log") + .unwrap(); + + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .appender(Appender::builder().build("requests", Box::new(requests))) + .logger(Logger::builder().build("app::backend::db", LevelFilter::Info)) + .logger(Logger::builder() + .appender("requests") + .additive(false) + .build("app::requests", LevelFilter::Info)) + .build(Root::builder().appender("stdout").build(LevelFilter::Warn)) + .unwrap(); + + let handle = log4rs::init_config(config).unwrap(); + + // use handle to change logger configuration at runtime +} +pub fn init_syslog() -> anyhow::Result<()> { + // use function name + let console = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new( + // TODO: make this configurable opt? Maybe only for syslog or only file log + // "[{d(%Y-%m-%d %H:%M:%S)} {h({l:5})}] [{f}:{L}] {m}{n}", // Log file name and line + "{h([{d(%H:%M:%S)} {l:5}])} {m}{n}", + ))) + .build(); + + let syslog = SyslogAppender::builder() + .encoder(Box::new(PatternEncoder::new( + // TODO: make this configurable opt? Maybe only for syslog or only file log + // FIXME: Nihi: add valid syslog pattern in Appender + "[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] {m}{n}", // Log file name and line + ))) + .build(); + let file = FileAppender::builder() + // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] [{M}] [{f}:{L}] {m}{n}\n"))) + // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] {P} [{l:6}] [{t}] {m}{n}"))) + .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)} {P} {l:6}| {t:10}] {m}{n}"))) + // .build("my_log_file.log")?; + .build(r"U:\Unchained\UnchainedSleuth\unchained.log")?; // FIXME: Nihi: LOCAL FILE + let kismet = FileAppender::builder() + .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)} {P} {l:6}| {t} ] {m}{n}"))) + .build(r"U:\Unchained\UnchainedSleuth\kismet.log")?; + let console_filter = ThresholdFilter::new(log::LevelFilter::Info); + // let console_filter: MetaDataFilter = MetaDataFilter::new(log::LevelFilter::Info); + + // Build the config programmatically + let config = Config::builder() + .appender(Appender::builder().filter(Box::new(console_filter)).build("console", Box::new(console))) + // .appender(Appender::builder().build("syslog", Box::new(syslog))) + .appender(Appender::builder().build("file", Box::new(file))) + .appender(Appender::builder().build("kismet", Box::new(kismet))) + .build( + Root::builder() + .appender("console") + // .appender("syslog") + .appender("file") + // .additive(false) + // .appender("kismet") + .build(log::LevelFilter::Debug), + )?; + + init_config(config)?; + + Ok(()) +} diff --git a/sleuth/src/tools/mod.rs b/sleuth/src/tools/mod.rs index d06ec76..248e3fb 100644 --- a/sleuth/src/tools/mod.rs +++ b/sleuth/src/tools/mod.rs @@ -1,5 +1,10 @@ + pub mod syslog; +pub mod logger; #[macro_use] // pub mod log_macros_squashed; // pub mod log_macros_kv; -pub mod log_macros; \ No newline at end of file +pub mod log_macros; + +#[cfg(feature="server-registration")] +pub mod server_registration; \ No newline at end of file diff --git a/sleuth/src/tools/server_registration.rs b/sleuth/src/tools/server_registration.rs new file mode 100644 index 0000000..d0b4036 --- /dev/null +++ b/sleuth/src/tools/server_registration.rs @@ -0,0 +1,389 @@ +use std::{ + sync::{Arc, Mutex, Condvar}, + thread, + time::{Duration, Instant}, +}; +use a2s::A2SClient; +use log::error; +use once_cell::sync::Lazy; +use reqwest::blocking::Client; + +const MAX_RETRIES: u8 = 10; + +// Reg start +use serde::{Deserialize, Serialize}; + +use crate::globals; + +#[derive(Serialize)] +struct RegisterRequest<'a> { + ports: Ports, + name: &'a str, + description: &'a str, + password_protected: bool, + current_map: &'a str, + player_count: i32, + max_players: i32, + local_ip_address: &'a str, + mods: &'a [ModInfo<'a>], +} + +#[derive(Serialize)] +struct Ports { + game: u16, + ping: u16, + a2s: u16, +} + +#[derive(Serialize)] +struct ModInfo<'a> { + name: &'a str, + version: &'a str, +} + +#[derive(Deserialize)] +struct RegisterResponse { + server: RegisteredServer, + key: String, + refresh_before: f64, +} + +#[derive(Deserialize)] +struct RegisteredServer { + unique_id: String, +} + +// Reg end +#[derive(Debug, Serialize)] +struct UpdatePayload<'a> { + player_count: u8, + max_players: u8, + map_name: &'a str, +} +// static mut LAST_INFO:Option = None; +pub static LAST_INFO: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); + +// Info { +// protocol: 85, +// name: "Créc", +// map: "TO_Hippodrome", +// folder: "TO", +// game: "Chivalry 2", +// app_id: 100, +// players: 1, +// max_players: 64, +// bots: 0, +// server_type: Listen, +// server_os: Windows, +// visibility: false, +// vac: false, +// the_ship: None, +// version: "261740", +// edf: 128, +// extended_server_info: ExtendedServerInfo { +// port: Some( +// 7777, +// ), +// steam_id: None, +// keywords: None, +// game_id: None, +// }, +// source_tv: None, +// } + +// static EMPTY_MODS: &[ModInfo] = &[]; // FIXME: Nihi: yuck +static EMPTY_MODS: &[ModInfo<'static>] = &[]; +pub static REGISTRATION: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); +pub struct Registration { + server_addr: String, + query_port: u16, + client: Client, + stop_update: Arc<(Mutex, Condvar)>, + stop_heartbeat: Arc<(Mutex, Condvar)>, + heartbeat_thread: Mutex>>, + last_info: Option +} + +fn instant_from_unix_time(unix_secs: f64) -> Option { + // Convert f64 seconds to Duration + let whole = unix_secs.trunc() as u64; + let frac = unix_secs.fract(); + let nanos = (frac * 1_000_000_000.0) as u32; + + let sys_time = std::time::UNIX_EPOCH.checked_add(Duration::new(whole, nanos))?; + let now = std::time::SystemTime::now(); + + if sys_time > now { + // time in the future: compute duration from now + let dur = sys_time.duration_since(now).ok()?; + Some(Instant::now() + dur) + } else { + // time in the past: compute duration ago + let dur = now.duration_since(sys_time).ok()?; + Instant::now().checked_sub(dur) + } +} + +impl Registration { + pub fn new(ip: &str, query_port: u16) -> Self { + Self { + server_addr: format!("{}:{}", ip, query_port), + query_port, + client: Client::new(), + stop_update: Arc::new((Mutex::new(false), Condvar::new())), + stop_heartbeat: Arc::new((Mutex::new(false), Condvar::new())), + heartbeat_thread: Mutex::new(None), + last_info: None + } + } + + pub fn register_server( + &self, + server_list_url: &str, + local_ip: &str, + game_port: u16, + ping_port: u16, + query_port: u16, + name: &str, + description: &str, + current_map: &str, + player_count: i32, + max_players: i32, + // mods: &[ModInfo], + password_protected: bool, + // ) -> Result<(String, String, f64), reqwest::Error> { + + ) -> Result<(String, String, f64), String> { + let mods: &[ModInfo] = &[]; + let req_body = RegisterRequest { + ports: Ports { + game: game_port, + ping: ping_port, + a2s: query_port, + }, + name, + description, + password_protected, + current_map, + player_count, + max_players, + local_ip_address: local_ip, + mods, + }; + + let response = self + .client + .post(&format!("{}/api/v1/servers", server_list_url)) + .json(&req_body) + .send().unwrap(); + + if !response.status().is_success() { + // You can implement detailed error handling here + eprintln!("Registration failed: {}", + reqwest::StatusCode::from_u16(response.status().as_u16()).unwrap()); + // return Err(reqwest::Error::new(Kind::Request, "Registration Failed")); + return Err("ERROR".to_string()) + // return Err(reqwest::Error::new( + // reqwest::StatusCode::from_u16(response.status().as_u16()).unwrap(), + // "Registration failed", + // )); + } + + // let parsed: RegisterResponse = response.json()?; + let resp: Result<(String, String, f64), String> = match response.json() as Result { + Ok(resp) => { + Ok(( + resp.server.unique_id, + resp.key, + resp.refresh_before, + )) + }, + _ => { + eprintln!("NO RESPONSE"); + Err("some".to_string()) + } + }; + resp + + } + + pub fn start(&self, server_list_url: &str, id: &str, key: &str) { + let client = self.client.clone(); + let server_addr = self.server_addr.clone(); + let stop_flag = self.stop_update.clone(); + let id = id.to_string(); + let key = key.to_string(); + let url = server_list_url.to_string(); + + thread::spawn(move || { + let a2s = A2SClient::new().unwrap(); + loop { + let &(ref lock, ref cvar) = &*stop_flag; + if *lock.lock().unwrap() { + break; + } + + let mut retries = 0; + while retries < MAX_RETRIES { + // match a2s.info(&server_addr) { + // Ok(info) => { + // println!("CONNECTED {info:#?}") + // }, + // Err(e) => println!("failed {e}"), + // } + if let Ok(info) = a2s.info(&server_addr) { + let payload = UpdatePayload { + player_count: info.players, + max_players: info.max_players, + // map_name: &info.map_name, + map_name: info.map.as_str(), + }; + + let res = client.post(format!("{}/update", url)) + .json(&payload) + .send(); + + if res.is_ok() { + // self.last_info = Some(info.clone()); + let last_info = Arc::clone(&LAST_INFO); + *last_info.lock().unwrap() = Some(info.clone()); + sdebug!(f; "Updated server: {:?}", payload); + break; + } + } + else { + sdebug!(f; "Failed to connect to a2s"); + } + + retries += 1; + sdebug!(f; "Retrying A2S query ({}/{})", retries, MAX_RETRIES); + thread::sleep(Duration::from_secs(1)); + } + + let now = Instant::now(); + let timeout = Duration::from_secs(10); + let _ = cvar.wait_timeout(lock.lock().unwrap(), timeout).unwrap(); + } + }); + } + + pub fn stop(&self) { + let &(ref lock, ref cvar) = &*self.stop_update; + *lock.lock().unwrap() = true; + cvar.notify_all(); + } + + pub fn start_heartbeat(self: Arc, server_list_url: &str, id: &str, key: &str) { + let stop_flag = self.stop_heartbeat.clone(); + let client = self.client.clone(); + let url = server_list_url.to_string(); + let mut id = id.to_string(); + let mut key = key.to_string(); + let mut refresh_before: f64 = 0.0; + let self_clone = Arc::clone(&self); + let stop_flag = self_clone.stop_heartbeat.clone(); + + let handle = thread::spawn(move || { + let mut refresh_by = Instant::now() + Duration::from_secs(30); // default interval + + loop { + let &(ref lock, ref cvar) = &*stop_flag; + if *lock.lock().unwrap() { + break; + } + + // Only send heartbeat if it's near refresh_by time + if Instant::now() >= refresh_by { + // Send heartbeat POST/GET, here assumed POST to "/heartbeat" + // Replace with your actual heartbeat logic & parsing + + let res = client.post(format!("{}/heartbeat", url)) + .json(&serde_json::json!({ + "id": id, + "key": key, + })) + .send(); + + match res { + Ok(resp) if resp.status().is_success() => { + // If your server returns next refresh interval, parse it here. + // For demo, assume fixed 30s interval: + refresh_by = Instant::now() + Duration::from_secs(30); + sdebug!(f; "Heartbeat successful"); + } + Ok(resp) if resp.status().as_u16() == 404 => { + sdebug!(f; "Registration expired; re-register here"); + + let last_info = Arc::clone(&LAST_INFO); + // *last_info.lock().unwrap() = Some(info.clone()); + let empty: &[ModInfo<'_>] = &[]; + if let Some(info) = LAST_INFO.lock().unwrap().as_ref() { + let args = &globals().cli_args; + let name = format!("{}\n(local server)", info.name); + let txt = format!("{} (build {})\n{} server ", info.game, info.version, info.folder); + + let res = self_clone.register_server( + &url, + "127.0.0.1", + info.extended_server_info.port.unwrap(), + args.game_server_ping_port.unwrap(), + args.game_server_query_port.unwrap(), + &name, + &txt, + &info.map, + info.players as i32, + info.max_players as i32, + // EMPTY_MODS, + args.server_password.is_some(), + ); + + // sdebug!(f; "INFO: {info:#?}"); + + match res { + Ok((unique_id, new_key, refresh)) => { + id = unique_id; + key = new_key; + // let test = Instant::now() + (Duration::from_secs_f64(refresh) - std::time::UNIX_EPOCH); + refresh_by = instant_from_unix_time(refresh).unwrap() - Duration::from_secs(5); + }, + Err(e) => serror!(f; "FAILED TO REGISTER {e}"), + } + } + else { + serror!(f; "No info saved"); + refresh_by = Instant::now() + Duration::from_secs(5); + } + + // TODO: re-register logic (POST register, update id/key) + } + Ok(resp) => { + serror!(f; "Heartbeat failed with status: {}", resp.status()); + } + Err(e) => { + serror!(f; "Heartbeat error: {}", e); + } + } + } + + // Sleep until next check or stop signal + let wait_time = refresh_by.saturating_duration_since(Instant::now()); + let mut lock_guard = lock.lock().unwrap(); + if cvar.wait_timeout(lock_guard, wait_time).unwrap().0.clone() { + break; + } + } + }); + + *self.heartbeat_thread.lock().unwrap() = Some(handle); + } + + pub fn stop_heartbeat(&self) { + let &(ref lock, ref cvar) = &*self.stop_heartbeat; + *lock.lock().unwrap() = true; + cvar.notify_all(); + + if let Some(handle) = self.heartbeat_thread.lock().unwrap().take() { + handle.join().unwrap(); + } + } +} diff --git a/sleuth/src/tools/syslog.rs b/sleuth/src/tools/syslog.rs index 1f78504..6db597e 100644 --- a/sleuth/src/tools/syslog.rs +++ b/sleuth/src/tools/syslog.rs @@ -47,6 +47,7 @@ use std::io::{self, Cursor, Write}; use std::sync::{Arc, Mutex}; use log4rs::append::file::FileAppender; use log4rs::filter::threshold::ThresholdFilter; +use log4rs::filter::{Filter, Response}; use once_cell::sync::Lazy; const DEFAULT_BUF_SIZE: usize = 4096; @@ -106,7 +107,7 @@ use chrono::Local; use std::{fmt}; use std::net::UdpSocket; -use log::{info, Level, Record}; +use log::{info, Level, LevelFilter, Record}; use log4rs::{ append::{console::{ConsoleAppender, Target}, Append}, config::{Appender, Config, Root}, @@ -246,63 +247,68 @@ impl fmt::Debug for SyslogAppender { } } -pub fn init_syslog() -> anyhow::Result<()> { - // let console = ConsoleAppender::builder() - // .encoder(Box::new(PatternEncoder::new( - // "[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] {m}{n}", - // ))) - // .build(); - info!(target:"syslog_init", "hello"); - info!(target:"syslog", "hello syslog"); - - // use function name - let console = ConsoleAppender::builder() - .encoder(Box::new(PatternEncoder::new( - // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", - // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", - - // TODO: make this configurable opt? Maybe only for syslog or only file log - // "[{d(%Y-%m-%d %H:%M:%S)} {h({l:5})}] [{f}:{L}] {m}{n}", // Log file name and line - "{h([{d(%H:%M:%S)} {l:5}])} {m}{n}", - ))) - .build(); - - let syslog = SyslogAppender::builder() - .encoder(Box::new(PatternEncoder::new( - // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", - // "[{d(%Y-%m-%d %H:%M:%S)}] {h:1}[{l:5}]{h:-1} {m}{n}", - - // TODO: make this configurable opt? Maybe only for syslog or only file log - "[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] {m}{n}", // Log file name and line - ))) - .build(); - let file = FileAppender::builder() - // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] [{M}] [{f}:{L}] {m}{n}\n"))) - .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] {P} [{l:6}] [{t}] {m}{n}"))) - // .build("my_log_file.log")?; - .build(r"U:\Unchained\UnchainedSleuth\unchained.log")?; - // Set up our custom syslog UDP appender - // let syslog = SyslogAppender::new( - // "127.0.0.1:514", // TODO: config option - // "rustlib", // Build from launch type? - // "unchained", - // )?; - let console_filter = ThresholdFilter::new(log::LevelFilter::Info); - - // Build the config programmatically - let config = Config::builder() - .appender(Appender::builder().filter(Box::new(console_filter)).build("console", Box::new(console))) - .appender(Appender::builder().build("syslog", Box::new(syslog))) - .appender(Appender::builder().build("file", Box::new(file))) - .build( - Root::builder() - .appender("console") - .appender("syslog") - .appender("file") - .build(log::LevelFilter::Debug), - )?; - - init_config(config)?; - - Ok(()) -} \ No newline at end of file + +// Filter + +pub struct MetaDataFilterConfig { + level: LevelFilter, +} + +/// A filter that rejects all events at a level below a provided threshold. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct MetaDataFilter { + level: LevelFilter, +} + +impl MetaDataFilter { + /// Creates a new `MetaDataFilter` with the specified threshold. + pub fn new(level: LevelFilter) -> MetaDataFilter { + MetaDataFilter { level } + } +} + +impl Filter for MetaDataFilter { + fn filter(&self, record: &Record) -> Response { + fn strip_ansi_codes(input: &str) -> String { + let ansi_regex = regex::Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap(); + ansi_regex.replace_all(input, "").into_owned() + } + + println!("FILTERING {:#?}", record); + // let clean_str = strip_ansi_codes(asd); + if record.level() > self.level { + Response::Reject + } else { + Response::Neutral + } + } +} + +/// A deserializer for the `MetaDataFilter`. +/// +/// # Configuration +/// +/// ```yaml +/// kind: threshold +/// +/// # The threshold log level to filter at. Required +/// level: warn +/// ``` +#[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct MetaDataFilterDeserializer; + +#[cfg(feature = "config_parsing")] +impl Deserialize for MetaDataFilterDeserializer { + type Trait = dyn Filter; + + type Config = MetaDataFilterConfig; + + fn deserialize( + &self, + config: MetaDataFilterConfig, + _: &Deserializers, + ) -> anyhow::Result> { + Ok(Box::new(MetaDataFilter::new(config.level))) + } +} From 2a9c10febea786c2d5fe16d7d57085ee5fc665b3 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 17:28:41 +0200 Subject: [PATCH 07/18] extra cfg for registration --- sleuth/Cargo.toml | 6 +++--- sleuth/src/lib.rs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 6c617c5..e632fcf 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -85,11 +85,11 @@ default = [ "chat-log", "chiv2", "mirage", - "demo", - "syslog-client", + #"demo", + #"syslog-client", "rcon", "dev", - "server-registration", + #"server-registration", ] serde-resolvers = [] image-elf = [] diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 09c2f30..90d8184 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -33,6 +33,7 @@ use resolvers::kismet_dev::*; use serde::Serialize; use serde_json::to_writer_pretty; use tools::logger::init_syslog; +#[cfg(feature="server-registration")] use tools::server_registration::Registration; use winapi::um::winnt::FILE_APPEND_DATA; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; From 8a43bc8fd1af3f8737ded48e955086d140eaa777 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 17:31:28 +0200 Subject: [PATCH 08/18] enable all features --- sleuth/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index e632fcf..6c617c5 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -85,11 +85,11 @@ default = [ "chat-log", "chiv2", "mirage", - #"demo", - #"syslog-client", + "demo", + "syslog-client", "rcon", "dev", - #"server-registration", + "server-registration", ] serde-resolvers = [] image-elf = [] From a33354dfc82e2d9c494da280173087a29a4e4ff1 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 17:33:24 +0200 Subject: [PATCH 09/18] clippy --- sleuth/src/lib.rs | 23 +++++++++-------------- sleuth/src/resolvers/admin_control.rs | 19 ++++++++----------- sleuth/src/resolvers/kismet_dev.rs | 2 +- sleuth/src/resolvers/macros.rs | 8 ++++---- sleuth/src/resolvers/mod.rs | 2 +- sleuth/src/resolvers/rcon.rs | 9 ++++----- sleuth/src/scan.rs | 9 +++------ sleuth/src/tools/server_registration.rs | 23 +++++++++++------------ sleuth/src/tools/syslog.rs | 12 ++++-------- 9 files changed, 45 insertions(+), 62 deletions(-) diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 90d8184..485db74 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -2,7 +2,6 @@ mod resolvers; mod scan; -use core::fmt; use std::time::Duration; use std::{env, thread}; use std::fs::File; @@ -14,7 +13,6 @@ mod tools; mod chiv2; use anyhow::Result; -use chrono::offset; use patternsleuth::resolvers::unreal::blueprint_library::UFunctionBind; use patternsleuth::resolvers::unreal::*; // use dll_hook::ue::*; @@ -26,7 +24,6 @@ use patternsleuth::resolvers::unreal::{fname::FNameToString, gmalloc::GMalloc, guobject_array::{FUObjectArrayAllocateUObjectIndex, FUObjectArrayFreeUObjectIndex, GUObjectArray} }; -use resolvers::macros; use resolvers::admin_control::*; #[cfg(feature="kismet-log")] use resolvers::kismet_dev::*; @@ -35,12 +32,10 @@ use serde_json::to_writer_pretty; use tools::logger::init_syslog; #[cfg(feature="server-registration")] use tools::server_registration::Registration; -use winapi::um::winnt::FILE_APPEND_DATA; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; -use log::{debug, error, info, warn}; +use log::info; use clap::{command, CommandFactory, FromArgMatches, Parser, Subcommand}; -use async_std::io; pub static test_intro: &str = "\x1b[38;5;228m\ \ @@ -233,7 +228,7 @@ pub fn dump_builds() -> Result<()> { let offsets = crate::scan::OFFSETS.get().unwrap(); let base_addr = BASE_ADDR.get().unwrap(); - let mut file_path: String = env::current_exe().unwrap().to_string_lossy().into(); + let file_path: String = env::current_exe().unwrap().to_string_lossy().into(); // match env::current_exe() { // Ok(path) => file_path = path.to_string_lossy().into(), @@ -332,7 +327,7 @@ fn normalize_and_filter_args>(args: I) -> Vec = CLIArgs::command() .get_arguments() - .filter_map(|a| a.get_long().map(|s| format!("--{}", s))) + .filter_map(|a| a.get_long().map(|s| format!("--{s}"))) .collect(); let mut result = vec![bin_name]; @@ -371,7 +366,7 @@ fn normalize_and_filter_args>(args: I) -> Vec 0 && !flag.starts_with('-') { + else if !result.is_empty() && !flag.starts_with('-') { let last_valid = result.last().unwrap(); if let Some(last) = last_flag { // println!("Last '{last}' last valid '{last_valid}'"); @@ -380,7 +375,7 @@ fn normalize_and_filter_args>(args: I) -> Vec>(args: I) -> Vec Result<(CLIArgs), clap::error::Error> { +unsafe fn load_cli() -> Result { let args = std::env::args(); let parsed = normalize_and_filter_args(args); let cli = CLIArgs::try_parse_from(parsed).expect("Failed to parse CLI atgs"); @@ -448,8 +443,8 @@ fn intro() { let max_color = 231; for line in test_intro.lines() { for ch in line.chars() { - let color = format!("\x1b[38;5;{}m", color_index); - print!("{}{}\x1b[0m", color, ch); + let color = format!("\x1b[38;5;{color_index}m"); + print!("{color}{ch}\x1b[0m"); color_index += 1; if color_index > max_color { @@ -480,7 +475,7 @@ pub extern "C" fn generate_json() -> u8 { // intro(); // thread::sleep(Duration::from_secs(10)); print!("{test_intro}"); - print!("\n"); + println!(); init_syslog().expect("Failed to init syslog"); unsafe { init_globals() }; diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index fb6de70..5875f60 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -1,15 +1,12 @@ -use client_message::{parse_chat_line, ChatMessage}; +use client_message::parse_chat_line; use log::{debug, info, warn}; -use retour::static_detour; -use std::{collections::HashMap, sync::Arc}; -use std::error::Error; +use std::sync::Arc; use std::os::raw::c_void; -use std::mem; use crate::resolvers::rcon::LAST_COMMAND; -use crate::{chiv2::*, sdebug}; -use crate::{globals, ue::*}; +use crate::chiv2::*; +use crate::ue::*; define_pattern_resolver![UTBLLocalPlayer_Exec, { // "75 18 ?? ?? ?? ?? 75 12 4d 85 f6 74 0d 41 38 be ?? ?? ?? ?? 74 04 32 db eb 9b 48 8b 5d 7f 49 8b d5 4c 8b 45 77 4c 8b cb 49 8b cf", // EGS - latest @@ -46,7 +43,7 @@ CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { } if fstring.len() > 1 { - warn!("Execuing Command: {}", fstring); + warn!("Execuing Command: {fstring}"); *lock.lock().unwrap() = None; unsafe { o_ExecuteConsoleCommand.call(&mut fstring); } } @@ -115,11 +112,11 @@ CREATE_HOOK!(StaticFindObjectSafe, *mut UObject, (ObjectClass:*mut UClass, Objec mod client_message { - use log::warn; + use regex::Regex; use crate::chiv2::EChatType; - use std::str::FromStr; + #[derive(Debug)] pub struct ChatMessage<'a> { @@ -227,7 +224,7 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi info!("Chat: channel {}, name {}, message {}, ", msg.channel, msg.name, msg.message); } else { - info!(target: "system_chat", "\x1b[38;5;214m[ {:10?} ] \x1b[38;5;251m{}\x1b[38;5;255m", chat_type, message); + info!(target: "system_chat", "\x1b[38;5;214m[ {chat_type:10?} ] \x1b[38;5;251m{message}\x1b[38;5;255m"); } } else { diff --git a/sleuth/src/resolvers/kismet_dev.rs b/sleuth/src/resolvers/kismet_dev.rs index 9cc7475..643d030 100644 --- a/sleuth/src/resolvers/kismet_dev.rs +++ b/sleuth/src/resolvers/kismet_dev.rs @@ -1,5 +1,5 @@ - use crate::sdebug; + use crate::swarn; /*KismetExecutionMessage*/ diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 6e32373..5489920 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -87,7 +87,7 @@ macro_rules! attach_hooks_list { $pattern:ident ),+ $(,)? ]) => {{ use std::collections::HashMap; - use crate::resolvers::macros::HookFn; + use $crate::resolvers::macros::HookFn; paste::paste! { #[allow(dead_code)] enum ActiveHooks { @@ -166,7 +166,7 @@ macro_rules! CREATE_HOOK { .enable()?;; // crate::debug_where!("Attached [ 0x{:#x?} ]", rel_address); - crate::sdebug!(f; "Attached [ 0x{:#x?} ]", rel_address); + $crate::sdebug!(f; "Attached [ 0x{:#x?} ]", rel_address); Ok(Some(rel_address as usize)) }, } @@ -584,7 +584,7 @@ macro_rules! define_signature_fn { ) => { #[allow(non_snake_case)] #[allow(dead_code)] - pub fn $fn_name<'a>(s: &'a str) -> crate::resolvers::macros::Signature<'a> { + pub fn $fn_name<'a>(s: &'a str) -> $crate::resolvers::macros::Signature<'a> { let calc: std::sync::Arc< dyn Fn(&AsyncContext<'a>) -> Pin> + Send + 'a>> + Send @@ -602,7 +602,7 @@ macro_rules! define_signature_fn { Box::pin(fut) }); - crate::resolvers::macros::Signature { + $crate::resolvers::macros::Signature { // kind: $kind, offset_calculator: calc, signature_string: s.to_string(), diff --git a/sleuth/src/resolvers/mod.rs b/sleuth/src/resolvers/mod.rs index 505783f..2022e7c 100644 --- a/sleuth/src/resolvers/mod.rs +++ b/sleuth/src/resolvers/mod.rs @@ -23,7 +23,7 @@ pub fn current_platform() -> PlatformType { impl std::fmt::Display for PlatformType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs index 08d43c4..4d2344a 100644 --- a/sleuth/src/resolvers/rcon.rs +++ b/sleuth/src/resolvers/rcon.rs @@ -1,5 +1,5 @@ use std::{ - io::{stdin, stdout, BufRead, BufReader, Write}, + io::{stdin, BufRead, BufReader}, net::TcpListener, sync::{Arc, Mutex}, thread, @@ -8,7 +8,6 @@ use std::{ use log::{error, info, warn}; use once_cell::sync::Lazy; -use crate::{sdebug, sinfo}; #[cfg(feature="rcon")] fn get_rcon_port() -> Option { @@ -28,7 +27,7 @@ pub fn handle_rcon() { let listener = TcpListener::bind(("127.0.0.1", port)) .expect("[RCON] Failed to bind to port"); - info!("[RCON] Listening on 127.0.0.1:{}", port); + info!("[RCON] Listening on 127.0.0.1:{port}"); for stream in listener.incoming() { match stream { @@ -46,7 +45,7 @@ pub fn handle_rcon() { } }); } - Err(e) => error!("[RCON] Connection failed: {}", e), + Err(e) => error!("[RCON] Connection failed: {e}"), } } } @@ -56,7 +55,7 @@ pub fn handle_rcon() { // maybe a proper prompt etc #[cfg(feature="cli-commands")] pub fn handle_cmd() { - let mut line = String::new(); + let line = String::new(); loop { let mut input = String::new(); stdin().read_line(&mut input) diff --git a/sleuth/src/scan.rs b/sleuth/src/scan.rs index 034a39a..9d5500a 100644 --- a/sleuth/src/scan.rs +++ b/sleuth/src/scan.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; -use patternsleuth::resolvers::{resolvers, unreal::game_loop::UGameEngineTick, NamedResolver, Resolution}; -use serde::{Deserialize, Serialize}; +use patternsleuth::resolvers::resolvers; use std::process; @@ -11,9 +10,7 @@ use std::process; // patternsleuth::resolvers::ResolveError // >> = Vec::new(); -use once_cell::sync::{Lazy, OnceCell}; -use std::sync::Arc; -use patternsleuth::resolvers::{ResolveError}; +use once_cell::sync::OnceCell; pub static OFFSETS: OnceCell> = OnceCell::new(); @@ -45,7 +42,7 @@ pub fn scan() -> Result, String> { .split(['(', ')']) .nth(1) .and_then(|s| s.parse::().ok()) - .map(|n| format!("{:#x}", n)) + .map(|n| format!("{n:#x}")) { // sigs_json.insert(MyItem { id: resolver.name.to_string(), name: hex.to_string() }); let val = u64::from_str_radix(hex.trim_start_matches("0x"), 16).map_err(|e| e.to_string())?; diff --git a/sleuth/src/tools/server_registration.rs b/sleuth/src/tools/server_registration.rs index d0b4036..60c20da 100644 --- a/sleuth/src/tools/server_registration.rs +++ b/sleuth/src/tools/server_registration.rs @@ -4,7 +4,6 @@ use std::{ time::{Duration, Instant}, }; use a2s::A2SClient; -use log::error; use once_cell::sync::Lazy; use reqwest::blocking::Client; @@ -127,7 +126,7 @@ fn instant_from_unix_time(unix_secs: f64) -> Option { impl Registration { pub fn new(ip: &str, query_port: u16) -> Self { Self { - server_addr: format!("{}:{}", ip, query_port), + server_addr: format!("{ip}:{query_port}"), query_port, client: Client::new(), stop_update: Arc::new((Mutex::new(false), Condvar::new())), @@ -173,7 +172,7 @@ impl Registration { let response = self .client - .post(&format!("{}/api/v1/servers", server_list_url)) + .post(format!("{server_list_url}/api/v1/servers")) .json(&req_body) .send().unwrap(); @@ -218,7 +217,7 @@ impl Registration { thread::spawn(move || { let a2s = A2SClient::new().unwrap(); loop { - let &(ref lock, ref cvar) = &*stop_flag; + let (lock, cvar) = &*stop_flag; if *lock.lock().unwrap() { break; } @@ -239,7 +238,7 @@ impl Registration { map_name: info.map.as_str(), }; - let res = client.post(format!("{}/update", url)) + let res = client.post(format!("{url}/update")) .json(&payload) .send(); @@ -268,7 +267,7 @@ impl Registration { } pub fn stop(&self) { - let &(ref lock, ref cvar) = &*self.stop_update; + let (lock, cvar) = &*self.stop_update; *lock.lock().unwrap() = true; cvar.notify_all(); } @@ -279,7 +278,7 @@ impl Registration { let url = server_list_url.to_string(); let mut id = id.to_string(); let mut key = key.to_string(); - let mut refresh_before: f64 = 0.0; + let refresh_before: f64 = 0.0; let self_clone = Arc::clone(&self); let stop_flag = self_clone.stop_heartbeat.clone(); @@ -287,7 +286,7 @@ impl Registration { let mut refresh_by = Instant::now() + Duration::from_secs(30); // default interval loop { - let &(ref lock, ref cvar) = &*stop_flag; + let (lock, cvar) = &*stop_flag; if *lock.lock().unwrap() { break; } @@ -297,7 +296,7 @@ impl Registration { // Send heartbeat POST/GET, here assumed POST to "/heartbeat" // Replace with your actual heartbeat logic & parsing - let res = client.post(format!("{}/heartbeat", url)) + let res = client.post(format!("{url}/heartbeat")) .json(&serde_json::json!({ "id": id, "key": key, @@ -367,8 +366,8 @@ impl Registration { // Sleep until next check or stop signal let wait_time = refresh_by.saturating_duration_since(Instant::now()); - let mut lock_guard = lock.lock().unwrap(); - if cvar.wait_timeout(lock_guard, wait_time).unwrap().0.clone() { + let lock_guard = lock.lock().unwrap(); + if *cvar.wait_timeout(lock_guard, wait_time).unwrap().0 { break; } } @@ -378,7 +377,7 @@ impl Registration { } pub fn stop_heartbeat(&self) { - let &(ref lock, ref cvar) = &*self.stop_heartbeat; + let (lock, cvar) = &*self.stop_heartbeat; *lock.lock().unwrap() = true; cvar.notify_all(); diff --git a/sleuth/src/tools/syslog.rs b/sleuth/src/tools/syslog.rs index 6db597e..d53acb3 100644 --- a/sleuth/src/tools/syslog.rs +++ b/sleuth/src/tools/syslog.rs @@ -45,8 +45,6 @@ use std::cell::RefCell; use std::io::{self, Cursor, Write}; use std::sync::{Arc, Mutex}; -use log4rs::append::file::FileAppender; -use log4rs::filter::threshold::ThresholdFilter; use log4rs::filter::{Filter, Response}; use once_cell::sync::Lazy; @@ -75,7 +73,7 @@ impl BufWriter { /// Returns the current buffer as a UTF-8 string and resets buffer. pub fn flush_to_string(&self) -> String { PERSISTENT_BUF.with(|buf| { - let mut buf = buf.borrow_mut(); + let buf = buf.borrow_mut(); let pos = buf.position() as usize; let slice = &buf.get_ref()[..pos]; match std::str::from_utf8(slice) { @@ -107,12 +105,10 @@ use chrono::Local; use std::{fmt}; use std::net::UdpSocket; -use log::{info, Level, LevelFilter, Record}; +use log::{Level, LevelFilter, Record}; use log4rs::{ - append::{console::{ConsoleAppender, Target}, Append}, - config::{Appender, Config, Root}, + append::{console::Target, Append}, encode::{pattern::PatternEncoder, Encode}, - init_config, // ConfigBuilder, }; @@ -274,7 +270,7 @@ impl Filter for MetaDataFilter { ansi_regex.replace_all(input, "").into_owned() } - println!("FILTERING {:#?}", record); + println!("FILTERING {record:#?}"); // let clean_str = strip_ansi_codes(asd); if record.level() > self.level { Response::Reject From 788aee40e55c9917d4aaf912fdbc125d1a335291 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 17:57:17 +0200 Subject: [PATCH 10/18] warnings --- sleuth/src/lib.rs | 58 ++++++---- sleuth/src/resolvers/admin_control.rs | 3 +- sleuth/src/resolvers/kismet_dev.rs | 2 +- sleuth/src/resolvers/macros.rs | 2 +- sleuth/src/resolvers/rcon.rs | 4 +- sleuth/src/tools/log_macros.rs | 6 +- sleuth/src/tools/logger.rs | 71 +++++++----- sleuth/src/tools/server_registration.rs | 33 +++--- sleuth/src/tools/syslog.rs | 144 ++++++++++++------------ sleuth/src/ue.rs | 2 + 10 files changed, 180 insertions(+), 145 deletions(-) diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 485db74..1af6e3a 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -35,9 +35,9 @@ use tools::server_registration::Registration; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; use log::info; -use clap::{command, CommandFactory, FromArgMatches, Parser, Subcommand}; +use clap::{command, CommandFactory, Parser, Subcommand}; -pub static test_intro: &str = "\x1b[38;5;228m\ +pub static TEST_INTRO: &str = "\x1b[38;5;228m\ \ ▄████████ ▄█ █▄ ▄█ ▄█ █▄ ▄████████ ▄█ ▄████████ ▄██ ▄ ▄█ ▄█ \r\n\ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ██▄ ███ ███ \r\n\ @@ -59,7 +59,7 @@ pub static test_intro: &str = "\x1b[38;5;228m\ \x1b[38;5;255m \r\n\ "; -pub static test_intro3: &str = "\ +pub static TEST_INTRO3: &str = "\ | █ \r\n\ | ███████ \r\n\ | ██ ███ \r\n\ @@ -147,17 +147,17 @@ struct CLIArgs { // UNHANDLED START #[arg(long = "AUTH_LOGIN")] - AUTH_LOGIN: Option, + auth_login: Option, #[arg(long = "AUTH_PASSWORD")] - AUTH_PASSWORD: Option, + auth_password: Option, #[arg(long = "AUTH_TYPE")] - AUTH_TYPE: Option, + auth_type: Option, #[arg(long = "epicapp")] epicapp: Option, #[arg(long = "epicenv")] epicenv: Option, #[arg(long = "EpicPortal")] - EpicPortal: bool, + epic_portal: bool, #[arg(long = "epicusername")] epicusername: Option, #[arg(long = "epicuserid")] @@ -287,7 +287,7 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - // use crate::resolvers::macros; hooks_new.iter().for_each(|(s, f)| { match (f)(base_address, offsets.clone()) { - Ok(addr) => { + Ok(_) => { sinfo![f; "☑ {} ", s] }, Err(e) => { @@ -368,7 +368,7 @@ fn normalize_and_filter_args>(args: I) -> Vec Result<(), clap::error::Error>{ guobject_array: guobject_array.into(), resolution, main_thread_id: std::thread::current().id(), - last_command: None, + // last_command: None, base_address: exe.base_address, is_server: false, cli_args: args, @@ -438,10 +438,11 @@ unsafe fn init_globals() -> Result<(), clap::error::Error>{ // r: 18 // n: 18 // ▌: 13 +#[allow(dead_code)] fn intro() { let mut color_index = 16; let max_color = 231; - for line in test_intro.lines() { + for line in TEST_INTRO.lines() { for ch in line.chars() { let color = format!("\x1b[38;5;{color_index}m"); print!("{color}{ch}\x1b[0m"); @@ -458,26 +459,32 @@ fn intro() { // https://stackoverflow.com/questions/38088067/equivalent-of-func-or-function-in-rust // https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=df5975cd589ae7286a769e1c70e7715d -macro_rules! function { - () => {{ - fn f() {} - fn type_name_of(_: T) -> &'static str { - std::any::type_name::() - } - let name = type_name_of(f); - &name[..name.len() - 3] - }} -} +// #[allow(unused_macros)] +// macro_rules! function { +// () => {{ +// fn f() {} +// fn type_name_of(_: T) -> &'static str { +// std::any::type_name::() +// } +// let name = type_name_of(f); +// &name[..name.len() - 3] +// }} +// } // define_pocess! #[no_mangle] pub extern "C" fn generate_json() -> u8 { // intro(); // thread::sleep(Duration::from_secs(10)); - print!("{test_intro}"); + print!("{TEST_INTRO}"); println!(); init_syslog().expect("Failed to init syslog"); - unsafe { init_globals() }; + unsafe { + match init_globals() { + Ok(_) => {}, + Err(e) => serror!(f; "No globals: {}", e), + } + }; #[cfg(feature="rcon")] std::thread::spawn(|| { @@ -586,8 +593,9 @@ static mut GLOBALS: Option = None; pub struct Globals { resolution: DllHookResolution, guobject_array: parking_lot::FairMutex<&'static ue::FUObjectArray>, + #[allow(dead_code)] main_thread_id: std::thread::ThreadId, - last_command: Option, + // last_command: Option, platform: PlatformType, base_address: usize, is_server: bool, @@ -634,6 +642,8 @@ impl Globals { } } +// FIXME: Nihi: ? +#[allow(static_mut_refs)] pub fn globals() -> &'static Globals { unsafe { GLOBALS.as_ref().unwrap() } } \ No newline at end of file diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 5875f60..0407b72 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -141,7 +141,6 @@ mod client_message { // "<8>: FACTION NOT VALID" // warn!("LINE: '{line}'"); let re = Regex::new(r#"<(\d+)>: (.+)"#).unwrap(); - let test = r#"""<4>: Carryable_Peasant_Raid_Child2_C_2147471851 : X=-3256.829 Y=-1015.456 Z=-145.546"""#; // warn!("TEST: '{test}'"); if let Some(caps) = re.captures(line) { @@ -188,7 +187,7 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi // TODO: handle commands tranformed by ChatHooks // e.g. ".hello" -> '<7>: Console Command: hello', Type: 'None'(0), MsgLifeTime: '0' // this needs to provide the playfabid somehow - unsafe { + { let cmd_filter = |c| ['/', '.'].contains(&c); match client_message::parse_chat_line(message_repl.as_str()) { Some(chat) => { diff --git a/sleuth/src/resolvers/kismet_dev.rs b/sleuth/src/resolvers/kismet_dev.rs index 643d030..463f1d6 100644 --- a/sleuth/src/resolvers/kismet_dev.rs +++ b/sleuth/src/resolvers/kismet_dev.rs @@ -24,7 +24,7 @@ CREATE_HOOK!(KismetExecutionMessage, *mut UObject, (Message:*const u16, Type: u8 unsafe { let msg = widestring::U16CStr::from_ptr_str(Message as *const u16); match FString::try_from(msg.as_slice_with_nul()) { - Ok(str) => { + Ok(_) => { let mut message = msg.to_string_lossy(); message = match message.contains('\n') { true => format!("\n{message}"),//.replace("\r\n", " "), diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 5489920..0558e79 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -163,7 +163,7 @@ macro_rules! CREATE_HOOK { [] .initialize(target, [<$name _detour_fkt>])? - .enable()?;; + .enable()?; // crate::debug_where!("Attached [ 0x{:#x?} ]", rel_address); $crate::sdebug!(f; "Attached [ 0x{:#x?} ]", rel_address); diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs index 4d2344a..dc2a03c 100644 --- a/sleuth/src/resolvers/rcon.rs +++ b/sleuth/src/resolvers/rcon.rs @@ -55,7 +55,7 @@ pub fn handle_rcon() { // maybe a proper prompt etc #[cfg(feature="cli-commands")] pub fn handle_cmd() { - let line = String::new(); + // let line = String::new(); loop { let mut input = String::new(); stdin().read_line(&mut input) @@ -63,10 +63,10 @@ pub fn handle_cmd() { let cmd_store: Arc>> = Arc::clone(&LAST_COMMAND); *cmd_store.lock().unwrap() = Some(input.trim().to_string()); match input.as_str() { - _ => {}, "findobj" => { crate::sdebug!(f; "findobj {:?}", 123); } + _ => {}, } } } \ No newline at end of file diff --git a/sleuth/src/tools/log_macros.rs b/sleuth/src/tools/log_macros.rs index eaab906..3f47d22 100644 --- a/sleuth/src/tools/log_macros.rs +++ b/sleuth/src/tools/log_macros.rs @@ -120,7 +120,7 @@ macro_rules! sinfo { // $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); // }; ( $( $flag:ident ),+ ; $($arg:tt)* ) => { - $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*); + $crate::__slog_internal!(info, $( $flag ),+ ; $($arg)*) }; ($($arg:tt)*) => { $crate::__slog_internal!(info, $($arg)*); @@ -130,7 +130,7 @@ macro_rules! sinfo { #[macro_export] macro_rules! swarn { ( $( $flag:ident ),+ ; $($arg:tt)* ) => { - $crate::__slog_internal!(warn, $( $flag ),+ ; $($arg)*); + $crate::__slog_internal!(warn, $( $flag ),+ ; $($arg)*) }; ($($arg:tt)*) => { $crate::__slog_internal!(warn, $($arg)*); @@ -160,7 +160,7 @@ macro_rules! strace { #[macro_export] macro_rules! serror { ( $( $flag:ident ),+ ; $($arg:tt)* ) => { - $crate::__slog_internal!(error, $( $flag ),+ ; $($arg)*); + $crate::__slog_internal!(error, $( $flag ),+ ; $($arg)*) }; ($($arg:tt)*) => { $crate::__slog_internal!(error, $($arg)*); diff --git a/sleuth/src/tools/logger.rs b/sleuth/src/tools/logger.rs index 5e790c5..67073b3 100644 --- a/sleuth/src/tools/logger.rs +++ b/sleuth/src/tools/logger.rs @@ -4,32 +4,33 @@ use log4rs::{append::{console::ConsoleAppender, file::FileAppender}, config::{Ap use super::syslog::SyslogAppender; -use log::LevelFilter; -use log4rs::config::{ Logger}; - -fn main() { - let stdout = ConsoleAppender::builder().build(); - - let requests = FileAppender::builder() - .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) - .build("log/requests.log") - .unwrap(); - - let config = Config::builder() - .appender(Appender::builder().build("stdout", Box::new(stdout))) - .appender(Appender::builder().build("requests", Box::new(requests))) - .logger(Logger::builder().build("app::backend::db", LevelFilter::Info)) - .logger(Logger::builder() - .appender("requests") - .additive(false) - .build("app::requests", LevelFilter::Info)) - .build(Root::builder().appender("stdout").build(LevelFilter::Warn)) - .unwrap(); - - let handle = log4rs::init_config(config).unwrap(); - - // use handle to change logger configuration at runtime -} +// use log::LevelFilter; +// use log4rs::config::{ Logger}; + +// fn main() { +// let stdout = ConsoleAppender::builder().build(); + +// let requests = FileAppender::builder() +// .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) +// .build("log/requests.log") +// .unwrap(); + +// let config = Config::builder() +// .appender(Appender::builder().build("stdout", Box::new(stdout))) +// .appender(Appender::builder().build("requests", Box::new(requests))) +// .logger(Logger::builder().build("app::backend::db", LevelFilter::Info)) +// .logger(Logger::builder() +// .appender("requests") +// .additive(false) +// .build("app::requests", LevelFilter::Info)) +// .build(Root::builder().appender("stdout").build(LevelFilter::Warn)) +// .unwrap(); + +// // let handle = log4rs::init_config(config).unwrap(); + +// // use handle to change logger configuration at runtime +// } + pub fn init_syslog() -> anyhow::Result<()> { // use function name let console = ConsoleAppender::builder() @@ -40,6 +41,7 @@ pub fn init_syslog() -> anyhow::Result<()> { ))) .build(); + #[cfg(feature="syslog-client")] let syslog = SyslogAppender::builder() .encoder(Box::new(PatternEncoder::new( // TODO: make this configurable opt? Maybe only for syslog or only file log @@ -47,6 +49,7 @@ pub fn init_syslog() -> anyhow::Result<()> { "[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] {m}{n}", // Log file name and line ))) .build(); + let file = FileAppender::builder() // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] [{l:5}] [{M}] [{f}:{L}] {m}{n}\n"))) // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] {P} [{l:6}] [{t}] {m}{n}"))) @@ -60,9 +63,19 @@ pub fn init_syslog() -> anyhow::Result<()> { // let console_filter: MetaDataFilter = MetaDataFilter::new(log::LevelFilter::Info); // Build the config programmatically - let config = Config::builder() - .appender(Appender::builder().filter(Box::new(console_filter)).build("console", Box::new(console))) - // .appender(Appender::builder().build("syslog", Box::new(syslog))) + let mut builder = Config::builder() + .appender(Appender::builder().filter(Box::new(console_filter)).build("console", Box::new(console))); + + #[cfg(feature="syslog-client")] + { + builder = builder + .appender(Appender::builder().build("syslog", Box::new(syslog))); + } + + // let config = Config::builder() + // .appender(Appender::builder().filter(Box::new(console_filter)).build("console", Box::new(console))) + // // .appender(Appender::builder().build("syslog", Box::new(syslog))) + let config = builder .appender(Appender::builder().build("file", Box::new(file))) .appender(Appender::builder().build("kismet", Box::new(kismet))) .build( diff --git a/sleuth/src/tools/server_registration.rs b/sleuth/src/tools/server_registration.rs index 60c20da..11d701d 100644 --- a/sleuth/src/tools/server_registration.rs +++ b/sleuth/src/tools/server_registration.rs @@ -91,16 +91,16 @@ pub static LAST_INFO: Lazy>>> = Lazy::new(|| // } // static EMPTY_MODS: &[ModInfo] = &[]; // FIXME: Nihi: yuck -static EMPTY_MODS: &[ModInfo<'static>] = &[]; -pub static REGISTRATION: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); +// static EMPTY_MODS: &[ModInfo<'static>] = &[]; +// pub static REGISTRATION: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); pub struct Registration { server_addr: String, - query_port: u16, + // query_port: u16, client: Client, stop_update: Arc<(Mutex, Condvar)>, stop_heartbeat: Arc<(Mutex, Condvar)>, heartbeat_thread: Mutex>>, - last_info: Option + // last_info: Option } fn instant_from_unix_time(unix_secs: f64) -> Option { @@ -127,12 +127,12 @@ impl Registration { pub fn new(ip: &str, query_port: u16) -> Self { Self { server_addr: format!("{ip}:{query_port}"), - query_port, + // query_port, client: Client::new(), stop_update: Arc::new((Mutex::new(false), Condvar::new())), stop_heartbeat: Arc::new((Mutex::new(false), Condvar::new())), heartbeat_thread: Mutex::new(None), - last_info: None + // last_info: None } } @@ -206,12 +206,14 @@ impl Registration { } - pub fn start(&self, server_list_url: &str, id: &str, key: &str) { + pub fn start(&self, server_list_url: &str, _: &str, _: &str) { + // pub fn start(&self, server_list_url: &str, id: &str, key: &str) { let client = self.client.clone(); let server_addr = self.server_addr.clone(); let stop_flag = self.stop_update.clone(); - let id = id.to_string(); - let key = key.to_string(); + // todo: run registration from here? + // let id = id.to_string(); + // let key = key.to_string(); let url = server_list_url.to_string(); thread::spawn(move || { @@ -259,13 +261,14 @@ impl Registration { thread::sleep(Duration::from_secs(1)); } - let now = Instant::now(); let timeout = Duration::from_secs(10); let _ = cvar.wait_timeout(lock.lock().unwrap(), timeout).unwrap(); } }); } + // FIXME: Nihi: implement + #[allow(dead_code)] pub fn stop(&self) { let (lock, cvar) = &*self.stop_update; *lock.lock().unwrap() = true; @@ -273,12 +276,12 @@ impl Registration { } pub fn start_heartbeat(self: Arc, server_list_url: &str, id: &str, key: &str) { - let stop_flag = self.stop_heartbeat.clone(); + // let stop_flag = self.stop_heartbeat.clone(); let client = self.client.clone(); let url = server_list_url.to_string(); let mut id = id.to_string(); let mut key = key.to_string(); - let refresh_before: f64 = 0.0; + // let refresh_before: f64 = 0.0; let self_clone = Arc::clone(&self); let stop_flag = self_clone.stop_heartbeat.clone(); @@ -313,9 +316,9 @@ impl Registration { Ok(resp) if resp.status().as_u16() == 404 => { sdebug!(f; "Registration expired; re-register here"); - let last_info = Arc::clone(&LAST_INFO); + // let last_info = Arc::clone(&LAST_INFO); // *last_info.lock().unwrap() = Some(info.clone()); - let empty: &[ModInfo<'_>] = &[]; + // let empty: &[ModInfo<'_>] = &[]; if let Some(info) = LAST_INFO.lock().unwrap().as_ref() { let args = &globals().cli_args; let name = format!("{}\n(local server)", info.name); @@ -376,6 +379,8 @@ impl Registration { *self.heartbeat_thread.lock().unwrap() = Some(handle); } + // FIXME: Nihi: implement + #[allow(dead_code)] pub fn stop_heartbeat(&self) { let (lock, cvar) = &*self.stop_heartbeat; *lock.lock().unwrap() = true; diff --git a/sleuth/src/tools/syslog.rs b/sleuth/src/tools/syslog.rs index d53acb3..3333870 100644 --- a/sleuth/src/tools/syslog.rs +++ b/sleuth/src/tools/syslog.rs @@ -44,9 +44,8 @@ use std::cell::RefCell; use std::io::{self, Cursor, Write}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use log4rs::filter::{Filter, Response}; -use once_cell::sync::Lazy; const DEFAULT_BUF_SIZE: usize = 4096; type PersistentBuf = Cursor>; @@ -57,8 +56,8 @@ thread_local! { } // Optional: for some kind of global shared output string -pub static SYSLOG_BUF: Lazy>>> = - Lazy::new(|| Arc::new(Mutex::new(None))); +// pub static SYSLOG_BUF: Lazy>>> = +// Lazy::new(|| Arc::new(Mutex::new(None))); pub struct BufWriter; @@ -91,7 +90,7 @@ impl Write for BufWriter { fn flush(&mut self) -> io::Result<()> { // In case you want to trigger network send from here - let log_line = self.flush_to_string(); + // let log_line = self.flush_to_string(); // println!("Sending syslog: {}", log_line); // send_to_syslog(log_line); // You can call your network send function here Ok(()) @@ -101,13 +100,12 @@ impl log4rs::encode::Write for BufWriter {} // https://en.wikipedia.org/wiki/Syslog -use chrono::Local; -use std::{fmt}; +use std::fmt; use std::net::UdpSocket; -use log::{Level, LevelFilter, Record}; +use log::{LevelFilter, Record}; use log4rs::{ - append::{console::Target, Append}, + append::Append, encode::{pattern::PatternEncoder, Encode}, // ConfigBuilder, }; @@ -116,7 +114,7 @@ use log4rs::{ pub struct SyslogAppender { // writer: Writer, encoder: Box, - do_write: bool, + // do_write: bool, socket: Arc, target_addr: String, hostname: String, @@ -145,7 +143,7 @@ impl SyslogAppender { pub fn builder() -> SyslogAppenderBuilder { SyslogAppenderBuilder { encoder: None, - target: Target::Stdout, + // target: Target::Stdout, } } } @@ -163,7 +161,7 @@ impl SyslogAppender { /// A builder for `SyslogAppender`s. pub struct SyslogAppenderBuilder { encoder: Option>, - target: Target, + // target: Target, } impl SyslogAppenderBuilder { @@ -194,7 +192,7 @@ impl SyslogAppenderBuilder { encoder: self .encoder .unwrap_or_else(|| Box::::default()), - do_write: true, + // do_write: true, socket: Arc::new(socket), target_addr: "127.0.0.1:514".to_string(), hostname: "unchained".to_string(), @@ -204,34 +202,34 @@ impl SyslogAppenderBuilder { } impl SyslogAppender { - pub fn new(syslog_addr: &str, hostname: &str, tag: &str) -> std::io::Result { - let socket = UdpSocket::bind("0.0.0.0:0")?; - Ok(Self { - socket: Arc::new(socket), - target_addr: syslog_addr.to_string(), - hostname: hostname.to_string(), - tag: tag.to_string(), - encoder: todo!(), - do_write: todo!(), - }) - } - - fn format_syslog_message(&self, level: Level, msg: &str) -> String { - let pri = match level { - Level::Error => 3, - Level::Warn => 4, - Level::Info => 6, - Level::Debug | Level::Trace => 7, - } + 8; // facility 1 (user) - // TODO: Extend facility, let user provide it. Enum? - // Also syslog it supports more severity levels - - let timestamp = Local::now().format("%b %d %H:%M:%S"); - format!( - "<{}>{} {} {}: {}", - pri, timestamp, self.hostname, self.tag, msg - ) - } + // pub fn new(syslog_addr: &str, hostname: &str, tag: &str) -> std::io::Result { + // let socket = UdpSocket::bind("0.0.0.0:0")?; + // Ok(Self { + // socket: Arc::new(socket), + // target_addr: syslog_addr.to_string(), + // hostname: hostname.to_string(), + // tag: tag.to_string(), + // encoder: todo!(), + // // do_write: todo!(), + // }) + // } + + // fn format_syslog_message(&self, level: Level, msg: &str) -> String { + // let pri = match level { + // Level::Error => 3, + // Level::Warn => 4, + // Level::Info => 6, + // Level::Debug | Level::Trace => 7, + // } + 8; // facility 1 (user) + // // TODO: Extend facility, let user provide it. Enum? + // // Also syslog it supports more severity levels + + // let timestamp = Local::now().format("%b %d %H:%M:%S"); + // format!( + // "<{}>{} {} {}: {}", + // pri, timestamp, self.hostname, self.tag, msg + // ) + // } } impl fmt::Debug for SyslogAppender { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -246,16 +244,22 @@ impl fmt::Debug for SyslogAppender { // Filter +// FIXME: Nihi: implement +#[allow(dead_code)] pub struct MetaDataFilterConfig { level: LevelFilter, } +// FIXME: Nihi: implement +#[allow(dead_code)] /// A filter that rejects all events at a level below a provided threshold. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct MetaDataFilter { level: LevelFilter, } +// FIXME: Nihi: implement +#[allow(dead_code)] impl MetaDataFilter { /// Creates a new `MetaDataFilter` with the specified threshold. pub fn new(level: LevelFilter) -> MetaDataFilter { @@ -263,6 +267,8 @@ impl MetaDataFilter { } } +// FIXME: Nihi: implement +#[allow(dead_code)] impl Filter for MetaDataFilter { fn filter(&self, record: &Record) -> Response { fn strip_ansi_codes(input: &str) -> String { @@ -280,31 +286,31 @@ impl Filter for MetaDataFilter { } } -/// A deserializer for the `MetaDataFilter`. -/// -/// # Configuration -/// -/// ```yaml -/// kind: threshold -/// -/// # The threshold log level to filter at. Required -/// level: warn -/// ``` -#[cfg(feature = "config_parsing")] -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub struct MetaDataFilterDeserializer; - -#[cfg(feature = "config_parsing")] -impl Deserialize for MetaDataFilterDeserializer { - type Trait = dyn Filter; - - type Config = MetaDataFilterConfig; - - fn deserialize( - &self, - config: MetaDataFilterConfig, - _: &Deserializers, - ) -> anyhow::Result> { - Ok(Box::new(MetaDataFilter::new(config.level))) - } -} +// /// A deserializer for the `MetaDataFilter`. +// /// +// /// # Configuration +// /// +// /// ```yaml +// /// kind: threshold +// /// +// /// # The threshold log level to filter at. Required +// /// level: warn +// /// ``` +// #[cfg(feature = "config_parsing")] +// #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +// pub struct MetaDataFilterDeserializer; + +// #[cfg(feature = "config_parsing")] +// impl Deserialize for MetaDataFilterDeserializer { +// type Trait = dyn Filter; + +// type Config = MetaDataFilterConfig; + +// fn deserialize( +// &self, +// config: MetaDataFilterConfig, +// _: &Deserializers, +// ) -> anyhow::Result> { +// Ok(Box::new(MetaDataFilter::new(config.level))) +// } +// } diff --git a/sleuth/src/ue.rs b/sleuth/src/ue.rs index f80bae7..c6b1a2c 100644 --- a/sleuth/src/ue.rs +++ b/sleuth/src/ue.rs @@ -2,6 +2,8 @@ // https://github.com/trumank/patternsleuth/blob/master/examples/dll_hook/src/ue.rs // FIXME: Nihi: the file is unchanged. Find a way to get use it via git without submodules +#![allow(dead_code, private_interfaces)] + use std::{ cell::UnsafeCell, ffi::c_void, From 0dd75267ad03985c8807616a44d8eefbd5ae23b1 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 18:08:35 +0200 Subject: [PATCH 11/18] more warnings --- sleuth/src/chiv2.rs | 1 + sleuth/src/lib.rs | 4 +++- sleuth/src/resolvers/kismet_dev.rs | 29 ++++++++++++----------------- sleuth/src/resolvers/mod.rs | 1 + sleuth/src/resolvers/rcon.rs | 9 +++------ sleuth/src/scan.rs | 4 ++-- 6 files changed, 22 insertions(+), 26 deletions(-) diff --git a/sleuth/src/chiv2.rs b/sleuth/src/chiv2.rs index 2ee3ad6..21249bf 100644 --- a/sleuth/src/chiv2.rs +++ b/sleuth/src/chiv2.rs @@ -1,6 +1,7 @@ #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(clippy::upper_case_acronyms)] pub enum EChatType { AllSay, TeamSay, diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 1af6e3a..ab129a6 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -85,7 +85,9 @@ pub static TEST_INTRO3: &str = "\ // CLI args #[derive(Debug, Subcommand)] enum Commands { + #[allow(clippy::upper_case_acronyms)] TBL, + #[allow(clippy::upper_case_acronyms)] NONE, } // #[derive(Parser, Debug)] @@ -368,7 +370,7 @@ fn normalize_and_filter_args>(args: I) -> Vec { - let mut message = msg.to_string_lossy(); - message = match message.contains('\n') { - true => format!("\n{message}"),//.replace("\r\n", " "), - false => message, - }; - - match LIST_OF_SHAME.iter().any(|x| message.contains(x)) { - true => {} // filtered out - false => log::debug!(target: "kismet", "{message}"), - } - }, - Err(e) => swarn!(f; "{e}") + let msg = widestring::U16CStr::from_ptr_str(Message); + // let string = FString::from(msg.as_slice_with_nul()); + let mut message = msg.to_string_lossy(); + message = match message.contains('\n') { + true => format!("\n{message}"),//.replace("\r\n", " "), + false => message, + }; + + match LIST_OF_SHAME.iter().any(|x| message.contains(x)) { + true => {} // filtered out + false => log::debug!(target: "kismet", "{message}"), } } } diff --git a/sleuth/src/resolvers/mod.rs b/sleuth/src/resolvers/mod.rs index 2022e7c..f343ad8 100644 --- a/sleuth/src/resolvers/mod.rs +++ b/sleuth/src/resolvers/mod.rs @@ -7,6 +7,7 @@ use once_cell::sync::OnceCell; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "UPPERCASE")] +#[allow(clippy::upper_case_acronyms)] pub enum PlatformType { EGS, STEAM, diff --git a/sleuth/src/resolvers/rcon.rs b/sleuth/src/resolvers/rcon.rs index dc2a03c..54482a8 100644 --- a/sleuth/src/resolvers/rcon.rs +++ b/sleuth/src/resolvers/rcon.rs @@ -36,7 +36,7 @@ pub fn handle_rcon() { let cmd_pending = Arc::clone(&COMMAND_PENDING); thread::spawn(move || { let reader = BufReader::new(stream); - for line in reader.lines().flatten() { + for line in reader.lines().map_while(Result::ok) { if !line.trim().is_empty() { warn!("[RCON] Received: {}", line.trim()); *cmd_store.lock().unwrap() = Some(line.trim().to_string()); @@ -62,11 +62,8 @@ pub fn handle_cmd() { .expect("UTF-8 unsupported"); let cmd_store: Arc>> = Arc::clone(&LAST_COMMAND); *cmd_store.lock().unwrap() = Some(input.trim().to_string()); - match input.as_str() { - "findobj" => { - crate::sdebug!(f; "findobj {:?}", 123); - } - _ => {}, + if input.as_str() == "findobj" { + crate::sdebug!(f; "findobj {:?}", 123); } } } \ No newline at end of file diff --git a/sleuth/src/scan.rs b/sleuth/src/scan.rs index 9d5500a..5af1bc2 100644 --- a/sleuth/src/scan.rs +++ b/sleuth/src/scan.rs @@ -19,13 +19,13 @@ pub static OFFSETS: OnceCell> = OnceCell::new(); pub fn scan() -> Result, String> { - let pid = Some(process::id() as i32); + let pid = process::id() as i32; let resolvers = resolvers().collect::>(); let dyn_resolvers = resolvers.iter().map(|res| res.getter).collect::>(); // let name = format!("PID={}", pid.unwrap()); - let game_name = format!("pid={}", pid.unwrap()); // fixme + let game_name = format!("pid={pid}"); // fixme let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string())?; let resolution = tracing::info_span!("scan", game = game_name) From c87ae008e017baeeb07853e1171b2f68fdb8e889 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Tue, 10 Jun 2025 19:25:53 +0200 Subject: [PATCH 12/18] add ClientTravelInternal --- sleuth/Cargo.lock | 1 + sleuth/Cargo.toml | 2 + sleuth/src/lib.rs | 21 ++++--- sleuth/src/resolvers/admin_control.rs | 28 --------- sleuth/src/resolvers/kismet_dev.rs | 89 ++++++++++++++++++++++++++- 5 files changed, 105 insertions(+), 36 deletions(-) diff --git a/sleuth/Cargo.lock b/sleuth/Cargo.lock index 63f64b9..a6df69c 100644 --- a/sleuth/Cargo.lock +++ b/sleuth/Cargo.lock @@ -3531,6 +3531,7 @@ dependencies = [ "retour 0.3.1", "serde", "serde_json", + "serde_urlencoded", "shell-words", "simple-log", "tracing", diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 6c617c5..7a31d67 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -57,6 +57,7 @@ windows = { version = "0.52.0", features = ["Win32_Foundation", ]} rand = "0.9.1" async-std = "1.13.1" +serde_urlencoded = "0.7" # Server registration # a2s = { version = "0.5.2", optional = true } @@ -110,6 +111,7 @@ chat-log = [] mirage = [] # WIP +rpc-debug = [] object-lookup = [] server = [ diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index ab129a6..8c761c3 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -270,21 +270,28 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - // attach_GameEngineTick(base_address, offsets).unwrap(); info!("Attaching hooks:"); - let hooks_new = attach_hooks_list![[ + let mut hooks_new = attach_hooks_list![[ UGameEngineTick, ExecuteConsoleCommand, FEngineLoopInit, ClientMessage, - #[cfg(feature="demo")] - SomeRandomFunction, // StaticFindObjectSafe, #[cfg(feature="kismet-log")] KismetExecutionMessage, - #[cfg(feature="dev")] + ]]; + + // Misc hooks + #[cfg(feature="dev")] + hooks_new.extend(attach_hooks_list![[ + ClientTravelToSession, + ClientTravelInternal, + #[cfg(feature="rpc-debug")] LogReliableRPC, - #[cfg(feature="dev")] + #[cfg(feature="rpc-debug")] LogReliableRPCFailed, - ]]; + #[cfg(feature="demo")] + SomeRandomFunction, + ]]); // use crate::resolvers::macros; hooks_new.iter().for_each(|(s, f)| { @@ -531,7 +538,7 @@ pub extern "C" fn generate_json() -> u8 { #[cfg(feature="server-registration")] - { + if globals().cli_args.rcon_port.is_some() { // FIXME: Nihi: better way than checking for rcon port. Listen? let cli = globals().args(); let backend = cli.server_browser_backend.clone().unwrap(); diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 0407b72..112cb04 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -241,31 +241,3 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi } }); - -#[cfg(feature = "dev")] -define_pattern_resolver!(LogReliableRPCFailed,[ - "48 89 5C 24 08 57 48 83 EC 40 41 83 78 08 00 49 8B F8 48 8B D9 ?? ?? 49 8B 00 ?? ?? 48" - ]); - -// void __thiscall -// UNetConnection::LogReliableRPCFailed -// (UNetConnection* this, FInBunch* param_1, FString* param_2, int param_3) -#[cfg(feature = "dev")] -CREATE_HOOK!(LogReliableRPCFailed, c_void, (this_ptr: *mut c_void, arg1: *mut FString, arg2: u32), { - println!("LogReliableRPCFailed"); - // crate::sinfo![f; "Triggered!"]; -}); - - - -#[cfg(feature = "dev")] -define_pattern_resolver!(LogReliableRPC,[ - "48 89 5C 24 10 48 89 6C 24 18 48 89 74 24 20 41 56 48 83 EC 20 48 8B 01 41 8B E8 48 8B DA 4C 8B F1 ?? ?? ?? ?? ?? ?? 48 8B C8 ?? ?? ?? ?? ?? 48 8B F0 48 85 C0 ?? ?? ?? ?? ?? ?? ?? 48 8B 4E 10 48" - ]); - -//void __thiscall ATBLCharacter::LogReliableRPC(ATBLCharacter *this,FName param_1,int param_2) -#[cfg(feature = "dev")] -CREATE_HOOK!(LogReliableRPC, c_void, (this_ptr: *mut c_void, arg1: *mut FString, arg2: u32), { - println!("LogReliableRPC"); - // crate::sinfo![f; "Triggered!"]; -}); \ No newline at end of file diff --git a/sleuth/src/resolvers/kismet_dev.rs b/sleuth/src/resolvers/kismet_dev.rs index d215362..d3aa538 100644 --- a/sleuth/src/resolvers/kismet_dev.rs +++ b/sleuth/src/resolvers/kismet_dev.rs @@ -1,8 +1,10 @@ +use std::ffi::c_void; + /*KismetExecutionMessage*/ -use crate::ue::*; +use crate::{sinfo, ue::*}; static LIST_OF_SHAME: [&str; 4] = [ "/Game/Maps/Frontend/CIT/FE_Citadel_Atmospherics.FE_Citadel_Atmospherics_C", "Divide by zero: ProjectVectorOnToVector with zero Target vector", @@ -37,3 +39,88 @@ CREATE_HOOK!(KismetExecutionMessage, *mut UObject, (Message:*const u16, Type: u8 } }); + +#[cfg(feature = "rpc-debug")] +define_pattern_resolver!(LogReliableRPCFailed,[ + "48 89 5C 24 08 57 48 83 EC 40 41 83 78 08 00 49 8B F8 48 8B D9 ?? ?? 49 8B 00 ?? ?? 48" + ]); + +// FIXME: Nihi: prints are messed up +// void __thiscall +// UNetConnection::LogReliableRPCFailed +// (UNetConnection* this, FInBunch* param_1, FString* param_2, int param_3) +#[cfg(feature = "rpc-debug")] +CREATE_HOOK!(LogReliableRPCFailed, c_void, (this_ptr: *mut c_void, arg1: *mut FString, arg2: u32), { + if !arg1.is_null() { + let string_ref: &FString = unsafe{ &*arg1 }; + let message = string_ref.to_string(); + if string_ref.len() < 1024 { + println!("LogReliableRPCFailed: {}", message); + } + else { + println!("LogReliableRPCFailed"); + } + } + else { + println!("LogReliableRPCFailed"); + } + // println!("LogReliableRPCFailed"); + // crate::sinfo![f; "Triggered!"]; +}); + +#[cfg(feature = "rpc-debug")] +define_pattern_resolver!(LogReliableRPC,[ + "48 89 5C 24 10 48 89 6C 24 18 48 89 74 24 20 41 56 48 83 EC 20 48 8B 01 41 8B E8 48 8B DA 4C 8B F1 ?? ?? ?? ?? ?? ?? 48 8B C8 ?? ?? ?? ?? ?? 48 8B F0 48 85 C0 ?? ?? ?? ?? ?? ?? ?? 48 8B 4E 10 48" + ]); + +//void __thiscall ATBLCharacter::LogReliableRPC(ATBLCharacter *this,FName param_1,int param_2) +#[cfg(feature = "rpc-debug")] +CREATE_HOOK!(LogReliableRPC, c_void, (this_ptr: *mut c_void, arg1: FName, arg2: u32), { + println!("LogReliableRPC: {}", arg1); +}); + + +#[cfg(feature = "dev")] +define_pattern_resolver!(ClientTravelToSession,["48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 50 48 8B 79 30 33 ED 49 8B D8 8B F2"]); +#[cfg(feature = "dev")] +// Nihi: looks like this is never called +// bool __thiscall UGameInstance::ClientTravelToSession(UGameInstance *this,int param_1,FName param_2) +CREATE_HOOK!(ClientTravelToSession, bool, (this_ptr: *mut c_void, param: i32, Name: *mut FName), { + sinfo![F; "{}", unsafe { &*Name }]; +}); + +#[cfg(feature = "dev")] +define_pattern_resolver!(ClientTravelInternal,["4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 6B 10 41 0F B6 E9 49 89 73 18 48"]); +#[cfg(feature = "dev")] +// void __thiscall +// APlayerController::ClientTravelInternal +// (APlayerController *this,FString *param_1,ETravelType param_2,bool param_3,FGuid *param_4) +CREATE_HOOK!(ClientTravelInternal, c_void, (PC: *mut c_void, arg1: *mut FString, TravelType: u8, arg3: bool, guid: *mut c_void),{ + + #[derive(Debug, serde::Deserialize)] + #[allow(dead_code)] + struct ConnectionInfo { + Address: String, + PartyId: Option, + PartySize: Option, + PlayFabId: Option, + UnofficialTicket: Option, + } + + //127.0.0.1:7777&PartyId=1231231&PartySize=1&PlayFabId=123123&UnofficialTicket=12323 + fn parse_connection_info(raw: &str) -> Result> { + let query_str = raw.replace('?', "&"); + let query_with_address = format!("Address={}", query_str); + let info: ConnectionInfo = serde_urlencoded::from_str(&query_with_address)?; + Ok(info) + } + + let string_ref: &FString = unsafe{ &*arg1 }; + + match parse_connection_info(string_ref.to_string().as_str()) { + Ok(info) => { + sinfo!(f; "info: {:#?}", info); + } + Err(e) => eprintln!("Error: {}", e), + } +}); \ No newline at end of file From 180a68376292c2b6c8f2ca8522db45b889e45560 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Wed, 11 Jun 2025 03:07:28 +0200 Subject: [PATCH 13/18] add clienttravel hook and parsing. Disable startup message --- sleuth/Cargo.toml | 2 + sleuth/src/lib.rs | 5 + sleuth/src/resolvers/kismet_dev.rs | 257 ++++++++++++++++++++++++----- sleuth/src/resolvers/macros.rs | 17 +- src/main.cpp | 6 + 5 files changed, 246 insertions(+), 41 deletions(-) diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 7a31d67..0c0bad0 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -91,6 +91,7 @@ default = [ "rcon", "dev", "server-registration", + #"dev-joindata", ] serde-resolvers = [] image-elf = [] @@ -113,6 +114,7 @@ mirage = [] # WIP rpc-debug = [] object-lookup = [] +dev-joindata = [] server = [ # "chat-commands", diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 8c761c3..7175455 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -291,6 +291,11 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - LogReliableRPCFailed, #[cfg(feature="demo")] SomeRandomFunction, + #[cfg(feature = "dev-joindata")] + JoinData, + JoinDataTwo, + ShowSusMessage, + // JoinDataFour ]]); // use crate::resolvers::macros; diff --git a/sleuth/src/resolvers/kismet_dev.rs b/sleuth/src/resolvers/kismet_dev.rs index d3aa538..8a10bac 100644 --- a/sleuth/src/resolvers/kismet_dev.rs +++ b/sleuth/src/resolvers/kismet_dev.rs @@ -1,10 +1,11 @@ -use std::ffi::c_void; +use std::{ffi::c_void, os::raw::{c_char, c_longlong}}; + /*KismetExecutionMessage*/ -use crate::{sinfo, ue::*}; +use crate::{globals, sinfo, ue::*}; static LIST_OF_SHAME: [&str; 4] = [ "/Game/Maps/Frontend/CIT/FE_Citadel_Atmospherics.FE_Citadel_Atmospherics_C", "Divide by zero: ProjectVectorOnToVector with zero Target vector", @@ -39,6 +40,222 @@ CREATE_HOOK!(KismetExecutionMessage, *mut UObject, (Message:*const u16, Type: u8 } }); +#[cfg(feature = "dev")] +define_pattern_resolver!(ClientTravelInternal,["4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 6B 10 41 0F B6 E9 49 89 73 18 48"]); +#[cfg(feature = "dev")] +// void __thiscall +// APlayerController::ClientTravelInternal +// (APlayerController *this,FString *param_1,ETravelType param_2,bool param_3,FGuid *param_4) +CREATE_HOOK!(ClientTravelInternal, c_void, (PC: *mut c_void, arg1: *mut FString, TravelType: u8, arg3: bool, guid: *mut c_void),{ + + #[derive(Debug, serde::Deserialize)] + #[allow(dead_code)] + struct ConnectionInfo { + Address: String, + PartyId: Option, + PartySize: Option, + PlayFabId: Option, + UnofficialTicket: Option, + } + + //127.0.0.1:7777&PartyId=1231231&PartySize=1&PlayFabId=123123&UnofficialTicket=12323 + fn parse_connection_info(raw: &str) -> Result> { + let query_str = raw.replace('?', "&"); + let query_with_address = format!("Address={}", query_str); + let info: ConnectionInfo = serde_urlencoded::from_str(&query_with_address)?; + Ok(info) + } + + let string_ref: &FString = unsafe{ &*arg1 }; + + match parse_connection_info(string_ref.to_string().as_str()) { + Ok(info) => { + sinfo!(f; "info: {:#?}", info); + } + Err(e) => eprintln!("Error: {}", e), + } +}); + + +#[cfg(feature = "dev-joindata")] +use { + futures::future::join_all, + patternsleuth::{resolvers::ensure_one, scanner::Pattern}, +}; + +// 48 8d 55 97 48 8d 4d d7 e8 ?? ?? ?? ?? +// This is actually something else than I was looking for +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /Client/GetTitleData +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /Client/GetCatalogItems +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /Client/GetUserInventory +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /Authentication/GetEntityToken +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /Client/GetCatalogItems +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /Match/CancelAllMatchmakingTicketsForPlayer +// [20:09:50 INFO ] [JoinData_detour_fkt] Triggered! /MultiplayerServer/ListQosServersForTitle +#[cfg(feature = "dev-joindata")] +define_pattern_resolver!(JoinData, [ + "48 8d 55 97 48 8d 4d d7 e8 | ?? ?? ?? ??", + ], +|ctx, patterns| { + let res = futures::future::join_all(patterns.iter().map(|p| ctx.scan(patternsleuth::scanner::Pattern::new(p).unwrap()))).await; + + let temp_res = res.iter() + .flatten() + .map(|a| -> patternsleuth::resolvers::Result { Ok(patternsleuth::MemoryTrait::rip4(&ctx.image().memory, *a)?) }); + let len = temp_res.clone().count(); + let lvec: Vec> = temp_res.clone().collect(); + println!("JoinData: {:#?}", lvec); + // patternsleuth::resolvers::try_ensure_one(lvec.get(1).unwrap())? + *lvec.get(3).unwrap().as_ref().unwrap() // 2 and 3 point to the correct one +}); + +#[cfg(feature = "dev-joindata")] +CREATE_HOOK!(JoinData, *mut u8, (arg0: *mut u8, arg1: *mut FString),{ + let string_ref: &FString = unsafe{ &*arg1 }; + crate::sinfo![f; "Triggered! {string_ref}"]; +}); +// // INVALID +// #[cfg(feature = "dev")] +// define_pattern_resolver!(JoinDataTwo,{ +// OTHER: [ +// "48 89 5C 24 08 48 89 6C 24 18 48 89 74 24 20 57 48 83 EC 70 49 8B F0 48 8B DA 48 8B E9 ?? ?? ?? ?? ?? 48 8B F8 48 8D 4C 24 30 33", +// // "48 89 5C 24 08 48 89 6C 24 18 48 89 74 24 20 57 48 83 EC 70 49 8B F0 48 8B DA 48 8B E9 ? ? ? ? ? 48 8B F8 48 8D 4C 24 30 33" +// ] + +// }); +// #[cfg(feature = "dev")] +// CREATE_HOOK!(JoinDataTwo, c_void, (arg0: c_void, arg1: *mut c_void, arg3: c_void),{ +// crate::sinfo![f; "Triggered!"]; +// }); + + + +#[cfg(feature = "dev")] +define_pattern_resolver!(JoinDataTwo,{ + OTHER: [ + // "24 48 48 8B 07 48 8B CF 48 8B 15", + "4C 8B DC 48 83 EC 38 33 C0 49 89 5B 08 49 89 43 E8 49 89 43 F0 49 8D 43 E8 49 89 7B F8 48" + //"4C 8B DC 48 83 EC 38 33 C0 49 89 5B 08 49 89 43 E8 49 89 43 F0 49 8D 43 E8 49 89 7B F8 48 8B F9 48 3B C2 ? ? 48 63 5A 08 49 89 73 10 48 8B 32 89 5C 24 28 85 DB ? ? 45 33 C0 49 8D 4B E8 8B D3 ? ? ? ? ? 48 8B 4C 24 20 4C 8B C3 48 8B D6 ? ? ? ? ? 48 8B 74 24 48 48 8B 07 48 8B CF 48 8B 15 90 77" + // "48 89 5C 24 08 48 89 6C 24 18 48 89 74 24 20 57 48 83 EC 70 49 8B F0 48 8B DA 48 8B E9 ? ? ? ? ? 48 8B F8 48 8D 4C 24 30 33" + ] + +}, |ctx, patterns| { + let futures = ::patternsleuth::resolvers::futures::future::join_all( + patterns.iter() + .map(|p| ctx.scan(::patternsleuth::scanner::Pattern::new(p).unwrap())) + ).await; + + let lvec = futures.into_iter().flatten(); + let lvec2: Vec = lvec.clone().collect(); + // println!("JoinData: {:#?}", lvec); + // patternsleuth::resolvers::try_ensure_one(lvec.get(1).unwrap())? + + // let mut fns = patternsleuth::resolvers::unreal::util::root_functions(ctx, &lvec2)?; + // // crate::sinfo![f; "Funcs: {:#?}", fns]; + let mut cnt = 0; + let base_addr = globals().get_base_address(); + // fns.clone().into_iter().for_each(|x| { + // crate::sinfo![f; "{}: 0x{:#?}", cnt, x - base_addr]; + // cnt += 1; + // }); + lvec2.clone().into_iter().for_each(|x| { + crate::sinfo![f; "{}: 0x14{:X?}", cnt, x - base_addr]; + cnt += 1; + }); + // *fns.get(0).unwrap() // 2 and 3 point to the correct one + // 0x0cdabd0 + base_addr + let val: usize = 0x1b5dba0; + let offset:usize = (0x1b5dba0 & 0xFFFFFFF); + let sel_addr: usize = offset + base_addr; + + // crate::sinfo![f; "Selected: 0x{sel_addr:X?}, base 0x{base_addr:X?}, offset 0x{offset:X?} TEST: 0x{val:X?}"]; + // [21:49:25 INFO ] [resolver] Selected: 0x140699541494688, base 0x140699512799232, offset 0x28695456 TEST: 0x28695456 + let sel2 = *lvec2.get(1).unwrap(); + let sel_temp: usize = sel2 - base_addr; + crate::sinfo![f; "Selected: 0x{sel_temp:X?}"]; + // sel_addr + // sel2 + 0x1951330 + base_addr + // ::patternsleuth::resolvers::ensure_one()? +}); + +#[cfg(feature = "dev")] +// CREATE_HOOK!(JoinDataTwo, c_void, (arg0: *mut c_void, str2: *mut c_void),{ +// CREATE_HOOK!(JoinDataTwo, c_void, (param_1: *mut c_longlong, param_2: *mut *mut c_void),{ +CREATE_HOOK!(JoinDataTwo, c_void, (param_1: u64, param_2: *mut u16, param_3: *mut c_char),{ // some other + unsafe { + + // let my_string = std::ffi::CString::new("Hello world").expect("CString::new failed"); + // // let ptr: *mut c_char + // let raw = my_string.into_raw(); // takes ownership, must free later + + // *param_3 = *raw; + // unsafe { + // // Use `ptr` as needed... + // println!("ptr = {}", std::ffi::CStr::from_ptr(param_3).to_string_lossy()); + + // // When done, free the memory + // let _ = std::ffi::CString::from_raw(param_3); // Reclaim to drop it + // } + + + // let fstring = FString::from( + // widestring::U16CString::from_ptr_str(param_2) + // .as_slice_with_nul()); + // // let string_ref: &FString = unsafe{ &*arg1 }; + // // let message = string_ref.to_string(); + // println!("JoinDataTwo: {}", fstring); + + log::info!("C string: {}", std::ffi::CStr::from_ptr(param_3).to_string_lossy()); + + crate::sinfo![f; "Triggered!"]; + println!("ASDFG"); + } +}); + +// Disables startup message +// InitializeModule? +// FIXME: Nihi: offset in macro +// xref search + offset +// 11.06.25 Steam 19626AE + 0xF = 19626BD +// 19626[ae] 4c 8d 05 LEA R8,[s_Ple +// 3b 30 dd 02 +// 19626b5 48 8b cf MOV RCX,RDI +// 19626b8 48 8d 54 LEA RDX,[RSP + 0x50] +// 24 50 +// 19626[bd] {e8 6e ec} CALL FUN_141951330 +// {fe ff} +// 19626c2 48 85 db TEST RBX,RBX +// 19626c5 74 08 JZ LAB_1419626cf +#[cfg(feature = "dev")] +define_pattern_resolver!(ShowSusMessage, [ + patternsleuth::resolvers::unreal::util::utf8_pattern("Please, start the game") +], |ctx, patterns| { + let strings = ctx.scan(patterns.first().unwrap().clone()).await; + let refs: Vec = patternsleuth::resolvers::unreal::util::scan_xrefs(ctx, &strings).await; + patternsleuth::resolvers::ensure_one(refs)? + 0xF +}); + +#[cfg(feature = "dev")] +CREATE_HOOK!(ShowSusMessage, c_void, (param_1: c_longlong),{ + println!("ASDFG"); + crate::sinfo![f; "Triggered!"]; +}); + +// #[cfg(feature = "dev")] +// define_pattern_resolver!(JoinDataFour, [ +// "DE AD BE EF AA AA AA AA AA" +// ], +// |ctx, patterns| { +// // 0x1d25da0 + globals().get_base_address() +// }); + +// #[cfg(feature = "dev")] +// CREATE_HOOK!(JoinDataFour, c_void, (param_1: *mut FString),{ +// let string_ref: &FString = unsafe{ &*param_1 }; +// println!("ASDFG {:#?}", string_ref); +// crate::sinfo![f; "Triggered!"]; +// }); #[cfg(feature = "rpc-debug")] define_pattern_resolver!(LogReliableRPCFailed,[ @@ -88,39 +305,3 @@ define_pattern_resolver!(ClientTravelToSession,["48 89 5C 24 08 48 89 6C 24 10 4 CREATE_HOOK!(ClientTravelToSession, bool, (this_ptr: *mut c_void, param: i32, Name: *mut FName), { sinfo![F; "{}", unsafe { &*Name }]; }); - -#[cfg(feature = "dev")] -define_pattern_resolver!(ClientTravelInternal,["4C 8B DC 48 83 EC 58 33 C0 49 89 5B 08 49 89 6B 10 41 0F B6 E9 49 89 73 18 48"]); -#[cfg(feature = "dev")] -// void __thiscall -// APlayerController::ClientTravelInternal -// (APlayerController *this,FString *param_1,ETravelType param_2,bool param_3,FGuid *param_4) -CREATE_HOOK!(ClientTravelInternal, c_void, (PC: *mut c_void, arg1: *mut FString, TravelType: u8, arg3: bool, guid: *mut c_void),{ - - #[derive(Debug, serde::Deserialize)] - #[allow(dead_code)] - struct ConnectionInfo { - Address: String, - PartyId: Option, - PartySize: Option, - PlayFabId: Option, - UnofficialTicket: Option, - } - - //127.0.0.1:7777&PartyId=1231231&PartySize=1&PlayFabId=123123&UnofficialTicket=12323 - fn parse_connection_info(raw: &str) -> Result> { - let query_str = raw.replace('?', "&"); - let query_with_address = format!("Address={}", query_str); - let info: ConnectionInfo = serde_urlencoded::from_str(&query_with_address)?; - Ok(info) - } - - let string_ref: &FString = unsafe{ &*arg1 }; - - match parse_connection_info(string_ref.to_string().as_str()) { - Ok(info) => { - sinfo!(f; "info: {:#?}", info); - } - Err(e) => eprintln!("Error: {}", e), - } -}); \ No newline at end of file diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 0558e79..50b0602 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -161,9 +161,20 @@ macro_rules! CREATE_HOOK { type [] = unsafe extern "C" fn ($( $ty ),+ ) -> $out_type; - [] - .initialize(target, [<$name _detour_fkt>])? - .enable()?; + let res = [] + .initialize(target, [<$name _detour_fkt>]); + + match res { + Ok(_) => crate::sdebug!(f; "INIT Attached!"), + Err(e) => crate::serror!(f; "INIT FAILED TO ATTACH! {e}"), + } + let res2 = [] + .enable(); + + match res2 { + Ok(_) => crate::sdebug!(f; "ENABLE Attached!"), + Err(e) => crate::serror!(f; "ENABLE FAILED TO ATTACH! {e}"), + } // crate::debug_where!("Attached [ 0x{:#x?} ]", rel_address); $crate::sdebug!(f; "Attached [ 0x{:#x?} ]", rel_address); diff --git a/src/main.cpp b/src/main.cpp index c3f0d71..dd436ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -209,6 +209,12 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { // Dedicated server hook in ApproveLogin //Nop(module_base + g_state->GetBuildMetadata().GetOffset(strFunc[F_ApproveLogin]) + 0x46, 6); + // 19626AE + 0xF FIXME: THIS IS STEAM ONLYYY + Ptch_Nop(module_base + 0x19626bd, 5); // nop the function call which assigns str or smth + + // Ptch_Repl(module_base + 0x196260a, 0xEB); // try to replace if in eos msg + + if (!all_hooks_successful) { GLOG_ERROR("Failed to hook all functions. Unchained may not function as expected."); } From d886f867d7f661a8726ebf3fc86537d50b77cc1f Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Sat, 14 Jun 2025 02:55:10 +0200 Subject: [PATCH 14/18] (WIP) patch functions, egs crash --- sleuth/src/lib.rs | 39 ++++++++++++++++--- sleuth/src/resolvers/kismet_dev.rs | 62 +++++++++++++++++++++++------- sleuth/src/tools/logger.rs | 6 ++- sleuth/src/tools/memtools.rs | 44 +++++++++++++++++++++ sleuth/src/tools/mod.rs | 1 + src/main.cpp | 2 +- 6 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 sleuth/src/tools/memtools.rs diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 7175455..8e9ac33 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -101,10 +101,10 @@ struct CLIArgs { // #[arg()] // game_id: String, - #[arg(long = "next-map-mod-actors")] + #[arg(long = "next-map-mod-actors", value_delimiter = ',', required = false)] next_mod_actors: Option>, - #[arg(long = "all-mod-actors")] + #[arg(long = "all-mod-actors", value_delimiter = ',', required = false)] mod_paks: Option>, #[arg(long = "unchained")] @@ -273,6 +273,7 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - let mut hooks_new = attach_hooks_list![[ UGameEngineTick, ExecuteConsoleCommand, + #[cfg(feature="engine_loop")] FEngineLoopInit, ClientMessage, // StaticFindObjectSafe, @@ -294,7 +295,7 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - #[cfg(feature = "dev-joindata")] JoinData, JoinDataTwo, - ShowSusMessage, + // ShowSusMessage, // JoinDataFour ]]); @@ -441,6 +442,7 @@ unsafe fn init_globals() -> Result<(), clap::error::Error>{ cli_args: args, platform }); + sinfo!(f; "{GLOBALS:#?}"); Ok(()) } @@ -485,7 +487,7 @@ fn intro() { // }} // } // define_pocess! - +use tools::memtools::*; #[no_mangle] pub extern "C" fn generate_json() -> u8 { // intro(); @@ -532,10 +534,35 @@ pub extern "C" fn generate_json() -> u8 { let len_u8 = offsets.len() as u8; // FIXME: Nihi: ? unsafe { - attach_hooks(exe.base_address, offsets).unwrap(); + attach_hooks(exe.base_address, offsets.clone()).unwrap(); } dump_builds().expect("Failed to dump builds JSON"); + sinfo!("trying to patch"); + // 19626AE + 0xF = 19626BD + match offsets.get("ShowSusMessage") { + Some(a) => { //FIXME: Nihi: <- usize or u64 or *mut u8 + + // let addr = exe.base_address + 0x19626BD; // *a as usize;// + 0xF; + let extra = match globals().get_platform() { + PlatformType::EGS => 0xE, + PlatformType::STEAM => 0xF, + PlatformType::XBOX => todo!(), + PlatformType::OTHER => todo!(), + }; + swarn!(f; "Offset: 0x{:X?}", extra); + let addr = exe.base_address + *a as usize + extra;// + 0xF; + swarn!(f; "sus: {:X?} {:X?}", a-exe.base_address as u64, a, ); + swarn!(f; "an addr: {:X?}", *a as usize ); + swarn!(f; "a base: {:X?}", *a as usize + extra); + unsafe { nop(addr as *mut u8, 5) }; + // unsafe { nop(addr as *mut u8, 5) }; + swarn!(f; "Patched the function {:X?} {:X?}", addr, addr - exe.base_address); + swarn!(f; "Patchessthe function {:X?} {:X?}", a, a - exe.base_address as u64); + }, + None => serror!(f; "No address to patch"), + } + #[cfg(feature="cli-commands")] std::thread::spawn(|| { resolvers::rcon::handle_cmd(); @@ -603,7 +630,7 @@ impl<'de> Deserialize<'de> for DllHookResolution { // used by ue.rs static mut GLOBALS: Option = None; - +#[derive(Debug)] pub struct Globals { resolution: DllHookResolution, guobject_array: parking_lot::FairMutex<&'static ue::FUObjectArray>, diff --git a/sleuth/src/resolvers/kismet_dev.rs b/sleuth/src/resolvers/kismet_dev.rs index 8a10bac..594c5e1 100644 --- a/sleuth/src/resolvers/kismet_dev.rs +++ b/sleuth/src/resolvers/kismet_dev.rs @@ -5,7 +5,7 @@ use std::{ffi::c_void, os::raw::{c_char, c_longlong}}; /*KismetExecutionMessage*/ -use crate::{globals, sinfo, ue::*}; +use crate::{globals, resolvers::macros::First_signature, sinfo, ue::*}; static LIST_OF_SHAME: [&str; 4] = [ "/Game/Maps/Frontend/CIT/FE_Citadel_Atmospherics.FE_Citadel_Atmospherics_C", "Divide by zero: ProjectVectorOnToVector with zero Target vector", @@ -227,20 +227,56 @@ CREATE_HOOK!(JoinDataTwo, c_void, (param_1: u64, param_2: *mut u16, param_3: *mu // {fe ff} // 19626c2 48 85 db TEST RBX,RBX // 19626c5 74 08 JZ LAB_1419626cf +// 14.06.2025 EGS +// 14193dcd4 4c 8d 05 LEA R8,[s_Please,_start_the_game_via_Chivalry2Laun = "Please, start the game via Ch +// b5 5c e0 02 +// 14193dcdb 48 8b cf MOV RCX,RDI +// 14193dcde 48 8d 55 40 LEA RDX,[RBP + 0x40] +// 14193dce2 e8 59 dc CALL FUN_14192b940 undefined FUN_14192b940() +// fe ff +// 14193dce7 48 85 db TEST RBX,RBX +// 14193dcea 74 08 JZ LAB_14193dcf4 + +// 4c 8d 05 +// ?? ?? ?? ?? +// 48 8b cf +// 48 ?? ?? +// ?? ?? +// ?? ?? ?? +// ?? ?? +// 48 85 db +// 74 08 + + +// 4c 8d 05 +// 3b 30 dd 02 +// 48 8b cf +// 48 8d 54 +// 24 50 +// e8 6e ec +// fe ff +// 48 85 db +// 74 08 #[cfg(feature = "dev")] -define_pattern_resolver!(ShowSusMessage, [ - patternsleuth::resolvers::unreal::util::utf8_pattern("Please, start the game") -], |ctx, patterns| { - let strings = ctx.scan(patterns.first().unwrap().clone()).await; - let refs: Vec = patternsleuth::resolvers::unreal::util::scan_xrefs(ctx, &strings).await; - patternsleuth::resolvers::ensure_one(refs)? + 0xF -}); +define_pattern_resolver!(ShowSusMessage, Simple, { + EGS: ["4c 8d 05 b5 5c e0 02 48 8b cf 48 8d 55 40 e8 59 dc fe ff 48 85 db 74 08"], + STEAM: ["4c 8d 05 3b 30 dd 02 48 8b cf 48 8d 54 24 50 e8 6e ec fe ff 48 85 db 74 08"] + // patternsleuth::resolvers::unreal::util::utf8_pattern("Please, start the game") +} +// , |ctx, patterns| { +// let sig = First_signature(patterns.first().unwrap().clone()); +// // let strings = ctx.scan(patterns.first().unwrap().clone()).await; +// // let refs: Vec = patternsleuth::resolvers::unreal::util::scan_xrefs(ctx, &strings).await; +// // match globals().get_platform() { +// // } +// } +); -#[cfg(feature = "dev")] -CREATE_HOOK!(ShowSusMessage, c_void, (param_1: c_longlong),{ - println!("ASDFG"); - crate::sinfo![f; "Triggered!"]; -}); +// #[cfg(feature = "dev")] +// CREATE_HOOK!(ShowSusMessage, c_void, (param_1: c_longlong),{ +// println!("ASDFG"); +// crate::sinfo![f; "Triggered!"]; +// }); // #[cfg(feature = "dev")] // define_pattern_resolver!(JoinDataFour, [ diff --git a/sleuth/src/tools/logger.rs b/sleuth/src/tools/logger.rs index 67073b3..bacaa87 100644 --- a/sleuth/src/tools/logger.rs +++ b/sleuth/src/tools/logger.rs @@ -55,10 +55,12 @@ pub fn init_syslog() -> anyhow::Result<()> { // .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)}] {P} [{l:6}] [{t}] {m}{n}"))) .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)} {P} {l:6}| {t:10}] {m}{n}"))) // .build("my_log_file.log")?; - .build(r"U:\Unchained\UnchainedSleuth\unchained.log")?; // FIXME: Nihi: LOCAL FILE + // .build(r"U:\Unchained\UnchainedSleuth\unchained.log")?; // FIXME: Nihi: LOCAL FILE + .build(r"unchained.log")?; // FIXME: Nihi: LOCAL FILE let kismet = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("[{d(%Y-%m-%d %H:%M:%S)} {P} {l:6}| {t} ] {m}{n}"))) - .build(r"U:\Unchained\UnchainedSleuth\kismet.log")?; + // .build(r"U:\Unchained\UnchainedSleuth\kismet.log")?; + .build(r"kismet.log")?; let console_filter = ThresholdFilter::new(log::LevelFilter::Info); // let console_filter: MetaDataFilter = MetaDataFilter::new(log::LevelFilter::Info); diff --git a/sleuth/src/tools/memtools.rs b/sleuth/src/tools/memtools.rs new file mode 100644 index 0000000..3a9ddb7 --- /dev/null +++ b/sleuth/src/tools/memtools.rs @@ -0,0 +1,44 @@ +use std::{ptr, slice}; +use windows::Win32::System::Memory::{ + VirtualProtect, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, +}; + +/// Change memory protection for a given region. +unsafe fn protect(addr: *mut u8, size: usize, new_protect: PAGE_PROTECTION_FLAGS) -> PAGE_PROTECTION_FLAGS { + let mut old: PAGE_PROTECTION_FLAGS = PAGE_PROTECTION_FLAGS(0); + let _ = VirtualProtect(addr as _, size, new_protect, &mut old); + old +} + +/// Overwrite memory with custom bytes. +pub unsafe fn patch(addr: *mut u8, data: &[u8]) { + let size = data.len(); + let old = protect(addr, size, PAGE_EXECUTE_READWRITE); + ptr::copy_nonoverlapping(data.as_ptr(), addr, size); + protect(addr, size, old); +} + +/// Fill memory with NOPs (0x90). +pub unsafe fn nop(addr: *mut u8, count: usize) { + let old = protect(addr, count, PAGE_EXECUTE_READWRITE); + ptr::write_bytes(addr, 0x90, count); + protect(addr, count, old); +} + +/// Read memory at address into a Vec. +pub unsafe fn read_memory(addr: *const u8, size: usize) -> Vec { + slice::from_raw_parts(addr, size).to_vec() +} + +/// Overwrite a pointer-sized value. +pub unsafe fn write_ptr(addr: *mut T, value: T) { + let size = std::mem::size_of::(); + let old = protect(addr as *mut u8, size, PAGE_EXECUTE_READWRITE); + ptr::write(addr, value); + protect(addr as *mut u8, size, old); +} + +/// Read a value from memory. +pub unsafe fn read_ptr(addr: *const T) -> T { + ptr::read(addr) +} diff --git a/sleuth/src/tools/mod.rs b/sleuth/src/tools/mod.rs index 248e3fb..ffbc814 100644 --- a/sleuth/src/tools/mod.rs +++ b/sleuth/src/tools/mod.rs @@ -1,6 +1,7 @@ pub mod syslog; pub mod logger; +pub mod memtools; #[macro_use] // pub mod log_macros_squashed; // pub mod log_macros_kv; diff --git a/src/main.cpp b/src/main.cpp index dd436ad..90988ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -210,7 +210,7 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { //Nop(module_base + g_state->GetBuildMetadata().GetOffset(strFunc[F_ApproveLogin]) + 0x46, 6); // 19626AE + 0xF FIXME: THIS IS STEAM ONLYYY - Ptch_Nop(module_base + 0x19626bd, 5); // nop the function call which assigns str or smth + // Ptch_Nop(module_base + 0x19626bd, 5); // nop the function call which assigns str or smth // Ptch_Repl(module_base + 0x196260a, 0xEB); // try to replace if in eos msg From 95253bcd7308c7a5bb62504fdcaeb8227e321929 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Mon, 23 Jun 2025 21:19:53 +0200 Subject: [PATCH 15/18] (WIP) rewrite registration --- sleuth/src/tools/mod.rs | 3 +- sleuth/src/tools/registration.rs | 361 +++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 sleuth/src/tools/registration.rs diff --git a/sleuth/src/tools/mod.rs b/sleuth/src/tools/mod.rs index ffbc814..8902e48 100644 --- a/sleuth/src/tools/mod.rs +++ b/sleuth/src/tools/mod.rs @@ -8,4 +8,5 @@ pub mod memtools; pub mod log_macros; #[cfg(feature="server-registration")] -pub mod server_registration; \ No newline at end of file +pub mod registration; +// pub mod server_registration; \ No newline at end of file diff --git a/sleuth/src/tools/registration.rs b/sleuth/src/tools/registration.rs new file mode 100644 index 0000000..0c01190 --- /dev/null +++ b/sleuth/src/tools/registration.rs @@ -0,0 +1,361 @@ +use std::sync::{Arc, Condvar, Mutex}; +use std::thread; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use a2s::A2SClient; +// use anyhow::Ok; +// use anyhow::Ok; +// use once_cell::sync::Lazy; +use reqwest::blocking::Client; +use serde::{Deserialize, Serialize}; + +use crate::globals; + +// Register Request +#[derive(Debug, Serialize)] +struct RegisterRequest<'a> { + ports: Ports, + name: &'a str, + description: &'a str, + password_protected: bool, + current_map: &'a str, + player_count: i32, + max_players: i32, + local_ip_address: &'a str, + mods: &'a [ModInfo<'a>], +} + +#[derive(Debug, Serialize)] +struct ModInfo<'a> { + name: &'a str, + version: &'a str, +} + +#[derive(Debug, Serialize)] +struct Ports { + game: u16, + ping: u16, + a2s: u16, +} + +// TODO: Maybe use Identity instead +#[derive(Debug, Deserialize, Clone)] +struct RegisterResponse { + server: RegisteredServer, + key: String, + refresh_before: f64, +} + +impl RegisterResponse { + fn refresh_eta_secs(&self) -> u64 { + let start = SystemTime::now(); + let since_epoch = start.duration_since(UNIX_EPOCH).expect("Time went backwards"); + sinfo!(f; "ETA: {}", self.refresh_before as u64 - since_epoch.as_secs()); + self.refresh_before as u64 - since_epoch.as_secs() + } +} + +#[derive(Debug, Deserialize, Clone)] +struct RegisteredServer { + unique_id: String, +} + +// Server id/key +struct Identity { + id: String, + key: String, + refresh_period: u64, +} + +impl From for Identity { + fn from(res: RegisterResponse) -> Self { + Identity { + id: res.server.unique_id.clone(), + key: res.key.clone(), + refresh_period: res.refresh_eta_secs() + } + } +} + +impl Identity { + fn default() -> Self { + Self { + id: String::new(), + key: String::new(), + refresh_period: 60, + } + } + + pub fn new(id: impl Into, key: impl Into) -> Self { + Self { + id: id.into(), + key: key.into(), + refresh_period: 60, + } + } +} + +#[derive(Debug, Serialize)] +struct UpdatePayload<'a> { + player_count: u8, + max_players: u8, + map_name: &'a str, +} + +pub struct Registration{ + server_addr: String, + client: Client, + a2s: A2SClient, + identity: Mutex>, + stop_update: Arc<(Mutex, Condvar)>, + stop_heartbeat: Arc<(Mutex, Condvar)>, + heartbeat_thread: Mutex>>>>, +} +impl Registration { + pub fn new(ip: &str, query_port: u16) -> Self { + Self { + server_addr: format!("{ip}:{query_port}"), + // query_port, + client: Client::new(), + a2s: A2SClient::new().unwrap(), + identity: Mutex::new(None), + stop_update: Arc::new((Mutex::new(false), Condvar::new())), + stop_heartbeat: Arc::new((Mutex::new(false), Condvar::new())), + heartbeat_thread: Mutex::new(None), + // last_info: None + } + } + + fn register_server(self: Arc, info: a2s::info::Info) -> Result { + let args = &globals().cli_args; + let desc = format!("{} (build {})\n{} server ", + info.game, + info.version, + info.folder); + let request = RegisterRequest{ + ports: Ports { + game: info.extended_server_info.port.unwrap(), + ping: args.game_server_ping_port.unwrap(), + a2s: args.game_server_query_port.unwrap(), + }, + name: &info.name, + description: &desc, + password_protected: args.server_password.is_some(), // TODO + current_map: &info.map, + player_count: info.players as i32, + max_players: info.max_players as i32, + local_ip_address: "127.0.0.1", + mods: &[], + }; + let backend = args.server_browser_backend.clone(); + let response = self.client + .post(format!("{}/api/v1/servers", &backend.unwrap())) + .json(&request) + .send().unwrap(); + + swarn!(f; "request {:#?}", request); + swarn!(f; "response {:#?}", response); + match response.json() as Result { + Ok(res) => { + swarn!(f; "json {:#?}", res); + + let mut identity_lock = self.identity.lock().unwrap(); + // *identity_lock = Some(Identity::new(&res.server.unique_id, &res.key)); + *identity_lock = Some(res.clone().into()); + Ok(res) + }, + Err(e) => { serror!(f; "Error: {}", e); Err(e) }, + } + } + + fn a2s_get_info(&self, retries: usize, period_s: f32) -> Result> { + let stop_flag = self.stop_update.clone(); + let mut itr = 0; + while itr < retries { + let (lock, cvar) = &*stop_flag; + if *lock.lock().unwrap() { + break; + } + sinfo!(f; "Retry {itr}"); + match self.a2s.info(&self.server_addr) { + Result::Ok(info) => { + serror!(f; "Connected {:#?}", info); + // serror!(f; "Players {:#?}", self.a2s.players(&self.server_addr)); // ! + // serror!(f; "Rules {:#?}", self.a2s.rules(&self.server_addr)); + return Ok(info) + }, + Err(e) => serror!(f; "Failed to get info: {e}"), + } + itr += 1; + if itr < retries { // TODO: do..while? + thread::sleep(Duration::from_secs(1)); + } + } + Err(format!("A2S failed after {retries} retries.").into()) + } + + fn start_discovery(self: Arc) -> Result> { + + match self.a2s_get_info(50, 1.0) { + Ok(info) => { + sinfo!(f; "discovered server!"); + match self.register_server(info) { + Ok(rinfo) => { + sinfo!(f; "ok"); + Ok(rinfo) + }, // TODO: handle player list? + Err(e) => { + serror!(f; "Not ok :{e}"); + Err(e.into()) + }, + } + }, + Err(e) => { + serror!(f; "Error: {}", e); + Err(format!("Error: {}", e).into()) + }, + } + // Err("Reached max retries".into()) + } + + pub fn start_a2s(self: Arc) { + let reg = self.clone(); + + thread::spawn(move || { + // reg.start_a2s(); + }); + } + + pub fn start(self: Arc) { + // self.clone().start_heartbeat(); + // self.clone().start_a2s(); + let reg = self.clone(); + + // spawn discovery thread + thread::spawn(move || { + thread::sleep(Duration::from_secs(20)); + match reg.start_discovery() { + Ok(_) => { + println!("Discovery successful, starting heartbeat..."); + match self.start_heartbeat() { + Ok(_) => sinfo!(f; "Heartbeat started"), + Err(e) => serror!(f; "Error: {e}"), + } + // Register server + // start heartbeat + // reg.start_heartbeat(); + } + Err(e) => { + eprintln!("Discovery failed: {:?}", e); + } + } + }); + } + + pub fn stop(self: Arc) { + + } + + fn send_update(self: Arc) -> Result<(), Box>{ + sinfo!(f; "start"); + let client = self.client.clone(); + // let url = self.server_addr.clone(); + let args = &globals().cli_args; + let backend = args.server_browser_backend.clone().unwrap(); + sinfo!(f; "getting lock"); + let identity_lock: std::sync::MutexGuard<'_, Option> = self.identity.lock().unwrap(); + sinfo!(f; "getting ident"); + let ident = identity_lock.as_ref() + .ok_or("Heartbeat with unknown identity")?; + sinfo!(f; "getting info"); + match self.a2s_get_info(1, 0.0) { + Ok(info) => { + sinfo!(f; "got a2s info"); + let payload = UpdatePayload { + player_count: info.players, + max_players: info.max_players, + map_name: info.map.as_str(), + }; + + let res = client.post(format!("{}/update", backend)) + .json(&payload) + .send(); + + match res { + Ok(update) => { + sinfo!(f; "sent update payload {:#?}", update); + // sinfo!("Sending update!"); + let res_hb = client + .post(format!("{}/{}/heartbeat", backend, ident.id)) + .header("x-chiv2-server-browser-key", ident.key.as_str()) + .send(); + + match res_hb { + Ok(hb) => { + sinfo!("Sent heartbeat!"); + Ok(()) + }, + Err(e) => Err(format!("Update: failed to send heartbeat: {}", e).into()), + } + }, + Err(e) => Err(format!("Update: failed to send update: {}", e).into()), + } + + + + // FIXME: why not derived here? + // Ok::<(), Box>(()) + } + + Err(e) => Err(format!("Update: failed to get A2S: {}", e).into()), + } + } + + pub fn start_heartbeat(self: Arc) -> Result<(), Box> { + sinfo!(f; "start"); + let stop_signal = Arc::clone(&self.stop_heartbeat); + let this = Arc::clone(&self); + // let identity = self.identity.lock().unwrap(); + // match self.identity.lock().unwrap().as_ref() { + // Some(ident) => todo!(), + // None => todo!(), + // }; + let handle = thread::spawn(move || -> Result<(), Box> { + let (lock, cvar) = &*stop_signal; + loop { + let mut stopped = lock.lock().unwrap(); + + if *stopped { + println!("🛑 Heartbeat stopping."); + break; + } + + + match this.clone().send_update() { + Ok(_) => sinfo!(f; "upadte success"), + Err(e) => sinfo!(f; "upadte error: {}", e), + }; + + let identity_lock = this.identity.lock().unwrap(); + let ident = identity_lock.as_ref() + .ok_or("Heartbeat with unknown identity")?; + + let result = cvar.wait_timeout(stopped, Duration::from_secs(ident.refresh_period)).unwrap(); + stopped = result.0; + // if *stopped { + // println!("🛑 Heartbeat stopping."); + // break; + // } + } + Ok(()) + }); + + *self.heartbeat_thread.lock().unwrap() = Some(handle); + Ok(()) + } + + pub fn stop_heartbeat(self: Arc) { + + } + +} \ No newline at end of file From 7c7ab3e8093a4139be6da71e703939009cd64321 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Mon, 23 Jun 2025 21:20:12 +0200 Subject: [PATCH 16/18] Extend macros to support execution before hook --- sleuth/src/resolvers/macros.rs | 76 ++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/sleuth/src/resolvers/macros.rs b/sleuth/src/resolvers/macros.rs index 50b0602..2a1d9ea 100644 --- a/sleuth/src/resolvers/macros.rs +++ b/sleuth/src/resolvers/macros.rs @@ -125,24 +125,84 @@ macro_rules! CREATE_HOOK { // [ $( $call_type ($pattern) ),+ ]; // }; - ($name:ident, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:block) => { + // No ret type specified + ($name:ident, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:expr) => { CREATE_HOOK!($name, ::std::ffi::c_void, ( $( $arg: $ty ),+ ), $body); }; - ($name:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:block) => { + // No hook type specified - run before original by default + ($name:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:expr) => { + CREATE_HOOK![$name, PRE, $out_type, ( $( $arg: $ty ),+ ), $body]; + }; + + + (@gen_detour NONE, $name:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:expr) => { + + paste::paste! { + #[allow(non_snake_case)] + pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) -> $out_type { + // println!("rust $name delta: {}", delta); + $body + // unsafe { [].call ( $( $arg ),+ ) } + } + } + }; + + (@gen_detour POST, $name:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:expr) => { + + paste::paste! { + #[allow(non_snake_case)] + pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) -> $out_type { + // println!("rust $name delta: {}", delta); + let ret_val = unsafe { [].call ( $( $arg ),+ ) }; + // let body_val = $body; + // let mut fallback = None; + + // let _ = match body_val { + // () => fallback = Some(ret_val), + // val => fallback = Some(body_val), + // }; + + // fallback.unwrap() + let ret_val = ret_val; + ($body(ret_val)) + } + } + }; + + (@gen_detour PRE, $name:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:expr) => { + + paste::paste! { + #[allow(non_snake_case)] + pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) -> $out_type { + $body + unsafe { [].call ( $( $arg ),+ ) } + } + } + }; + + ($name:ident, $hook_type:ident, $out_type:ty, ( $( $arg:ident: $ty:ty ),+ $(,)? ), $body:expr) => { paste::paste! { ::retour::static_detour! { pub static []: unsafe extern "C" fn ($( $ty ),+ ) -> $out_type; } + + CREATE_HOOK![@gen_detour $hook_type, $name, $out_type, ( $( $arg: $ty ),+ ), $body]; - #[allow(non_snake_case)] - pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) -> $out_type { - // println!("rust $name delta: {}", delta); - $body - unsafe { [].call ( $( $arg ),+ ) } - } + // #[allow(non_snake_case)] + // pub fn [<$name _detour_fkt>]( $( $arg: $ty ),+ ) -> $out_type { + // // println!("rust $name delta: {}", delta); + + // let someValue = $body; + // if (someValue != ()) { + // crate::serror!("SomeValue: {someValue:?}"); + // } + // unsafe { [].call ( $( $arg ),+ ) } + // // $body + // // unsafe { [].call ( $( $arg ),+ ) } + // } #[allow(non_snake_case)] pub unsafe fn [](base_address: usize, offsets: std::collections::HashMap) -> Result, Box>{ From 447e4d70c3fc64bccec788d8fcbe457b4e906f88 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Mon, 23 Jun 2025 21:21:17 +0200 Subject: [PATCH 17/18] fix staticobjectsafe, add matchupdate, console feed --- sleuth/src/resolvers/admin_control.rs | 186 +++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 3 deletions(-) diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 112cb04..34aa5b6 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use std::os::raw::c_void; use crate::resolvers::rcon::LAST_COMMAND; use crate::chiv2::*; +use crate::serror; use crate::ue::*; define_pattern_resolver![UTBLLocalPlayer_Exec, { @@ -43,7 +44,7 @@ CREATE_HOOK!(UGameEngineTick, (engine:*mut c_void, delta:f32, state:u8), { } if fstring.len() > 1 { - warn!("Execuing Command: {fstring}"); + warn!("Executing Command: {fstring}"); *lock.lock().unwrap() = None; unsafe { o_ExecuteConsoleCommand.call(&mut fstring); } } @@ -100,12 +101,31 @@ define_pattern_resolver!(StaticFindObjectSafe, [ // UObject * __cdecl StaticFindObjectSafe(UClass.conflict *param_1,UObject *param_2,wchar_t *param_3,bool param_4) // UObject* StaticFindObjectSafe( UClass* ObjectClass, UObject* ObjectParent, const TCHAR* InName, bool bExactClass ) #[cfg(feature="object-lookup")] -CREATE_HOOK!(StaticFindObjectSafe, *mut UObject, (ObjectClass:*mut UClass, ObjectParent:*mut UObject, InName:*mut FString, bExactClass: bool), { +CREATE_HOOK!(StaticFindObjectSafe, *mut UObject, (ObjectClass:*mut UClass, ObjectParent:*mut UObject, InName:*mut u16, bExactClass: bool), { if !InName.is_null() { unsafe { let reference = &*InName; // Now you can use `reference` safely - // crate::sdebug!(f; "{:?}", reference); + let mut class_str = "".to_string(); + let mut parent_str = "".to_string(); + // FIXME: Oh god + if !ObjectClass.is_null() { + // crate::sinfo!(f; "ObjectClass {}", (&*ObjectClass).ustruct.ufield.uobject.uobject_base_utility.uobject_base.name_private); + class_str = (&*ObjectClass).ustruct.ufield.uobject.uobject_base_utility.uobject_base.name_private.to_string(); + } + if !ObjectParent.is_null() { + // crate::sinfo!(f; "ObjectParent {}", (&*ObjectParent).uobject_base_utility.uobject_base.name_private); + parent_str = (&*ObjectParent).uobject_base_utility.uobject_base.name_private.to_string(); + } + + crate::sinfo!(f; "Exact? {} '{}' '{}' '{}'", + // (&*ObjectClass).ustruct.ufield.uobject.uobject_base_utility.uobject_base.name_private, + // (&*ObjectParent).uobject_base_utility.uobject_base.name_private, + bExactClass, + class_str, + parent_str, + widestring::U16CString::from_ptr_str(InName).display(), + ); } } }); @@ -241,3 +261,163 @@ CREATE_HOOK!(ClientMessage, (this:*mut c_void, S:*mut FString, Type:FName, MsgLi } }); +// Match state change +// void __thiscall UTBLMatchesSubsystem::MatchUpdate(UTBLMatchesSubsystem *this) +#[cfg(feature = "dev")] +define_pattern_resolver!(MatchUpdate,[ + "48 89 5C 24 08 57 48 83 EC 20 48 8B D9 ?? ?? ?? ?? ?? 48 8B F8 48 85 C0 ?? ?? ?? ?? ?? ?? 48 8D 54 24 38 48 8B C8 ?? ?? ?? ?? ?? 48 8B", + "48 89 5C 24 ?? 57 48 83 EC 20 48 8B D9 E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 0F 84 ?? ?? ?? ??" + ]); + +#[cfg(feature = "dev")] +CREATE_HOOK!(MatchUpdate,(arg0: *mut u8),{ + crate::sinfo![f; "Triggered!"]; +}); + +// // float __thiscall ATBLGameState::GetStageTimeRemaining(ATBLGameState *this) +#[cfg(feature = "dev")] +define_pattern_resolver![GetStageTimeRemaining,[ + "40 53 48 83 EC 40 48 8B 01 48 8B D9 0F 29 7C 24 ?? 0F 57 FF FF 90 ?? ?? ?? ?? 48 85 C0 74 ?? 48 8B 03 48 8B CB 0F 29 74 24 ?? F3 0F 10 B3 ?? ?? ?? ?? FF 90 ?? ?? ?? ?? F3 0F 5C F0 F3 0F 5F F7 0F 28 C6 0F 28 74 24 ?? 0F 28 7C 24 ?? 48 83 C4 40 5B C3 0F 28 7C 24 ?? 0F 57 C0 48 83 C4 40 5B C3" + ] +,|ctx, patterns| { + let futures = ::patternsleuth::resolvers::futures::future::join_all( + patterns.iter() + .map(|p| ctx.scan(::patternsleuth::scanner::Pattern::new(p).unwrap())) + ).await; + + // FIXME (2 of 3) + futures.into_iter().flatten().collect::>()[1] +} +]; + +#[cfg(feature = "dev")] +// CREATE_HOOK!(GetStageTimeRemaining, f32, (arg0: *mut u8), { +// // serror!("RETVAL: {}", ret_val); +// // crate::sinfo![f; "Triggered!"]; +// // ret_val +// }); +// CREATE_HOOK!(GetStageTimeRemaining, POST, f32, (arg0: *mut u8), |ret_val| { +// serror!("RETVAL: {}", ret_val); +// crate::sinfo![f; "Triggered!"]; +// ret_val +// }); + +// void __thiscall AGameMode::SetMatchState(AGameMode *this,FName param_1) +#[cfg(feature = "dev")] +define_pattern_resolver!(SetMatchState,[ + "48 89 5C 24 ?? 56 48 83 EC 20 48 8B DA 48 8B F1 48 39 91 ?? ?? ?? ??" + ]); +#[cfg(feature = "dev")] +CREATE_HOOK!(SetMatchState,(this_ptr: *mut u8, new_state: FName),{ + info![target: "server", "Changed! {}", new_state]; + // unsafe { + // let time_rem = o_GetStageTimeRemaining.call(this_ptr); + // if time_rem != 0.0 { + // crate::sinfo!(f; "Time: {}", time_rem); + // } + // } +}); + +enum UELogType { + NoLogging = 0, + Fatal = 1, + Error = 2, + Warning = 3, + Display = 4, + Log = 5, + Verbose = 6, + All = 7, + // VeryVerbose = 7, + NumVerbosity = 8, + VerbosityMask = 15, + SetColor = 64, + BreakOnLog = 128, +} + +// void __thiscall FOutputDevice::LogfImpl(FOutputDevice *this,Type param_1,wchar_t *param_2) +// longlong * FUN_14352dec0(longlong *param_1,longlong *param_2,short **param_3,char param_4) +#[cfg(feature = "dev")] +define_pattern_resolver!(LogFImpl,[ + // "4C 89 44 24 ?? 4C 89 4C 24 ?? 53 55 56 57 41 54 41 55 41 56 41 57 48 81 EC 58 04 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F8", + "44 88 4C 24 ?? 48 89 54 24 ?? 55 53 56 57 41 54 41 55" + ]); +#[cfg(feature = "dev")] +// CREATE_HOOK!(LogFImpl,(this_ptr: *mut c_void, Type: u8, Text: *mut u8),{ +CREATE_HOOK!(LogFImpl, POST, *mut i64, ( + param_1: *mut i64, + param_2: *mut i64, + param_3: *mut *mut i16, + param_4: i8,), |ret_val: *mut i64| { +// CREATE_HOOK!(LogFImpl,(this_ptr: *mut c_void, Type: u8, Text: *const u16),{ + + // crate::sinfo![f; "TEXT"]; + unsafe { + if param_3.is_null() || (*param_3).is_null() { + // Handle null pointer safely + // return std::ptr::null_mut(); + } else { + + let raw_u16_ptr: *const u16 = *param_3 as *const u16; + let u16_cstr = widestring::U16CStr::from_ptr_str(raw_u16_ptr); + if !u16_cstr.to_string_lossy().contains("RCON_INTERCEPT") { + crate::sinfo![f; "{}", u16_cstr.display()]; + } + } + + } + + // if !Text.is_null() { + // unsafe { + // let msg = widestring::U16CStr::from_ptr_str(Text); + // // let string = FString::from(msg.as_slice_with_nul()); + // let mut message = msg.to_string_lossy(); + // message = match message.contains('\n') { + // true => format!("\n{message}"),//.replace("\r\n", " "), + // false => message, + // }; + // crate::sinfo![f; "{}", message]; + + // } + // } + ret_val +}); + +// void __thiscall FOutputDevice::LogfImpl(FOutputDevice *this,wchar_t *param_1) +#[cfg(feature = "dev")] +define_pattern_resolver!(LogFImpl2,[ + "48 89 54 24 ?? 4C 89 44 24 ?? 4C 89 4C 24 ?? 53 55 56 57 41 54 41 55" + ]); +#[cfg(feature = "dev")] +// CREATE_HOOK!(LogFImpl2, POST, *mut u16, (this_ptr: *mut c_void, Text: *const u16), |ret_val: *mut u16|{ +CREATE_HOOK!(LogFImpl2, POST, *mut u16, ( + param_1: *mut i64, + param_2: *mut i64, + param_3: *mut *mut i16, + param_4: i8,), |ret_val: *mut u16|{ +// CREATE_HOOK!(LogFImpl2,(this_ptr: *mut c_void, Text: *const u16),{ + + crate::sinfo![f; "TEXT2"]; + // if !ret_val.is_null() { + // unsafe { + // let msg = widestring::U16CStr::from_ptr_str(ret_val.clone()); + // // let string = FString::from(msg.as_slice_with_nul()); + // let mut message = msg.to_string_lossy(); + // message = match message.contains('\n') { + // true => format!("\n{message}"),//.replace("\r\n", " "), + // false => message, + // }; + // crate::sinfo![f; "{}", message]; + + // } + // } + ret_val +}); + +// never triggers +// void __thiscall FOutputDevice::Log(FOutputDevice *this,wchar_t *param_1) +#[cfg(feature = "dev")] +define_pattern_resolver!(Log2,["48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 E8 ?? ?? ?? ?? 4C 8B 17"]); +#[cfg(feature = "dev")] +CREATE_HOOK!(Log2, (this_ptr: *mut c_void, Text: *mut *mut i16),{ + crate::sinfo![f; "Triggered!"]; +}); \ No newline at end of file From d86cc161434da33407a07a750324c8dff38b7627 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Mon, 23 Jun 2025 21:22:21 +0200 Subject: [PATCH 18/18] change window title based on profile. Filter out unused cli args. --- sleuth/Cargo.toml | 5 +- sleuth/src/lib.rs | 144 ++++++++++++++++++++++++++++++++-------------- src/main.cpp | 3 +- 3 files changed, 107 insertions(+), 45 deletions(-) diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 0c0bad0..8584e24 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -86,12 +86,13 @@ default = [ "chat-log", "chiv2", "mirage", - "demo", - "syslog-client", + #"demo", + #"syslog-client", "rcon", "dev", "server-registration", #"dev-joindata", + "object-lookup", ] serde-resolvers = [] image-elf = [] diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index 8e9ac33..78e404e 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -2,6 +2,7 @@ mod resolvers; mod scan; +use std::sync::Arc; use std::time::Duration; use std::{env, thread}; use std::fs::File; @@ -24,6 +25,7 @@ use patternsleuth::resolvers::unreal::{fname::FNameToString, gmalloc::GMalloc, guobject_array::{FUObjectArrayAllocateUObjectIndex, FUObjectArrayFreeUObjectIndex, GUObjectArray} }; +use resolvers::asset_registry::*; use resolvers::admin_control::*; #[cfg(feature="kismet-log")] use resolvers::kismet_dev::*; @@ -31,7 +33,10 @@ use serde::Serialize; use serde_json::to_writer_pretty; use tools::logger::init_syslog; #[cfg(feature="server-registration")] -use tools::server_registration::Registration; +use tools::registration::Registration; +// use tools::server_registration::Registration; +use windows::core::PCSTR; +use windows::Win32::System::Console::SetConsoleTitleA; use self::resolvers::{PLATFORM, BASE_ADDR, PlatformType}; use log::info; @@ -125,10 +130,16 @@ struct CLIArgs { #[arg(long = "next-map-name")] next_map: Option, // + #[arg(long = "launched-profile")] + launched_profile: Option, + // #[arg(long = "playable-listen")] playable_listen: bool, + // + #[arg(long = "register")] + register: bool, // // - #[arg(long = "server-browser-backend")] + #[arg(long = "server-browser-backend", default_value="https://servers.polehammer.net")] server_browser_backend: Option, // // #[arg(long = "server-password")] @@ -148,26 +159,26 @@ struct CLIArgs { // UNHANDLED START - #[arg(long = "AUTH_LOGIN")] - auth_login: Option, - #[arg(long = "AUTH_PASSWORD")] - auth_password: Option, - #[arg(long = "AUTH_TYPE")] - auth_type: Option, - #[arg(long = "epicapp")] - epicapp: Option, - #[arg(long = "epicenv")] - epicenv: Option, - #[arg(long = "EpicPortal")] - epic_portal: bool, - #[arg(long = "epicusername")] - epicusername: Option, - #[arg(long = "epicuserid")] - epicuserid: Option, - #[arg(long = "epiclocale")] - epiclocale: Option, - #[arg(long = "epicsandboxid")] - epicsandboxid: Option, + // #[arg(long = "AUTH_LOGIN")] + // auth_login: Option, + // #[arg(long = "AUTH_PASSWORD")] + // auth_password: Option, + // #[arg(long = "AUTH_TYPE")] + // auth_type: Option, + // #[arg(long = "epicapp")] + // epicapp: Option, + // #[arg(long = "epicenv")] + // epicenv: Option, + // #[arg(long = "EpicPortal")] + // epic_portal: bool, + // #[arg(long = "epicusername")] + // epicusername: Option, + // #[arg(long = "epicuserid")] + // epicuserid: Option, + // #[arg(long = "epiclocale")] + // epiclocale: Option, + // #[arg(long = "epicsandboxid")] + // epicsandboxid: Option, // UNHANDLED END #[arg(trailing_var_arg = true)] @@ -276,7 +287,7 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - #[cfg(feature="engine_loop")] FEngineLoopInit, ClientMessage, - // StaticFindObjectSafe, + StaticFindObjectSafe, #[cfg(feature="kismet-log")] KismetExecutionMessage, ]]; @@ -295,6 +306,19 @@ pub unsafe fn attach_hooks(base_address: usize, offsets: HashMap) - #[cfg(feature = "dev-joindata")] JoinData, JoinDataTwo, + MatchUpdate, + // GetStageTimeRemaining, + SetMatchState, + LogFImpl, + Log2, + GetAssetRegistry, + Conv_InterfaceToObject, + GetAssetsByClass, + GetAssetRegistry_Helper, + FNamePool, + FNameCtorWchar, + GetAsset, + // LogFImpl2, // ShowSusMessage, // JoinDataFour ]]); @@ -356,27 +380,31 @@ fn normalize_and_filter_args>(args: I) -> Vec) = if let Some((k, v)) = arg.split_once('=') { + // println!("{} Split by =", arg); + println!("k '{}' , v '{}' ", k, v); (format!("--{}", k.trim_start_matches('-')), Some(v.to_string())) } else if arg.starts_with('-') && !arg.starts_with("--") && arg.len() > 2 { + // println!("{} Split by -", arg); (format!("--{}", &arg[1..]), None) } else { + // println!("{} Split by else", arg); (arg.clone(), None) }; - // println!("cur: {flag}"); + // println!("cur: '{flag}'"); let cur_flag = flag.clone(); if known_flags.contains(&flag) { - result.push(flag); + result.push(flag.trim().to_string()); if let Some(v) = value_opt { - // println!("option: {v}"); + // println!("option: '{v}'"); last_opt = Some(v.clone()); - result.push(v); + result.push(v.trim().to_string()); } else if let Some(peek) = args.peek() { if !peek.starts_with('-') { let var = args.next().unwrap(); - // print!("pushing {var}"); - result.push(var); + // print!("pushing '{var}'"); + result.push(var.trim().to_string()); } } } @@ -390,8 +418,14 @@ fn normalize_and_filter_args>(args: I) -> Vec 0) + { + // println!("Trailing '{cur_flag}'"); + last_mut.push(' '); + last_mut.push_str(&cur_flag.trim().to_string()); + + } + // last_mut = last_mut.trim().to_string(); } } } @@ -400,14 +434,15 @@ fn normalize_and_filter_args>(args: I) -> Vec Result { let args = std::env::args(); let parsed = normalize_and_filter_args(args); + println!("Pre-Parsed CLI: {:#?}", parsed); let cli = CLIArgs::try_parse_from(parsed).expect("Failed to parse CLI atgs"); - // println!("Parsed CLI: {:#?}", cli); Ok(cli) } unsafe fn init_globals() -> Result<(), clap::error::Error>{ @@ -442,7 +477,7 @@ unsafe fn init_globals() -> Result<(), clap::error::Error>{ cli_args: args, platform }); - sinfo!(f; "{GLOBALS:#?}"); + sdebug!(f; "{GLOBALS:#?}"); Ok(()) } @@ -497,11 +532,28 @@ pub extern "C" fn generate_json() -> u8 { init_syslog().expect("Failed to init syslog"); unsafe { match init_globals() { - Ok(_) => {}, + Ok(_) => { + // swarn!("{:#?}", globals().cli_args) + }, Err(e) => serror!(f; "No globals: {}", e), } }; + + unsafe { + let mut title_pcstr = PCSTR("Chivalry 2 Unchained\0".to_string().as_ptr() as _); + + if let Some(profile_name) = &globals().cli_args.launched_profile { + let title = format!("Chivalry 2 Unchained - {}\0", profile_name); + title_pcstr = PCSTR(title.as_ptr() as _); + } + + match SetConsoleTitleA(title_pcstr) { + Ok(_) => {}, + Err(e) => serror!(f; "Failed to set title {e}"), + } + } + #[cfg(feature="rcon")] std::thread::spawn(|| { resolvers::rcon::handle_rcon(); @@ -530,8 +582,10 @@ pub extern "C" fn generate_json() -> u8 { // PLATFORM.set(platform).expect("Platform already set"); let exe = patternsleuth::process::internal::read_image().map_err(|e| e.to_string()).expect("failed to read image"); - let offsets = scan::scan().expect("Failed to scan"); + let mut offsets = scan::scan().expect("Failed to scan"); let len_u8 = offsets.len() as u8; + let addr_temp = *globals().resolution.kismet_system_library.0.get("Conv_InterfaceToObject").unwrap() - exe.base_address; + offsets.insert("Conv_InterfaceToObject".to_string(), addr_temp as u64); // FIXME: Nihi: ? unsafe { attach_hooks(exe.base_address, offsets.clone()).unwrap(); @@ -540,6 +594,7 @@ pub extern "C" fn generate_json() -> u8 { sinfo!("trying to patch"); // 19626AE + 0xF = 19626BD + // #[cfg(feature="skip_msg")] match offsets.get("ShowSusMessage") { Some(a) => { //FIXME: Nihi: <- usize or u64 or *mut u8 @@ -570,25 +625,30 @@ pub extern "C" fn generate_json() -> u8 { #[cfg(feature="server-registration")] - if globals().cli_args.rcon_port.is_some() { // FIXME: Nihi: better way than checking for rcon port. Listen? + if globals().cli_args.rcon_port.is_some() || + globals().cli_args.register { // FIXME: Nihi: better way than checking for rcon port. Listen? let cli = globals().args(); + // sinfo!(f; "cli: {cli:?}"); let backend = cli.server_browser_backend.clone().unwrap(); - let reg = Registration::new( + let reg = Arc::new(Registration::new( "127.0.0.1", - 7071 - ); + globals().cli_args.game_server_query_port.unwrap() + )); // let last_info = std::sync::Arc::clone(&tools::server_registration::REGISTRATION); // *last_info.lock().unwrap() = Some(reg); - reg.start(&backend, "Chivalry 2 Local Server", ""); - let registration = std::sync::Arc::new(reg); - registration.start_heartbeat(&backend, "Some Server", ""); + sinfo!(f; "Started server registration"); + // (&backend); + reg.start(); + // let registration = std::sync::Arc::new(reg); + // registration.start_heartbeat(&backend, "Some Server", ""); info!("Backend: {backend}"); // std::thread::spawn(|| { // }); } + sinfo!("Generate_json done"); len_u8 } diff --git a/src/main.cpp b/src/main.cpp index 90988ad..ab6f42c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -125,7 +125,7 @@ void CreateDebugConsole() { freopen_s(&pCerr, "CONOUT$", "w", stderr); std::ios::sync_with_stdio(true); - SetConsoleTitleA("Chivalry 2 Unchained Debug"); + // SetConsoleTitleA("Chivalry 2 Unchained Debug"); HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); DWORD consoleMode; @@ -141,6 +141,7 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { initialize_global_logger(LogLevel::INFO); GLOG_INFO("Logger initialized {}", generate_json()); + GLOG_INFO("Parsing CLI args"); auto cliArgs = CLIArgs::Parse(GetCommandLineW()); HMODULE hModule = static_cast(lpParameter);