From 8ee4d9cea5793e2561ba8e86cfe01a9f9af23141 Mon Sep 17 00:00:00 2001 From: Knutschbert Date: Wed, 10 Dec 2025 00:57:07 +0100 Subject: [PATCH 001/219] steam and EGS share offsets again in 2.11.4 --- sleuth/src/resolvers/admin_control.rs | 34 ++++++++++++------- sleuth/src/resolvers/backend_hooks.rs | 25 +++++++------- sleuth/src/resolvers/ownership_overrides.rs | 25 +++++++++----- sleuth/src/resolvers/unchained_integration.rs | 15 ++++---- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/sleuth/src/resolvers/admin_control.rs b/sleuth/src/resolvers/admin_control.rs index 5fbe97a..33c9c86 100644 --- a/sleuth/src/resolvers/admin_control.rs +++ b/sleuth/src/resolvers/admin_control.rs @@ -1,22 +1,27 @@ - - 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 // "75 17 45 84 ED", // STEAM // From Sigga - OTHER: ["75 ?? 45 84 ed 75 ?? 48 85 f6 74 ?? 40 38 be ?? 01 00 00"], // PDB + STEAM + OTHER: [ + "75 ?? 45 84 ed 75 ?? 48 85 f6 74 ?? 40 38 be ?? 01 00 00", + "75 18 40 38 7d d7 75 12 4d 85 f6 74 0d 41 38 be ?? 01 00 00" + ], // PDB + STEAM EGS: ["75 18 40 38 7d d7 75 12 4d 85 f6 74 0d 41 38 be ?? 01 00 00"], // EGS // "75 1a 45 84 ed 75 15 48 85 f6 74 10 40 38 be b0 01 00 00 74 07 32 db e9 a6 fd ff ff 48 8b 5d 60 49 8b d6 4c 8b 45 58 4c 8b cb 49 8b cf", // PDB }]; -define_pattern_resolver!(ExecuteConsoleCommand, [ - "40 53 48 83 EC 30 48 8B 05 ?? ?? ?? ?? 48 8B D9 48 8B 90 58 0C 00 00" -]); +define_pattern_resolver!( + ExecuteConsoleCommand, + ["40 53 48 83 EC 30 48 8B 05 ?? ?? ?? ?? 48 8B D9 48 8B 90 58 0C 00 00"] +); // 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"], - STEAM: ["40 53 55 57 48 83 EC 50 83 7A 08 01 48 8B F9 4C 89 B4 24 80 00 00 00 C7 44 24 70 00 00 00 00 7F 33 E8 ?? ?? ?? ?? 48 8B 58 08 48 8B 08 48 89 4C 24 20 48 89 5C 24 28 48 85 DB 74 04 F0 FF 43 08 8B 40 10 41 BE 01 00 00 00 89 44 24 30 48 8D 44 24 20 EB 18 48 8D 4C 24 38 E8 ?? ?? ?? ?? 48 8B 5C 24 28 41 BE 02 00 00 00 48 8B 08 48 89 0F 48 8B 48 08 48 89 4F 08 48 85 C9 74 04 F0 FF 41 08 8B 40 10 BD FF FF FF FF 89 47 10 41 F6 C6 02 74 46 48 89 74 24 78 41 83 E6 FD 48 8B 74 24 40 48 85 F6 74 2E 8B C5 F0 0F C1 46 08 83 F8 01 75 22 48 8B 06 48 8B CE FF 10 8B C5 F0 0F C1 46 0C 83 F8 01 75 0E 48 8B 06 BA 01 00 00 00 48 8B CE FF 50 ?? 48 8B 74 24 78 41 F6 C6 01 4C 8B B4 24 80 00 00 00 74 2E 48 85 DB 74 29 8B C5 F0 0F C1 43 08 83 F8 01 75 1D 48 8B 03 48 8B CB FF 10 F0 0F C1 6B 0C 83 FD 01 75 0B 48 8B 03 8B D5 48 8B CB FF 50 ?? 83 4F 10 02"] + STEAM: [ + "40 53 55 57 48 83 EC 50 83 7A 08 01 48 8B F9 4C 89 B4 24 80 00 00 00 C7 44 24 70 00 00 00 00 7F 33 E8 ?? ?? ?? ?? 48 8B 58 08 48 8B 08 48 89 4C 24 20 48 89 5C 24 28 48 85 DB 74 04 F0 FF 43 08 8B 40 10 41 BE 01 00 00 00 89 44 24 30 48 8D 44 24 20 EB 18 48 8D 4C 24 38 E8 ?? ?? ?? ?? 48 8B 5C 24 28 41 BE 02 00 00 00 48 8B 08 48 89 0F 48 8B 48 08 48 89 4F 08 48 85 C9 74 04 F0 FF 41 08 8B 40 10 BD FF FF FF FF 89 47 10 41 F6 C6 02 74 46 48 89 74 24 78 41 83 E6 FD 48 8B 74 24 40 48 85 F6 74 2E 8B C5 F0 0F C1 46 08 83 F8 01 75 22 48 8B 06 48 8B CE FF 10 8B C5 F0 0F C1 46 0C 83 F8 01 75 0E 48 8B 06 BA 01 00 00 00 48 8B CE FF 50 ?? 48 8B 74 24 78 41 F6 C6 01 4C 8B B4 24 80 00 00 00 74 2E 48 85 DB 74 29 8B C5 F0 0F C1 43 08 83 F8 01 75 1D 48 8B 03 48 8B CB FF 10 F0 0F C1 6B 0C 83 FD 01 75 0B 48 8B 03 8B D5 48 8B CB FF 50 ?? 83 4F 10 02", + "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" // EGS 2.11.4 + ] } // ,|ctx, patterns| { // let futures = ::patternsleuth::resolvers::futures::future::join_all( @@ -28,20 +33,23 @@ define_pattern_resolver![FText_AsCultureInvariant, First, { // } ]; - // define_pattern_resolver!(ConsoleCommand, First, [ // "40 53 48 83 EC 20 48 8B 89 D0 02 00 00 48 8B DA 48 85 C9 74 0E E8 ?? ?? ?? ?? 48 8B C3 48 83 C4 20 5B C3 33 C0 48 89 02 48 89 42 08 48 8B C3 48 83 C4 20 5B C3" // ]); -define_pattern_resolver!(BroadcastLocalizedChat, [ - "48 89 74 24 10 57 48 83 EC 30 48 8B 01 41 8B F8 48 8B F2 ?? ?? ?? ?? ?? ?? 48 8B C8 48 8D" -]); +define_pattern_resolver!( + BroadcastLocalizedChat, + ["48 89 74 24 10 57 48 83 EC 30 48 8B 01 41 8B F8 48 8B F2 ?? ?? ?? ?? ?? ?? 48 8B C8 48 8D"] +); define_pattern_resolver![GetTBLGameMode, { EGS : ["40 53 48 83 EC 20 48 8B D9 48 85 C9 ?? ?? 48 8B 01 ?? ?? ?? ?? ?? ?? 48 85 C0 ?? ?? 0F 1F 40 00 48 8B 5B 20 48 85 DB ?? ?? 48 8B 03 48 8B CB ?? ?? ?? ?? ?? ?? 48 85 C0 ?? ?? 48 8B 98 28 01 00 00 48 85 DB ?? ?? ?? ?? ?? ?? ?? 48 8B 4B 10 48 83 C0 30 48 63 50 08 3B 51"], // EGS - 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"] + 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", + "40 53 48 83 EC 20 48 8B D9 48 85 C9 ?? ?? 48 8B 01 ?? ?? ?? ?? ?? ?? 48 85 C0 ?? ?? 0F 1F 40 00 48 8B 5B 20 48 85 DB ?? ?? 48 8B 03 48 8B CB ?? ?? ?? ?? ?? ?? 48 85 C0 ?? ?? 48 8B 98 28 01 00 00 48 85 DB ?? ?? ?? ?? ?? ?? ?? 48 8B 4B 10 48 83 C0 30 48 63 50 08 3B 51" // EGS 2.11.4 + ] }]; 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 +]); diff --git a/sleuth/src/resolvers/backend_hooks.rs b/sleuth/src/resolvers/backend_hooks.rs index e85f3c9..f6fce25 100644 --- a/sleuth/src/resolvers/backend_hooks.rs +++ b/sleuth/src/resolvers/backend_hooks.rs @@ -1,30 +1,31 @@ - - 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 ]); -define_pattern_resolver!(PreLogin, XrefLast, [ - patternsleuth::resolvers::unreal::util::utf8_pattern(" Minutes") -]); +define_pattern_resolver!( + PreLogin, + XrefLast, + [patternsleuth::resolvers::unreal::util::utf8_pattern( + " Minutes" + )] +); define_pattern_resolver!(ApproveLogin, [ "48 89 5C 24 18 48 89 74 24 20 55 57 41 54 41 55 41 56 48 8D 6C 24 C9 48 81 EC A0 00 00 00 8B", // EGS "48 89 5C 24 10 48 89 74 24 18 55 57 41 54 41 56 41 57 48 8B EC 48 81 EC 80 00 00 00 8B", // STEAM ]); - // TODO: Shorter sigs define_pattern_resolver!(GetMotd, { EGS: ["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: ["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"] + STEAM: [ + "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", + "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" // EGS 2.11.4 + ] }); - -define_pattern_resolver!(GetCurrentGames, Call, [ - "E8 | ?? ?? ?? ?? 4C 39 ?8 74 3?" -]); +define_pattern_resolver!(GetCurrentGames, Call, ["E8 | ?? ?? ?? ?? 4C 39 ?8 74 3?"]); 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 +]); diff --git a/sleuth/src/resolvers/ownership_overrides.rs b/sleuth/src/resolvers/ownership_overrides.rs index 831fbf9..b444f03 100644 --- a/sleuth/src/resolvers/ownership_overrides.rs +++ b/sleuth/src/resolvers/ownership_overrides.rs @@ -1,8 +1,9 @@ - - define_pattern_resolver!(ATBLPlayerController__GetOwnershipFromPlayerControllerAndState, { EGS: ["40 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 B0 FD"], // EGS - STEAM: ["40 55 56 41 54 41 55 41 56 41 57 48 8D AC 24 B8"], // STEAM + STEAM: [ + "40 55 56 41 54 41 55 41 56 41 57 48 8D AC 24 B8", + "40 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 B0 FD" // EGS 2.11.4 + ], // STEAM OTHER: ["40 55 53 56 57 41 54 41 55 41 56 41 57 48 8d ac 24 38 fd"]// PDB }); @@ -11,17 +12,23 @@ define_pattern_resolver!(ATBLPlayerController__CanUseLoadoutItem, { // "48 89 5C 24 08 48 89 74 24 18 55 57 41 55 41 56 41 57 48 8B EC 48 83 EC", // STEAM // from sigga // "48 89 5C 24 08 48 89 74 24 10 55 57 41 55 41 56 41 57 48 8B EC 48 81 EC 80 00 00", // EGS - STEAM: ["48 89 5C 24 08 48 89 74 24 18 55 57 41 55 41 56 41 57 48 8B EC 48 83 EC 60 49 8B 31 33 FF C6 02 00"], // STEAM + STEAM: [ + "48 89 5C 24 08 48 89 74 24 18 55 57 41 55 41 56 41 57 48 8B EC 48 83 EC 60 49 8B 31 33 FF C6 02 00", + "48 89 5C 24 08 48 89 74 24 10 55 57 41 55 41 56 41 57 48 8B EC 48 81 EC 80 00 00" // EGS 2.11.4 + ], // STEAM }); -define_pattern_resolver!(ATBLPlayerController__CanUseCharacter, [ - "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 48 89 7C 24 20 41 56 48 83 EC 50 49 8B 18", // universal -]); +define_pattern_resolver!( + ATBLPlayerController__CanUseCharacter, + [ + "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 48 89 7C 24 20 41 56 48 83 EC 50 49 8B 18", // universal + ] +); define_pattern_resolver!(ATBLPlayerController__ConditionalInitializeCustomizationOnServer, { EGS: ["48 89 54 24 10 53 56 57 41 54 48 83 EC 78 48 8B 99 60 02 00 00 48 8B F2 0F B6"], // EGS - STEAM: ["48 89 54 24 10 53 55 57 41 54 48 83 EC 78"], // STEAM + STEAM: ["48 89 54 24 10 53 ?? 57 41 54 48 83 EC 78"], // STEAM // From Sigga // Did the function change? OTHER: ["41 54 48 81 EC 80 00 00 00 80 B9 F8 00 00 00 03 4C 8B E1 ?? ?? ?? ?? ?? ?? 80 B9 20 13 00 00 00 ?? ?? ?? ?? ?? ?? 80 B9 21"], // PDB -}); \ No newline at end of file +}); diff --git a/sleuth/src/resolvers/unchained_integration.rs b/sleuth/src/resolvers/unchained_integration.rs index 67ec18a..e41303f 100644 --- a/sleuth/src/resolvers/unchained_integration.rs +++ b/sleuth/src/resolvers/unchained_integration.rs @@ -1,17 +1,20 @@ - // not working? define_pattern_resolver!(FViewport, First, { STEAM: ["48 89 5C 24 08 48 89 74 24 10 48 89 7C 24 18 41 56 48 83 EC 30 33 F6"], EGS: ["48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 30 33 ED"], }); -define_pattern_resolver!(LoadFrontEndMap, [ - "48 8B C4 48 89 50 10 48 89 48 08 55 41 55 48 8D 68 98 48 81 EC 58 01 00 00 83 7A 08 00" -]); +define_pattern_resolver!( + LoadFrontEndMap, + ["48 8B C4 48 89 50 10 48 89 48 08 55 41 55 48 8D 68 98 48 81 EC 58 01 00 00 83 7A 08 00"] +); define_pattern_resolver!(InternalGetNetMode, { EGS: ["40 53 48 81 EC 90 00 00 00 48 8B D9 48 8B 49 38 48 85 C9"], // EGS - STEAM: ["40 57 48 81 EC 90 00 00 00 48 8B F9 48 8B"], // STEAM + STEAM: [ + "40 57 48 81 EC 90 00 00 00 48 8B F9 48 8B", + "40 53 48 81 EC 90 00 00 00 48 8B D9 48 8B 49 38 48 85 C9" // EGS 2.11.4 + ], // STEAM }); define_pattern_resolver!(UNetDriver_GetNetMode, [ @@ -25,4 +28,4 @@ define_pattern_resolver!(UGameplay_IsDedicatedServer, [ define_pattern_resolver!(EACAntiCheatMesssage, 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"] -}); \ No newline at end of file +}); From 11f8b5389a9b4a613824e2fc9bfab8aacaaf3dca Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Sun, 28 Dec 2025 18:18:51 -0600 Subject: [PATCH 002/219] Load BuildMetadata from memory using basic BuildInfo API --- CMakeLists.txt | 2 - sleuth/src/lib.rs | 191 +++++++++++++++++++++++++++++++----- src/builds.cpp | 127 +++++++----------------- src/builds.hpp | 4 +- src/main.cpp | 15 +-- src/state/BuildMetadata.cpp | 63 ++++-------- src/state/BuildMetadata.hpp | 5 +- 7 files changed, 234 insertions(+), 173 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3090b80..35b103c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,6 @@ configure_file( # Add dependencies add_subdirectory(lib/MinHook) -add_subdirectory(lib/tiny-json) # Define the library add_library(${PROJECT_NAME} SHARED @@ -105,7 +104,6 @@ add_subdirectory(sleuth) # Link libraries target_link_libraries(${PROJECT_NAME} PRIVATE MinHook - tiny-json winhttp sleuthlib_interface ) diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index f616ece..ff647a3 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -9,9 +9,13 @@ use std::io::{BufReader, Read}; use std::io::{BufWriter, Write}; use std::collections::HashMap; use std::path::PathBuf; +use std::ffi::CString; +use std::os::raw::c_char; +use std::sync::Mutex; +use once_cell::sync::Lazy; use anyhow::Result; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use serde_json::to_writer_pretty; use self::resolvers::{PLATFORM, PlatformType}; @@ -39,7 +43,7 @@ unsafe fn crc32_from_file(path: &str) -> std::io::Result { } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all(serialize = "PascalCase", deserialize = "snake_case"))] struct BuildInfo { build: u32, @@ -50,6 +54,9 @@ struct BuildInfo { offsets: HashMap, } +static CURRENT_BUILD_INFO: Lazy>> = Lazy::new(|| Mutex::new(None)); +static KNOWN_BUILDS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + fn expand_env_path(path: &str) -> Option { if let Some(stripped) = path.strip_prefix("%LOCALAPPDATA%") { if let Ok(base) = env::var("LOCALAPPDATA") { @@ -59,53 +66,189 @@ fn expand_env_path(path: &str) -> Option { None } -pub fn dump_builds(offsets: HashMap) -> Result<()> { +pub fn load_builds() -> Result<()> { + let builds_path = expand_env_path(r"%LOCALAPPDATA%\Chivalry 2\Saved\Config\c2uc.builds.json").unwrap().to_path_buf(); + if builds_path.exists() { + let file = File::open(&builds_path)?; + let reader = BufReader::new(file); + let data: HashMap = serde_json::from_reader(reader).unwrap_or_default(); + let mut known = KNOWN_BUILDS.lock().unwrap(); + *known = data; + } + Ok(()) +} + +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 known = KNOWN_BUILDS.lock().unwrap(); let file = File::create(builds_path)?; let mut writer = BufWriter::new(file); - let mut data = HashMap::new(); + to_writer_pretty(&mut writer, &*known)?; + writer.flush()?; - let mut file_path = String::new(); + Ok(()) +} +#[no_mangle] +pub extern "C" fn scan_build() -> u8 { + println!("Scanning build..."); + + let platform = match env::args().any(|arg| arg == "-epicapp=Peppermint") { + true => PlatformType::EGS, + false => PlatformType::STEAM + }; + + PLATFORM.set(platform).ok(); // Ignore if already set + + let offsets = scan::scan().expect("Failed to scan"); + + 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), } - println!("Current executable path: {:?}", file_path); let crc32 = unsafe { crc32_from_file(&file_path) }.expect("Failed to compute CRC"); - let build_info = BuildInfo { build: 0, file_hash: crc32, name: "".to_string(), - platform: PLATFORM.get().ok_or("OTHER").unwrap().to_string(),//platform.to_string(), + platform: PLATFORM.get().unwrap_or(&PlatformType::OTHER).to_string(), path: file_path.to_string(), offsets, }; - data.insert(crc32.to_string(), build_info); - to_writer_pretty(&mut writer, &data)?; - writer.flush()?; + let len = build_info.offsets.len() as u8; - Ok(()) + // Save to CURRENT_BUILD_INFO + { + let mut current = CURRENT_BUILD_INFO.lock().unwrap(); + *current = Some(build_info.clone()); + } + + // Update KNOWN_BUILDS + { + let mut known = KNOWN_BUILDS.lock().unwrap(); + known.insert(crc32.to_string(), build_info); + } + + // Still dump to JSON for future use + if let Err(e) = dump_builds() { + eprintln!("Failed to dump builds JSON: {}", e); + } + + len } #[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 load_known_builds() { + if let Err(e) = load_builds() { + eprintln!("Failed to load known builds: {}", e); + } +} - PLATFORM.set(platform).expect("Platform already set"); +#[no_mangle] +pub extern "C" fn get_known_builds_count() -> usize { + let known = KNOWN_BUILDS.lock().unwrap(); + known.len() +} - let offsets = scan::scan().expect("Failed to scan"); - let len_u8 = offsets.len() as u8; - dump_builds(offsets).expect("Failed to dump builds JSON"); - len_u8 +#[no_mangle] +pub extern "C" fn get_known_build_hash(index: usize) -> u32 { + let known = KNOWN_BUILDS.lock().unwrap(); + known.values().nth(index).map(|bi| bi.file_hash).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn get_known_build_platform(index: usize) -> *mut c_char { + let known = KNOWN_BUILDS.lock().unwrap(); + let platform = known.values().nth(index).map(|bi| bi.platform.as_str()).unwrap_or("OTHER"); + CString::new(platform).unwrap().into_raw() +} + +#[no_mangle] +pub extern "C" fn get_known_build_offset_count(index: usize) -> usize { + let known = KNOWN_BUILDS.lock().unwrap(); + known.values().nth(index).map(|bi| bi.offsets.len()).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn get_known_build_offset_name(build_index: usize, offset_index: usize) -> *mut c_char { + let known = KNOWN_BUILDS.lock().unwrap(); + if let Some(bi) = known.values().nth(build_index) { + if let Some((name, _)) = bi.offsets.iter().nth(offset_index) { + return CString::new(name.as_str()).unwrap().into_raw(); + } + } + std::ptr::null_mut() +} + +#[no_mangle] +pub extern "C" fn get_known_build_offset_value(build_index: usize, offset_index: usize) -> u64 { + let known = KNOWN_BUILDS.lock().unwrap(); + if let Some(bi) = known.values().nth(build_index) { + if let Some((_, value)) = bi.offsets.iter().nth(offset_index) { + return *value; + } + } + 0 +} + +#[no_mangle] +pub extern "C" fn get_file_hash() -> u32 { + let current = CURRENT_BUILD_INFO.lock().unwrap(); + current.as_ref().map(|bi| bi.file_hash).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn get_platform() -> *mut c_char { + let current = CURRENT_BUILD_INFO.lock().unwrap(); + let platform = current.as_ref().map(|bi| bi.platform.as_str()).unwrap_or("OTHER"); + CString::new(platform).unwrap().into_raw() +} + +#[no_mangle] +pub extern "C" fn get_offset_count() -> usize { + let current = CURRENT_BUILD_INFO.lock().unwrap(); + current.as_ref().map(|bi| bi.offsets.len()).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn get_offset_name(index: usize) -> *mut c_char { + let current = CURRENT_BUILD_INFO.lock().unwrap(); + if let Some(bi) = current.as_ref() { + if let Some((name, _)) = bi.offsets.iter().nth(index) { + return CString::new(name.as_str()).unwrap().into_raw(); + } + } + std::ptr::null_mut() +} + +#[no_mangle] +pub extern "C" fn get_offset_value(index: usize) -> u64 { + let current = CURRENT_BUILD_INFO.lock().unwrap(); + if let Some(bi) = current.as_ref() { + if let Some((_, value)) = bi.offsets.iter().nth(index) { + return *value; + } + } + 0 +} + +#[no_mangle] +pub extern "C" fn free_string(s: *mut c_char) { + if s.is_null() { + return; + } + unsafe { + drop(CString::from_raw(s)); + } +} + +#[no_mangle] +pub extern "C" fn generate_json() -> u8 { + scan_build() } diff --git a/src/builds.cpp b/src/builds.cpp index 52c8237..5e954ac 100644 --- a/src/builds.cpp +++ b/src/builds.cpp @@ -2,110 +2,51 @@ #include #include -#include -#include +#include #include -#include -#include -#include // SSE4.2 intrinsics -#include #include -#include - #include "logging/global_logger.hpp" #include "state/global_state.hpp" -uint32_t calculateCRC32(const std::string& filename) { - std::ifstream file(filename, std::ios::binary); - if (!file.is_open()) { - std::cerr << "Error opening file: " << filename << std::endl; - return 0; - } - - uint32_t crc = 0; // Initial value for CRC-32 - - char buffer[4096]; - while (file) { - file.read(buffer, sizeof(buffer)); - std::streamsize bytesRead = file.gcount(); - - for (std::streamsize i = 0; i < bytesRead; ++i) { - crc = _mm_crc32_u8(crc, buffer[i]); - } - } - - file.close(); - return crc ^ 0xFFFFFFFF; // Final XOR value for CRC-32 -} - -std::filesystem::path getBuildMetadataPath() { - char localAppData[MAX_PATH]; - DWORD result = GetEnvironmentVariableA("LOCALAPPDATA", localAppData, MAX_PATH); - - if (result == 0 || result > MAX_PATH) { - // Handle error - environment variable not found or buffer too small - GLOG_ERROR("Failed to get LOCALAPPDATA environment variable"); - return {}; - } - - return std::filesystem::path(localAppData) / - "Chivalry 2" / "Saved" / "Config" / "c2uc.builds.json"; -} +extern "C" void load_known_builds(); +extern "C" size_t get_known_builds_count(); +extern "C" uint32_t get_known_build_hash(size_t index); +extern "C" char* get_known_build_platform(size_t index); +extern "C" size_t get_known_build_offset_count(size_t index); +extern "C" char* get_known_build_offset_name(size_t build_index, size_t offset_index); +extern "C" uint64_t get_known_build_offset_value(size_t build_index, size_t offset_index); +extern "C" void free_string(char* s); std::map LoadBuildMetadata() { - - auto configPath = getBuildMetadataPath(); - std::map buildMap; + load_known_builds(); + std::map buildMap; - GLOG_DEBUG("Loading build metadata from: {}", configPath.string()); - - if (!std::filesystem::exists(configPath)) { - GLOG_WARNING("Build metadata file ({}) does not exist. This is normal on first start.", configPath); - return buildMap; - } - - std::ifstream file(configPath); - if (!file.is_open()) { - GLOG_ERROR("Error opening build metadata: {}", configPath); - return buildMap; - } - - std::string file_content{ - std::istreambuf_iterator(file), - std::istreambuf_iterator() - }; - - GLOG_DEBUG("File content preview: {}", file_content); - - json_t mem[128]; - const json_t* json = json_create(const_cast(file_content.c_str()), mem, 128); - - if (!json) { - GLOG_ERROR("Failed to create json parser"); - return buildMap; - } - json_t const* buildEntry; - - buildEntry = json_getChild(json); - while (buildEntry != nullptr) { - if (JSON_OBJ == json_getType(buildEntry)) { - const auto buildKey = std::string(json_getName(buildEntry)); - GLOG_DEBUG("Parsing build metadata: {}", buildKey); - - // Parse the build metadata - auto parsedBuild = BuildMetadata::Parse(buildEntry); - if (!parsedBuild.has_value()) { - GLOG_ERROR("Failed to parse build metadata for {}", buildKey); - buildEntry = json_getSibling(buildEntry); - continue; - } - - BuildMetadata bd = parsedBuild.value(); - buildMap.emplace(buildKey, bd); + size_t build_count = get_known_builds_count(); + for (size_t i = 0; i < build_count; ++i) { + uint32_t hash = get_known_build_hash(i); + char* platform_str = get_known_build_platform(i); + + Platform platform = STEAM; + if (string_to_platform.contains(platform_str)) { + platform = string_to_platform.at(platform_str); + } + free_string(platform_str); + + size_t offset_count = get_known_build_offset_count(i); + std::map offsets; + for (size_t j = 0; j < offset_count; ++j) { + char* name = get_known_build_offset_name(i, j); + uint64_t value = get_known_build_offset_value(i, j); + if (name) { + offsets[name] = value; + free_string(name); + } } - buildEntry = json_getSibling(buildEntry); + + BuildMetadata metadata(hash, std::move(offsets), platform); + buildMap.emplace(std::to_string(hash), std::move(metadata)); } return buildMap; diff --git a/src/builds.hpp b/src/builds.hpp index 86a0fc9..a564bfd 100644 --- a/src/builds.hpp +++ b/src/builds.hpp @@ -6,7 +6,5 @@ #include "state/BuildMetadata.hpp" -uint32_t calculateCRC32(const std::string& filename); - -// Loads builds from a JSON config file +// Loads builds via Sleuth memory std::map LoadBuildMetadata(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 97781dd..a37893b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -184,6 +184,13 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { auto found_offsets = generate_json(); GLOG_INFO("Sleuth found {} offsets", found_offsets); + auto current_build_opt = BuildMetadata::FromSleuth(); + if (!current_build_opt) { + GLOG_ERROR("Failed to get build metadata from Sleuth memory"); + return 1; + } + BuildMetadata& current_build_metadata = *current_build_opt; + auto cliArgs = CLIArgs::Parse(GetCommandLineW()); g_logger->set_level(cliArgs.log_level); @@ -208,17 +215,13 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { std::map loaded = LoadBuildMetadata(); - uint32_t build_hash = calculateCRC32("Chivalry2-Win64-Shipping.exe"); + uint32_t build_hash = current_build_metadata.GetFileHash(); std::string build_hash_string = std::to_string(build_hash); if (!loaded.contains(build_hash_string)) { - GLOG_ERROR("Failed to load build metadata for build hash: {}", build_hash_string); - GLOG_ERROR("Something is probably wrong with the rust module invocation"); - return 1; + GLOG_DEBUG("Current build hash {} not found in loaded metadata (expected if just scanned)", build_hash_string); } - BuildMetadata& current_build_metadata = loaded.at(build_hash_string); - auto state = new State(cliArgs, loaded, current_build_metadata); initialize_global_state(state); diff --git a/src/state/BuildMetadata.cpp b/src/state/BuildMetadata.cpp index d1f30b6..977d999 100644 --- a/src/state/BuildMetadata.cpp +++ b/src/state/BuildMetadata.cpp @@ -7,8 +7,6 @@ #include #include -#include - #include "../logging/global_logger.hpp" #include "../string_util.hpp" @@ -59,53 +57,34 @@ Platform BuildMetadata::GetPlatform() const { return platform; } -std::optional BuildMetadata::Parse(const json_t* json) { - if (!json) { - GLOG_ERROR("Invalid JSON object or build name"); - return std::nullopt; - } - - // Get File Hash - const json_t* fileHashJson = json_getProperty(json, "FileHash"); - if (!fileHashJson || JSON_INTEGER != json_getType(fileHashJson)) { - GLOG_ERROR("Error, the 'FileHash' property is not found or not an integer"); - return std::nullopt; - } - auto file_hash = static_cast(json_getInteger(fileHashJson)); +extern "C" uint32_t get_file_hash(); +extern "C" char* get_platform(); +extern "C" size_t get_offset_count(); +extern "C" char* get_offset_name(size_t index); +extern "C" uint64_t get_offset_value(size_t index); +extern "C" void free_string(char* s); - const json_t* platformJson = json_getProperty(json, "Platform"); - if (!platformJson || JSON_TEXT != json_getType(platformJson)) { - GLOG_ERROR("Error, the 'Platform' property is not found or not a string"); - return std::nullopt; - } - const char* platform_string = json_getValue(platformJson); +std::optional BuildMetadata::FromSleuth() { + uint32_t file_hash = get_file_hash(); + if (file_hash == 0) return std::nullopt; + char* platform_str = get_platform(); Platform platform = STEAM; - if (string_to_platform.contains(platform_string)) - platform = string_to_platform.at(platform_string); - else { - GLOG_ERROR("Invalid platform '{}'. Expected 'STEAM', 'EGS', or 'XBOX'", platform_string); - return std::nullopt; - } - - const json_t* offsetsJson = json_getProperty(json, "Offsets"); - if (!offsetsJson || JSON_OBJ != json_getType(offsetsJson)) { - GLOG_ERROR("Error, the 'Offsets' property is not found or not an object"); - return std::nullopt; + if (string_to_platform.contains(platform_str)) { + platform = string_to_platform.at(platform_str); } + free_string(platform_str); + size_t offset_count = get_offset_count(); std::map offsets; - for (const json_t* offsetEntry = json_getChild(offsetsJson); offsetEntry != nullptr; offsetEntry = json_getSibling(offsetEntry)) { - if (JSON_INTEGER == json_getType(offsetEntry)) { - const char* offsetName = json_getName(offsetEntry); - if (offsetName && strlen(offsetName) > 0) { - auto offsetValue = static_cast(json_getInteger(offsetEntry)); - offsets.emplace(offsetName, offsetValue); - } + for (size_t i = 0; i < offset_count; ++i) { + char* name = get_offset_name(i); + uint64_t value = get_offset_value(i); + if (name) { + offsets[name] = value; + free_string(name); } } - BuildMetadata metadata(file_hash, std::move(offsets), platform); - - return metadata; + return BuildMetadata(file_hash, std::move(offsets), platform); } \ No newline at end of file diff --git a/src/state/BuildMetadata.hpp b/src/state/BuildMetadata.hpp index 83a22fa..74a7b02 100644 --- a/src/state/BuildMetadata.hpp +++ b/src/state/BuildMetadata.hpp @@ -7,7 +7,6 @@ #include #include -#include "tiny-json.h" #include "../Platform.hpp" /** @@ -22,9 +21,9 @@ class BuildMetadata { BuildMetadata(uint32_t fileHash, std::map offsets, Platform platform); ~BuildMetadata(); - static std::optional Parse(const json_t* json); + static std::optional FromSleuth(); - void SetOffset(std::string name, uintptr_t offset); + void SetOffset(std::string name, uint64_t offset); std::optional GetOffset(const std::string& name) const; std::vector> GetOffsets() const; From 0a1e92679620d637b777d04c8f777ce5b7d89e4b Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Sun, 28 Dec 2025 20:57:31 -0600 Subject: [PATCH 003/219] Use mmap to load file faster for crc hash, fix build errors --- CMakeLists.txt | 1 + sleuth/Cargo.lock | 16 +- sleuth/Cargo.toml | 3 +- sleuth/src/lib.rs | 272 ++++++++++++---------------------- src/builds.cpp | 53 ------- src/builds.hpp | 10 -- src/main.cpp | 35 ++--- src/patching/PatchManager.hpp | 18 ++- src/state/BuildMetadata.cpp | 90 ----------- src/state/BuildMetadata.hpp | 36 ----- src/state/State.hpp | 22 +-- src/string_util.hpp | 1 + 12 files changed, 144 insertions(+), 413 deletions(-) delete mode 100644 src/builds.cpp delete mode 100644 src/builds.hpp delete mode 100644 src/state/BuildMetadata.cpp delete mode 100644 src/state/BuildMetadata.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 35b103c..4aa2cf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES # Include directories target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/lib/Sig ) diff --git a/sleuth/Cargo.lock b/sleuth/Cargo.lock index 33446b8..19d4c22 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" @@ -285,9 +285,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "lock_api" @@ -305,6 +305,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + [[package]] name = "miniz_oxide" version = "0.8.8" @@ -539,6 +548,7 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", + "memmap2", "once_cell", "paste", "patternsleuth", diff --git a/sleuth/Cargo.toml b/sleuth/Cargo.toml index 7a9c86b..db8f168 100644 --- a/sleuth/Cargo.toml +++ b/sleuth/Cargo.toml @@ -14,4 +14,5 @@ serde_json = "1.0.111" tracing = "0.1.40" futures = "0.3.31" once_cell = "1.19" -paste = "1.0" \ No newline at end of file +paste = "1.0" +memmap2 = "0.9.4" \ No newline at end of file diff --git a/sleuth/src/lib.rs b/sleuth/src/lib.rs index ff647a3..c39567d 100644 --- a/sleuth/src/lib.rs +++ b/sleuth/src/lib.rs @@ -1,15 +1,11 @@ - - mod resolvers; mod scan; use std::env; use std::fs::File; -use std::io::{BufReader, Read}; -use std::io::{BufWriter, Write}; +use std::io::{BufReader, BufWriter, Write}; use std::collections::HashMap; use std::path::PathBuf; -use std::ffi::CString; use std::os::raw::c_char; use std::sync::Mutex; use once_cell::sync::Lazy; @@ -20,32 +16,30 @@ use serde_json::to_writer_pretty; use self::resolvers::{PLATFORM, PlatformType}; // IEEE -use std::arch::x86_64::_mm_crc32_u8; +use std::arch::x86_64::{_mm_crc32_u8, _mm_crc32_u64}; + #[target_feature(enable = "sse4.2")] unsafe fn crc32_from_file(path: &str) -> std::io::Result { let file = File::open(path)?; - let mut reader = BufReader::new(file); - let mut buffer = [0u8; 4096]; - let mut crc: u32 = 0; - - loop { - let bytes_read = reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } + let mmap = memmap2::Mmap::map(&file)?; + let mut crc: u64 = 0; - for &byte in &buffer[..bytes_read] { - crc = _mm_crc32_u8(crc, byte); - } + let mut chunks = mmap.chunks_exact(8); + while let Some(chunk) = chunks.next() { + crc = _mm_crc32_u64(crc, u64::from_le_bytes(chunk.try_into().unwrap())); + } + + for &byte in chunks.remainder() { + crc = _mm_crc32_u8(crc as u32, byte) as u64; } - Ok(crc ^ 0xFFFFFFFF) + Ok((crc as u32) ^ 0xFFFFFFFF) } #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all(serialize = "PascalCase", deserialize = "snake_case"))] -struct BuildInfo { +pub struct BuildInfo { build: u32, file_hash: u32, name: String, @@ -55,7 +49,6 @@ struct BuildInfo { } static CURRENT_BUILD_INFO: Lazy>> = Lazy::new(|| Mutex::new(None)); -static KNOWN_BUILDS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); fn expand_env_path(path: &str) -> Option { if let Some(stripped) = path.strip_prefix("%LOCALAPPDATA%") { @@ -66,189 +59,118 @@ fn expand_env_path(path: &str) -> Option { None } -pub fn load_builds() -> Result<()> { - let builds_path = expand_env_path(r"%LOCALAPPDATA%\Chivalry 2\Saved\Config\c2uc.builds.json").unwrap().to_path_buf(); - if builds_path.exists() { - let file = File::open(&builds_path)?; - let reader = BufReader::new(file); - let data: HashMap = serde_json::from_reader(reader).unwrap_or_default(); - let mut known = KNOWN_BUILDS.lock().unwrap(); - *known = data; - } - Ok(()) +fn get_build_path(crc: u32) -> Option { + expand_env_path(&format!(r"%LOCALAPPDATA%\Chivalry 2\Saved\Config\{:08x}.build.json", crc)) } -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 known = KNOWN_BUILDS.lock().unwrap(); - let file = File::create(builds_path)?; - let mut writer = BufWriter::new(file); - to_writer_pretty(&mut writer, &*known)?; - writer.flush()?; - - Ok(()) -} +impl BuildInfo { + pub fn scan() -> Self { + println!("Scanning build..."); + + let platform = match env::args().any(|arg| arg == "-epicapp=Peppermint") { + true => PlatformType::EGS, + false => PlatformType::STEAM + }; -#[no_mangle] -pub extern "C" fn scan_build() -> u8 { - println!("Scanning build..."); - - let platform = match env::args().any(|arg| arg == "-epicapp=Peppermint") { - true => PlatformType::EGS, - false => PlatformType::STEAM - }; - - PLATFORM.set(platform).ok(); // Ignore if already set - - let offsets = scan::scan().expect("Failed to scan"); - - 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 crc32 = unsafe { crc32_from_file(&file_path) }.expect("Failed to compute CRC"); - - let build_info = BuildInfo { - build: 0, - file_hash: crc32, - name: "".to_string(), - platform: PLATFORM.get().unwrap_or(&PlatformType::OTHER).to_string(), - path: file_path.to_string(), - offsets, - }; - - let len = build_info.offsets.len() as u8; - - // Save to CURRENT_BUILD_INFO - { - let mut current = CURRENT_BUILD_INFO.lock().unwrap(); - *current = Some(build_info.clone()); - } + PLATFORM.set(platform).ok(); // Ignore if already set - // Update KNOWN_BUILDS - { - let mut known = KNOWN_BUILDS.lock().unwrap(); - known.insert(crc32.to_string(), build_info); + let offsets = scan::scan().expect("Failed to scan"); + + 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 crc32 = unsafe { crc32_from_file(&file_path) }.expect("Failed to compute CRC"); + + BuildInfo { + build: 0, + file_hash: crc32, + name: "".to_string(), + platform: PLATFORM.get().unwrap_or(&PlatformType::OTHER).to_string(), + path: file_path.to_string(), + offsets, + } } - // Still dump to JSON for future use - if let Err(e) = dump_builds() { - eprintln!("Failed to dump builds JSON: {}", e); + pub fn load(crc: u32) -> Result { + let path = get_build_path(crc).ok_or_else(|| anyhow::anyhow!("Failed to expand path"))?; + let file = File::open(path)?; + let reader = BufReader::new(file); + let build_info: BuildInfo = serde_json::from_reader(reader)?; + Ok(build_info) } - len -} + pub fn save(&self) -> Result<()> { + let path = get_build_path(self.file_hash).ok_or_else(|| anyhow::anyhow!("Failed to expand path"))?; + println!("Saving build info to {}", path.to_string_lossy()); + + let file = File::create(path)?; + let mut writer = BufWriter::new(file); + to_writer_pretty(&mut writer, self)?; + writer.flush()?; -#[no_mangle] -pub extern "C" fn load_known_builds() { - if let Err(e) = load_builds() { - eprintln!("Failed to load known builds: {}", e); + Ok(()) } -} - -#[no_mangle] -pub extern "C" fn get_known_builds_count() -> usize { - let known = KNOWN_BUILDS.lock().unwrap(); - known.len() -} -#[no_mangle] -pub extern "C" fn get_known_build_hash(index: usize) -> u32 { - let known = KNOWN_BUILDS.lock().unwrap(); - known.values().nth(index).map(|bi| bi.file_hash).unwrap_or(0) -} - -#[no_mangle] -pub extern "C" fn get_known_build_platform(index: usize) -> *mut c_char { - let known = KNOWN_BUILDS.lock().unwrap(); - let platform = known.values().nth(index).map(|bi| bi.platform.as_str()).unwrap_or("OTHER"); - CString::new(platform).unwrap().into_raw() -} - -#[no_mangle] -pub extern "C" fn get_known_build_offset_count(index: usize) -> usize { - let known = KNOWN_BUILDS.lock().unwrap(); - known.values().nth(index).map(|bi| bi.offsets.len()).unwrap_or(0) -} - -#[no_mangle] -pub extern "C" fn get_known_build_offset_name(build_index: usize, offset_index: usize) -> *mut c_char { - let known = KNOWN_BUILDS.lock().unwrap(); - if let Some(bi) = known.values().nth(build_index) { - if let Some((name, _)) = bi.offsets.iter().nth(offset_index) { - return CString::new(name.as_str()).unwrap().into_raw(); - } + pub fn get_file_hash(&self) -> u32 { + self.file_hash } - std::ptr::null_mut() -} -#[no_mangle] -pub extern "C" fn get_known_build_offset_value(build_index: usize, offset_index: usize) -> u64 { - let known = KNOWN_BUILDS.lock().unwrap(); - if let Some(bi) = known.values().nth(build_index) { - if let Some((_, value)) = bi.offsets.iter().nth(offset_index) { - return *value; - } + pub fn get_offset(&self, name: &str) -> Option<&u64> { + self.offsets.get(name) } - 0 } #[no_mangle] -pub extern "C" fn get_file_hash() -> u32 { - let current = CURRENT_BUILD_INFO.lock().unwrap(); - current.as_ref().map(|bi| bi.file_hash).unwrap_or(0) -} - -#[no_mangle] -pub extern "C" fn get_platform() -> *mut c_char { - let current = CURRENT_BUILD_INFO.lock().unwrap(); - let platform = current.as_ref().map(|bi| bi.platform.as_str()).unwrap_or("OTHER"); - CString::new(platform).unwrap().into_raw() -} +pub extern "C" fn load_current_build_info() -> *const BuildInfo { + let mut current = CURRENT_BUILD_INFO.lock().unwrap(); + if current.is_none() { + let mut file_path = String::new(); + if let Ok(path) = env::current_exe() { + file_path = path.to_string_lossy().into(); + } -#[no_mangle] -pub extern "C" fn get_offset_count() -> usize { - let current = CURRENT_BUILD_INFO.lock().unwrap(); - current.as_ref().map(|bi| bi.offsets.len()).unwrap_or(0) -} + let crc31 = unsafe { crc32_from_file(&file_path) }.expect("Failed to compute CRC"); -#[no_mangle] -pub extern "C" fn get_offset_name(index: usize) -> *mut c_char { - let current = CURRENT_BUILD_INFO.lock().unwrap(); - if let Some(bi) = current.as_ref() { - if let Some((name, _)) = bi.offsets.iter().nth(index) { - return CString::new(name.as_str()).unwrap().into_raw(); + match BuildInfo::load(crc31) { + Ok(bi) => { + *current = Some(bi); + } + Err(_) => { + let bi = BuildInfo::scan(); + *current = Some(bi); + } } } - std::ptr::null_mut() + + if let Some(ref bi) = *current { + bi as *const BuildInfo + } else { + std::ptr::null() + } } #[no_mangle] -pub extern "C" fn get_offset_value(index: usize) -> u64 { - let current = CURRENT_BUILD_INFO.lock().unwrap(); - if let Some(bi) = current.as_ref() { - if let Some((_, value)) = bi.offsets.iter().nth(index) { - return *value; - } +pub extern "C" fn build_info_save(bi: *const BuildInfo) -> u8 { + let bi = unsafe { &*bi }; + if let Err(e) = bi.save() { + eprintln!("Failed to save build info: {}", e); + return 0; } - 0 + 1 } #[no_mangle] -pub extern "C" fn free_string(s: *mut c_char) { - if s.is_null() { - return; - } - unsafe { - drop(CString::from_raw(s)); - } +pub extern "C" fn build_info_get_file_hash(bi: *const BuildInfo) -> u32 { + let bi = unsafe { &*bi }; + bi.get_file_hash() } #[no_mangle] -pub extern "C" fn generate_json() -> u8 { - scan_build() +pub extern "C" fn build_info_get_offset(bi: *const BuildInfo, name: *const c_char) -> u64 { + let bi = unsafe { &*bi }; + let name = unsafe { std::ffi::CStr::from_ptr(name) }.to_string_lossy(); + *bi.get_offset(name.as_ref()).unwrap_or(&0) } diff --git a/src/builds.cpp b/src/builds.cpp deleted file mode 100644 index 5e954ac..0000000 --- a/src/builds.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "builds.hpp" - -#include -#include -#include -#include -#include - -#include "logging/global_logger.hpp" -#include "state/global_state.hpp" - -extern "C" void load_known_builds(); -extern "C" size_t get_known_builds_count(); -extern "C" uint32_t get_known_build_hash(size_t index); -extern "C" char* get_known_build_platform(size_t index); -extern "C" size_t get_known_build_offset_count(size_t index); -extern "C" char* get_known_build_offset_name(size_t build_index, size_t offset_index); -extern "C" uint64_t get_known_build_offset_value(size_t build_index, size_t offset_index); -extern "C" void free_string(char* s); - -std::map LoadBuildMetadata() -{ - load_known_builds(); - std::map buildMap; - - size_t build_count = get_known_builds_count(); - for (size_t i = 0; i < build_count; ++i) { - uint32_t hash = get_known_build_hash(i); - char* platform_str = get_known_build_platform(i); - - Platform platform = STEAM; - if (string_to_platform.contains(platform_str)) { - platform = string_to_platform.at(platform_str); - } - free_string(platform_str); - - size_t offset_count = get_known_build_offset_count(i); - std::map offsets; - for (size_t j = 0; j < offset_count; ++j) { - char* name = get_known_build_offset_name(i, j); - uint64_t value = get_known_build_offset_value(i, j); - if (name) { - offsets[name] = value; - free_string(name); - } - } - - BuildMetadata metadata(hash, std::move(offsets), platform); - buildMap.emplace(std::to_string(hash), std::move(metadata)); - } - - return buildMap; -} \ No newline at end of file diff --git a/src/builds.hpp b/src/builds.hpp deleted file mode 100644 index a564bfd..0000000 --- a/src/builds.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "state/BuildMetadata.hpp" - -// Loads builds via Sleuth memory -std::map LoadBuildMetadata(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a37893b..46c9c6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,8 +12,10 @@ #include -extern "C" uint8_t generate_json(); - +struct RustBuildInfo; +extern "C" const RustBuildInfo* load_current_build_info(); +extern "C" uint8_t build_info_save(const void* bi); +extern "C" uint32_t build_info_get_file_hash(const void* bi); //black magic for the linker to get winsock2 to work //TODO: properly add this to the linker settings @@ -22,7 +24,6 @@ extern "C" uint8_t generate_json(); #include #include "constants.h" -#include "builds.hpp" #include "string_util.hpp" #include "logging/global_logger.hpp" @@ -181,15 +182,6 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { try { initialize_global_logger(LogLevel::TRACE); GLOG_INFO("Logger initialized."); - auto found_offsets = generate_json(); - GLOG_INFO("Sleuth found {} offsets", found_offsets); - - auto current_build_opt = BuildMetadata::FromSleuth(); - if (!current_build_opt) { - GLOG_ERROR("Failed to get build metadata from Sleuth memory"); - return 1; - } - BuildMetadata& current_build_metadata = *current_build_opt; auto cliArgs = CLIArgs::Parse(GetCommandLineW()); g_logger->set_level(cliArgs.log_level); @@ -213,16 +205,16 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { } GLOG_DEBUG("MinHook initialized"); - std::map loaded = LoadBuildMetadata(); - - uint32_t build_hash = current_build_metadata.GetFileHash(); - std::string build_hash_string = std::to_string(build_hash); - - if (!loaded.contains(build_hash_string)) { - GLOG_DEBUG("Current build hash {} not found in loaded metadata (expected if just scanned)", build_hash_string); + auto rust_build_info = load_current_build_info(); + if (rust_build_info == nullptr) { + GLOG_ERROR("Failed to get build info from Sleuth"); + return 1; } - auto state = new State(cliArgs, loaded, current_build_metadata); + uint32_t build_hash = build_info_get_file_hash(rust_build_info); + GLOG_INFO("Current build hash: {}", build_hash); + + auto state = new State(cliArgs); initialize_global_state(state); auto baseAddr = GetModuleHandleA("Chivalry2-Win64-Shipping.exe"); @@ -236,7 +228,7 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { GetModuleInformation(GetCurrentProcess(), baseAddr, &moduleInfo, sizeof(moduleInfo)); - PatchManager patch_manager(baseAddr, moduleInfo, current_build_metadata); + PatchManager patch_manager(baseAddr, moduleInfo, rust_build_info); for (auto& patch: ALL_REGISTERED_PATCHES) { patch_manager.register_patch(*patch); @@ -253,6 +245,7 @@ DWORD WINAPI main_thread(LPVOID lpParameter) { handleRCON(state->GetRCONState(), state->GetCLIArgs().rcon_port.value()); //this has an infinite loop for commands! Keep this at the end! } + build_info_save(rust_build_info); ExitThread(0); } catch (const std::exception& e) { std::string error = "std::exception: " + std::string(e.what()); diff --git a/src/patching/PatchManager.hpp b/src/patching/PatchManager.hpp index 09be33e..919d738 100644 --- a/src/patching/PatchManager.hpp +++ b/src/patching/PatchManager.hpp @@ -5,9 +5,13 @@ #include #include +#include #include "Patch.hpp" #include "../logging/global_logger.hpp" +struct RustBuildInfo; + +extern "C" uint64_t build_info_get_offset(const RustBuildInfo* info, const char* name); /** * The PatchManager keeps track of patches to be enabled @@ -16,16 +20,16 @@ class PatchManager { private: std::vector failed_patches; std::vector> pending_patches; - BuildMetadata& current_build_metadata; uintptr_t base_addr; MODULEINFO module_info; + const RustBuildInfo* build_info; public: - PatchManager(const HMODULE base_addr, const MODULEINFO &module_info, BuildMetadata& current_build_metadata) - :current_build_metadata(current_build_metadata) { + PatchManager(const HMODULE base_addr, const MODULEINFO &module_info, const RustBuildInfo* build_info) { this->base_addr = reinterpret_cast(base_addr); this->module_info = module_info; + this->build_info = build_info; this->failed_patches = {}; this->pending_patches = {}; } ; @@ -67,18 +71,18 @@ class PatchManager { GLOG_TRACE("Applying patch '{}'", patch_name); try { - const auto maybe_offset = this->current_build_metadata.GetOffset(patch_name); - if (!maybe_offset.has_value()) { + const uint64_t offset = build_info_get_offset(build_info, patch_name.c_str()); + if (offset == 0) { GLOG_ERROR("No offset found for patch '{}'", patch_name); if (batched) failed_patches.push_back(patch_name); return false; } - const auto result = patch->apply(base_addr + maybe_offset.value()); + const auto result = patch->apply(base_addr + offset); switch (result) { case APPLY_SUCCESS: - GLOG_INFO("0x{:X} : Successfully enabled patch '{}'", maybe_offset.value(), patch_name); + GLOG_INFO("0x{:X} : Successfully enabled patch '{}'", offset, patch_name); break; case APPLY_FAILED: GLOG_ERROR("Failed to enable patch '{}'", patch_name); diff --git a/src/state/BuildMetadata.cpp b/src/state/BuildMetadata.cpp deleted file mode 100644 index 977d999..0000000 --- a/src/state/BuildMetadata.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// -// Created by Fam on 5/27/2025. -// - -#include "BuildMetadata.hpp" - -#include -#include - -#include "../logging/global_logger.hpp" -#include "../string_util.hpp" - -BuildMetadata::BuildMetadata(uint32_t fileHash, std::map offsets, Platform platform) { - this->fileHash = fileHash; - this->offsets = std::move(offsets); - this->platform = platform; -} - -BuildMetadata::~BuildMetadata() {} - -void BuildMetadata::SetOffset(std::string name, uint64_t offset) { - if (offset == 0) return; - offsets[std::move(name)] = offset; -} - -std::optional BuildMetadata::GetOffset(const std::string &name) const { - if (auto it = offsets.find(name); it != offsets.end()) { - return it->second; - } - return std::nullopt; -} - -std::vector> BuildMetadata::GetOffsets() const { - std::vector> result; - result.reserve(offsets.size()); - - for (const auto& [name, offset] : offsets) { - result.emplace_back(name, offset); - } - - return result; -} - -void BuildMetadata::SetFileHash(uint32_t hash) { - fileHash = hash; -} - -uint32_t BuildMetadata::GetFileHash() const { - return fileHash; -} - -std::string BuildMetadata::GetBuildKey() const { - return std::format("{}", this->GetFileHash()); -} - -Platform BuildMetadata::GetPlatform() const { - return platform; -} - -extern "C" uint32_t get_file_hash(); -extern "C" char* get_platform(); -extern "C" size_t get_offset_count(); -extern "C" char* get_offset_name(size_t index); -extern "C" uint64_t get_offset_value(size_t index); -extern "C" void free_string(char* s); - -std::optional BuildMetadata::FromSleuth() { - uint32_t file_hash = get_file_hash(); - if (file_hash == 0) return std::nullopt; - - char* platform_str = get_platform(); - Platform platform = STEAM; - if (string_to_platform.contains(platform_str)) { - platform = string_to_platform.at(platform_str); - } - free_string(platform_str); - - size_t offset_count = get_offset_count(); - std::map offsets; - for (size_t i = 0; i < offset_count; ++i) { - char* name = get_offset_name(i); - uint64_t value = get_offset_value(i); - if (name) { - offsets[name] = value; - free_string(name); - } - } - - return BuildMetadata(file_hash, std::move(offsets), platform); -} \ No newline at end of file diff --git a/src/state/BuildMetadata.hpp b/src/state/BuildMetadata.hpp deleted file mode 100644 index 74a7b02..0000000 --- a/src/state/BuildMetadata.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../Platform.hpp" - -/** - * Build metadata is loaded from a json file at start, and then any unknown - * signatures are added as they are found. - */ -class BuildMetadata { - uint32_t fileHash = 0; - std::map offsets = {}; - Platform platform; -public: - BuildMetadata(uint32_t fileHash, std::map offsets, Platform platform); - ~BuildMetadata(); - - static std::optional FromSleuth(); - - void SetOffset(std::string name, uint64_t offset); - std::optional GetOffset(const std::string& name) const; - std::vector> GetOffsets() const; - - void SetFileHash(uint32_t hash); - uint32_t GetFileHash() const; - - std::string GetBuildKey() const; - - Platform GetPlatform() const; -}; diff --git a/src/state/State.hpp b/src/state/State.hpp index c169a34..32a180c 100644 --- a/src/state/State.hpp +++ b/src/state/State.hpp @@ -1,24 +1,22 @@ #pragma once #include +#include +#include -#include "BuildMetadata.hpp" #include "CLIArgs.hpp" #include "RCONState.hpp" class State { - CLIArgs args; // Changed from reference to object - BuildMetadata& current_build_metadata; // Changed from reference to object + CLIArgs args; void* uworld = nullptr; void* CurGameMode = nullptr; RCONState rcon_state; - std::map build_metadata; public: - // Updated constructor to take objects by value (copy) or by rvalue reference (move) - State(CLIArgs args, std::map all_build_metadata, BuildMetadata& current_build_metadata) - : args(std::move(args)), current_build_metadata(current_build_metadata), build_metadata(all_build_metadata){ + State(CLIArgs args) + : args(std::move(args)){ this->uworld = nullptr; this->rcon_state = RCONState(); } @@ -39,19 +37,9 @@ class State { return this->CurGameMode; } - // Updated getter to return const reference inline CLIArgs& GetCLIArgs() { return this->args; } - - // Updated getter to return const reference - inline BuildMetadata& GetBuildMetadata() const { - return this->current_build_metadata; - } - - inline std::map GetSavedBuildMetadata() const { - return this->build_metadata; - } inline RCONState& GetRCONState() { return this->rcon_state; diff --git a/src/string_util.hpp b/src/string_util.hpp index 329e988..2e99518 100644 --- a/src/string_util.hpp +++ b/src/string_util.hpp @@ -2,6 +2,7 @@ #include #include +#include inline auto quot = "\""; From 059a3d29b927c43455e62029b672e179be138e33 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Thu, 1 Jan 2026 18:31:27 -0600 Subject: [PATCH 004/219] Remove unused dependencies --- CMakeLists.txt | 1 - lib/Sig/Sig.hpp | 1142 --------------------------------- lib/tiny-json/CMakeLists.txt | 13 - lib/tiny-json/tiny-json.c | 461 ------------- lib/tiny-json/tiny-json.h | 176 ----- src/patching/PatchManager.hpp | 2 - 6 files changed, 1795 deletions(-) delete mode 100644 lib/Sig/Sig.hpp delete mode 100644 lib/tiny-json/CMakeLists.txt delete mode 100644 lib/tiny-json/tiny-json.c delete mode 100644 lib/tiny-json/tiny-json.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4aa2cf4..4f3d10c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,6 @@ set_target_properties(${PROJECT_NAME} PROPERTIES target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/lib/Sig ) target_compile_options(${PROJECT_NAME} PRIVATE /MP) diff --git a/lib/Sig/Sig.hpp b/lib/Sig/Sig.hpp deleted file mode 100644 index 6802596..0000000 --- a/lib/Sig/Sig.hpp +++ /dev/null @@ -1,1142 +0,0 @@ -#pragma once - -#include - - -#if (__cplusplus >= 202002) || _HAS_CXX20 -# define sig_has_cxx20 (1) -#else -# define sig_has_cxx20 (0) -#endif - - -struct Sig -{ - enum class Tag - { - val, - any, - pkg, - str, - raw, - rep, - set, - range, - compound - }; - - - struct Cmp - { - template - struct Eq - { - static bool cmp(const void* const pos) - { - return *static_cast(pos) == val; - } - }; - - template - struct Gr - { - static bool cmp(const void* const pos) - { - return *static_cast(pos) > val; - } - }; - - template - struct GrEq - { - static bool cmp(const void* const pos) - { - return *static_cast(pos) >= val; - } - }; - - template - struct Le - { - static bool cmp(const void* const pos) - { - return *static_cast(pos) < val; - } - }; - - template - struct LeEq - { - static bool cmp(const void* const pos) - { - return *static_cast(pos) <= val; - } - }; - - template - struct NotEq - { - static bool cmp(const void* const pos) - { - return *static_cast(pos) != val; - } - }; - - template - struct OneOf - { - static bool cmp(const void* const pos) - { - return (*static_cast(pos) & val) != 0; - } - }; - - template - struct AllOf - { - static bool cmp(const void* const pos) - { - return (*static_cast(pos) & val) == val; - } - }; - }; - - template - struct PackageHolder - { - }; - - - template typename Comparator, Type... values> - struct Holder - { - using BaseType = Type; - using Package = PackageHolder...>; - - static constexpr auto k_tag = Tag::pkg; - static constexpr auto k_count = sizeof...(values); - static constexpr auto k_size = k_count * sizeof(Type); - }; - static_assert(Holder::k_tag == Tag::pkg); - - template typename Comparator> - struct Holder - { - using BaseType = Type; - - static constexpr auto k_tag = Tag::any; - static constexpr auto k_count = 1; - static constexpr auto k_size = sizeof(Type); - }; - static_assert(Holder::k_tag == Tag::any); - - template typename Comparator, Type val> - struct Holder - { - using BaseType = Type; - using Cmp = Comparator; - - static constexpr auto k_tag = Tag::val; - static constexpr auto k_count = 1; - static constexpr auto k_size = sizeof(Type); - }; - static_assert(Holder::k_tag == Tag::val); - - template - struct RawCmp - { - using BaseType = Type; - - static constexpr auto k_tag = Tag::raw; - static constexpr auto k_count = count; - static constexpr auto k_size = sizeof(Type) * k_count; - }; - - template - struct Rep - { - using Type = Repeatable; - static constexpr auto k_tag = Tag::rep; - static constexpr auto k_count = count; - static constexpr auto k_size = k_count * Type::k_size; - }; - - template - struct Set - { - template - static constexpr auto maxof() - { - constexpr decltype(val) k_arr[sizeof...(values) + 1]{ val, values... }; - auto maxval = k_arr[0]; - for (size_t i = 1; i < sizeof...(values); ++i) - { - if (k_arr[i] > maxval) - { - maxval = k_arr[i]; - } - } - return maxval; - } - - static constexpr auto k_tag = Tag::set; - static constexpr auto k_size = maxof(); - static constexpr auto k_count = sizeof...(Entries); - }; - - template