diff --git a/Samson/dllmain.cpp b/Samson/dllmain.cpp new file mode 100644 index 0000000..210ba4c --- /dev/null +++ b/Samson/dllmain.cpp @@ -0,0 +1,376 @@ +#include +#include "CommonHeaders.h" +#include "CommonUEHeaders.h" + +using namespace SDK; + +// Constants +const std::string PLUGIN_NAME = "Samson"; +const std::string PLUGIN_LOG = PLUGIN_NAME + ".log"; +constexpr ULONGLONG DEFAULT_DELAY_BETWEEN_TICK = 100; // Used ProcessEvent +constexpr ULONGLONG DEFAULT_DELAY_BETWEEN_WTD_TICK = 100; // Used for World time dilation + +// Logger +std::shared_ptr logger; + +// Screen informations +static int screenWidth = GetSystemMetrics(SM_CXSCREEN); +static int screenHeight = GetSystemMetrics(SM_CYSCREEN); +float g_AspectRatio = (float)screenWidth / screenHeight; + +// Plugin states +static bool AOBScanDone = false; +static std::atomic g_fix_enabled = false; +static std::atomic g_fov_fix_enabled = false; +static std::atomic g_HUD_fix_enabled = false; +static std::atomic g_Camera_fix_enabled = false; +static std::atomic g_Letterboxing_fix_enabled = false; +static std::atomic g_CA_fix_enabled = false; +static std::atomic g_Vignetting_fix_enabled = false; +static std::atomic g_Fog_fix_enabled = false; +static std::atomic g_TimeDilation_fix_enabled = false; +static std::atomic g_GodMode_fix_enabled = false; +static std::atomic g_Stealth_fix_enabled = false; +static int g_AdditionalFOVValue = 0; +static float g_CameraMultiplier = 1.f; +static float g_WorldTimeDilationValue = 1.f; +static float g_AITimeDilationValue = 1.f; +static int g_HUDOffsets = 0; +static int g_UIOffsets = 0; +static float g_PlayerHealth = 0.f; +// Shared values +static float g_FOV_In = 75.f; +static float g_FOV_Out = 75.f; +static float g_CameraIn = 189.26f; +static float g_CameraOut = 189.26f; +// AOB Scan pointers +static uint8_t* CameraComponentaddress = nullptr; +static uint8_t* Cameraaddress = nullptr; +static uint8_t* WorldTimedilationaddress = nullptr; +static uint8_t* Timedilationaddress = nullptr; +// Hooking +static SafetyHookMid FOVHook{}; +static SafetyHookMid CameraHook{}; +static SafetyHookMid PEHook{}; +static SafetyHookMid WorldTimeDilationHook{}; +static SafetyHookMid TimeDilationHook{}; +// Prototypes +static void FOVFixEnabled(); +static void CameraFixEnabled(); +static void HUDUpdate(bool writeLog); +static void EnableConsole(); +static void EnableCheats(Cheat cheat); +static void ProcessEvent(); + +extern "C" __declspec(dllexport) void SetFixEnabled(bool enabled, bool init) { + g_fix_enabled = enabled; + if (init) { + logger = InitializeLogger("Samson", PLUGIN_LOG); + logger->info("Plugin {} loaded.", PLUGIN_NAME); + } + if (!AOBScanDone) { // Unreal Engine 5.7.4 + logger->info("--------------- AOB scan started ---------------"); + constexpr auto CameraComponentStringObfuscated = make_obfuscated<0xF3>("EB ?? F3 0F ?? ?? ?? ?? ?? ?? F3 0F ?? ?? ?? 8B 83 ?? ?? ?? ?? 89"); + constexpr auto CameraStringObfuscated = make_obfuscated<0xF3>("48 8D ?? ?? 0F ?? ?? 48 8D 8D ?? ?? ?? ?? 0F ?? ?? 0F ?? ?? F2"); + constexpr auto WorldTimeDilationStringObfuscated = make_obfuscated<0xF6>("F6 81 ?? ?? ?? ?? ?? 74 ?? F3 0F ?? ?? ?? ?? ?? ?? F3 0F ?? ?? ?? ?? ?? ?? F3 0F ?? ?? ?? ?? ?? ?? C3"); + constexpr auto TimeDilationStringObfuscated = make_obfuscated<0x44>("F3 0F ?? ?? ?? EB ?? F3 0F ?? ?? ?? ?? ?? ?? 48 8B ?? ?? 4C ?? ?? F3 0F ?? ?? 44"); + + using AOBScan::Make; + using OffsetScan::Make; + // Prepare all data for scanning + std::vector signatures = { + Make(&CameraComponentaddress, CameraComponentStringObfuscated, "Camera component"), + Make(&Cameraaddress, CameraStringObfuscated, "Camera"), + Make(&WorldTimedilationaddress, WorldTimeDilationStringObfuscated, "World time dilation"), + Make(&Timedilationaddress, TimeDilationStringObfuscated, "Actor time dilation"), + }; + // Scan all signature in a batch + Memory::AOBScanBatch(signatures, logger); + + if (CameraComponentaddress && Cameraaddress && WorldTimedilationaddress && Timedilationaddress) + logger->info("All AOB signatures found. Ready to patch..."); + + if (!GObjectsaddress || !AppendStringaddress || !ProcessEventaddress) { + logger->info("------------ UEngine offsets search ------------"); + uint8_t* baseModule = reinterpret_cast(GetModuleHandleA(nullptr)); // Get game base address + + constexpr auto GObjetcsStringObfuscated = make_obfuscated<0xC7>("48 8D 05 ?? ?? ?? ?? C7 83 ?? ?? ?? ?? ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ?? 48 89 05 ?? ?? ?? ?? 48"); + constexpr auto GWorldStringObfuscated = make_obfuscated<0x5B>("48 8B 05 ?? ?? ?? ?? 48 ?? ?? 75 ?? 48 83 ?? ?? 5B C3"); + constexpr auto AppendStringStringObfuscated = make_obfuscated<0x80>("48 89 ?? ?? ?? 48 89 ?? ?? ?? 57 48 83 ?? ?? 80 3D ?? ?? ?? ?? ?? 48 ?? F2 8B ?? 48 ?? ?? 74 ?? 4C 8D ?? ?? ?? ?? ?? EB ?? 48 8D ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C"); + constexpr auto ProcessEventStringObfuscated = make_obfuscated<0x56>("40 ?? 56 57 41 ?? 41 ?? 41 ?? 41 ?? 48 81 ?? ?? ?? ?? ?? 48 8D ?? ?? ?? 48 89 ?? ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 48 ?? ?? 48 89 ?? ?? ?? ?? ?? 8B 41"); + + // Prepare all data for scanning + std::vector UEoffsetsScans = { + Make(&GObjectsaddress, GObjetcsStringObfuscated, "GObjects", OffsetCalcType::GetOffsetFromOpcode, &Offsets::GObjects, 0x3), + Make(&GWorldaddress, GWorldStringObfuscated, "GWorld", OffsetCalcType::GetOffsetFromOpcode, &Offsets::GWorld, 0x3), + Make(&AppendStringaddress, AppendStringStringObfuscated, "AppendString", OffsetCalcType::UE_CalculateOffset, &Offsets::AppendString), + Make(&ProcessEventaddress, ProcessEventStringObfuscated, "ProcessEvent", OffsetCalcType::UE_CalculateOffset, &Offsets::ProcessEvent) + }; + // Retrieve all Unreal Engine offsets in a batch + Memory::OffsetScanBatch(UEoffsetsScans, baseModule, logger, ""); + } + logger->info("-------------- Fixes initialisation -------------"); + AOBScanDone = true; + } + + ProcessEvent(); + if (init) return; + FOVFixEnabled(); + CameraFixEnabled(); + HUDUpdate(false); + SetAllEffectsToBeToggled(); + LogFixToggle(GameFixes::None, g_fix_enabled); +} + +// Setters for Reshade addon call +extern "C" __declspec(dllexport) void SetFixesEnabled(GameFixes fix, bool enabled) { // Set each fix individually + bool bVisual = g_fix_enabled && enabled; + if (fix == GameFixes::DevConsole) { EnableConsole(); } + if (fix == GameFixes::FOV) { g_fov_fix_enabled = enabled; FOVFixEnabled(); } + if (fix == GameFixes::Camera) { g_Camera_fix_enabled = enabled; CameraFixEnabled(); } + if (fix == GameFixes::Cutscenes) { g_Letterboxing_fix_enabled = enabled; } + if (fix == GameFixes::ChromaticAberrations) { g_CA_fix_enabled = enabled; gPendingCA = true; LogFixToggle(fix, bVisual); } + if (fix == GameFixes::Vignetting) { g_Vignetting_fix_enabled = enabled; gPendingVignetting = true; LogFixToggle(fix, bVisual); } + if (fix == GameFixes::Fog) { g_Fog_fix_enabled = enabled; gPendingFog = true; LogFixToggle(fix, bVisual); } + if (fix == GameFixes::HUD) { g_HUD_fix_enabled = enabled; HUDUpdate(true); } + if (fix == GameFixes::TimeDilation) { g_TimeDilation_fix_enabled = enabled; EnableCheats(Cheat::TimeDilation); } + if (fix == GameFixes::GodMode) { g_GodMode_fix_enabled = enabled; EnableCheats(Cheat::GodMode); } + if (fix == GameFixes::Stealth) { g_Stealth_fix_enabled = enabled; EnableCheats(Cheat::Stealth); } +} + +extern "C" __declspec(dllexport) void SetValues(GameSetting setting, float value) { + if (setting == GameSetting::FOV) g_AdditionalFOVValue = (int)(value); + if (setting == GameSetting::HUD) { + g_HUDOffsets = (value * screenWidth) / 100; + HUDUpdate(false); + } + if (setting == GameSetting::UI) { + g_UIOffsets = (value * screenWidth) / 100; + HUDUpdate(false); + } + if (setting == GameSetting::CameraDistance) g_CameraMultiplier = value; + if (setting == GameSetting::WorldTimeDilation) g_WorldTimeDilationValue = value; + if (setting == GameSetting::AITimeDilation) g_AITimeDilationValue = value; +} +// Getters for Reshade addon call +extern "C" __declspec(dllexport) void GetGameInfos(GameInfos* infos) { + if (!infos) return; + infos->FOVIn = g_FOV_In; + infos->FOVOut = g_FOV_Out; + infos->cameraIn = g_CameraIn; + infos->cameraOut = g_CameraOut; + infos->Health = g_PlayerHealth; + infos->screenWidth = screenWidth; + infos->screenHeight = screenHeight; + infos->aspectRatio = (float)screenWidth / screenHeight; + infos->consoleEnabled = g_Console_Enabled; +} + +// -- Code injection functions -- +// Tracking sets +std::unordered_set g_TrackedCenteredUIWidgets; +std::unordered_set g_TrackedLeftSidedWidgets; +std::unordered_set g_TrackedRightSidedWidgets; +// Left, right sided and centered widgets +std::unordered_set g_LeftSidedWidgets = { + //"WBP_PlayerHUD_C" + "WBP_MiniMap_C", + "WBP_HUD_HealthStatus_C", // Health pills and more + "WBP_HUD_CurrentLocation_C", // Location bar (vies ...) + //"WBP_HUD_ProgressBar_C", // Health bar etc + //"WBP_HUD_Objective_C", // ?? + //"WBP_HUD_VehicleWidget_C", // Compte tour etc... + //"WBP_TailOnFoot_C", // ?? + //"WBP_HUD_Mission_C", // ?? + //"WBP_HUD_HudIcons_C", // Crosshair + "WBP_HUD_MissionGameplay_C", + //"WBP_MissionGameplay_C", // ?? + //"WBP_HUD_Skills_C", + "WBP_HUD_HintItem_C", // Hint voiture à garder + "WBP_LocateItem_C", // Location in the map + "WBP_TimeTrialTimeItem_C", + //"WBP_HUD_Timer_C" + //"WBP_HUD_MissionGameplay_C", // ? + //"WBP_OnboardingInstruction_C" // Instructions à virer +}; +std::unordered_set g_CenteredUIWidgets = { // WBP_CrimeScene_Map_C + "WBP_StartMenu_C", + "WBP_GameMenu_C" +}; +std::unordered_set g_RightSidedWidgets = { + "WBP_HUD_VehicleWidget_C", "WBP_HUD_HourToHour_C", +}; + +static UUserWidget* letterBoxWidget = nullptr; +static ULONGLONG lastScanTick = 0; // Last time tick was called +static void ProcessEvent() { + if (!PEHook && ProcessEventaddress) { + PEHook = safetyhook::create_mid(ProcessEventaddress + 0xc, + [](SafetyHookContext& ctx) { + ULONGLONG now = GetTickCount64(); + if (now - lastScanTick >= DEFAULT_DELAY_BETWEEN_TICK) { // Delay between each tick to avoid ProcessEvent stuttering + lastScanTick = now; + GetResolution(screenWidth, screenHeight, g_AspectRatio); + HUDUpdate(false); + } + + UObject* object = (UObject*)ctx.rcx; + UFunction* func = (UFunction*)ctx.rdx; + if (!object || !func) return; + + const std::string funcName = func->GetName(); + const std::string className = object->Class->GetName(); + const std::string objectFullName = object->GetFullName(); + + //if (className == "WBP_HUD_ProgressBar_C" && funcName != "Tick") // Debug + // logger->debug("WBP_HUD_ProgressBar_C - {} - function: {}",object->GetFullName(), funcName); + + if (object->IsA(UUserWidget::StaticClass())) { + auto* widget = static_cast(object); + + if (funcName == "Construct") { + TrackWidgetConstruct(widget); + + if (g_LeftSidedWidgets.contains(className)) g_TrackedLeftSidedWidgets.insert(widget); + if (objectFullName.find("ExitProgressBar") != std::string::npos) { + g_TrackedLeftSidedWidgets.insert(widget); + logger->debug("ExitProgressBar tracked"); + } + if (g_RightSidedWidgets.contains(className)) g_TrackedRightSidedWidgets.insert(widget); + if (g_CenteredUIWidgets.contains(className)) g_TrackedCenteredUIWidgets.insert(widget); + + if (g_fix_enabled && g_HUD_fix_enabled) + HUDUpdate(false); // Called once + if (className == "WBP_Letterbox_Overlay_C") { + letterBoxWidget = widget; + if (UKismetSystemLibrary::IsValid(widget) && g_fix_enabled && g_Letterboxing_fix_enabled) + widget->SetVisibility(ESlateVisibility::Collapsed); + } + } + + if (funcName == "Destruct") { + TrackWidgetDestruct(widget); + g_TrackedLeftSidedWidgets.erase(widget); + g_TrackedRightSidedWidgets.erase(widget); + g_TrackedCenteredUIWidgets.erase(widget); + if (className == "WBP_Letterbox_Overlay_C") letterBoxWidget = nullptr; + } + } + }); + } +} +// -- HUD positionning -- +static void HUDUpdate(bool writeLog) { + //return; + if (writeLog) logger->info("HUD fix {}", g_fix_enabled && g_HUD_fix_enabled ? "enabled" : "disabled"); + float UIoffset = g_fix_enabled && g_HUD_fix_enabled ? g_UIOffsets : 0; + float HUDoffset = g_fix_enabled && g_HUD_fix_enabled ? g_HUDOffsets : 0; + + // Copy sets to avoid race conditions + std::unordered_set copyLeftWidgets = g_TrackedLeftSidedWidgets; + std::unordered_set copyRightWidgets = g_TrackedRightSidedWidgets; + + for (auto* widget : copyLeftWidgets) { + if (!UKismetSystemLibrary::IsValid(widget)) continue; // Ensure the widget is valid + ApplyTransformOffset(widget, HUDoffset); + } + for (auto* widget : copyRightWidgets) { + if (!UKismetSystemLibrary::IsValid(widget)) continue; // Ensure the widget is valid + ApplyTransformOffset(widget, -HUDoffset); + } +} + +static void FOVFixEnabled() { + //if (g_fov_fix_enabled) // Here to track Widgets + // DumpUIAnalysis(logger); + //else ClearWidgetTracking(); + if (!CameraComponentaddress) return; + if (!FOVHook) { // Hook only once + FOVHook = safetyhook::create_mid(CameraComponentaddress + 0xa, + [](SafetyHookContext& ctx) { + g_FOV_In = ctx.xmm0.f32[0]; + ctx.xmm0.f32[0] += g_fix_enabled && g_fov_fix_enabled ? g_AdditionalFOVValue : 0.f; + g_FOV_Out = ctx.xmm0.f32[0]; + }); + } + logger->info("FOV fix {}", g_fix_enabled && g_fov_fix_enabled ? "enabled" : "disabled"); +} + +static void CameraFixEnabled() { + if (!Cameraaddress) return; + if (g_fix_enabled && g_Camera_fix_enabled) { + if (!CameraHook) { + CameraHook = safetyhook::create_mid(Cameraaddress, + [](SafetyHookContext& ctx) { + g_CameraIn = ctx.xmm1.f32[0]; + ctx.xmm1.f32[0] *= g_fix_enabled && g_Camera_fix_enabled ? g_CameraMultiplier : 1.f; + g_CameraOut = ctx.xmm1.f32[0]; + }); + } + else CameraHook.enable(); + } + else if (CameraHook) CameraHook.disable(); + + logger->info("Camera distance fix {}", g_fix_enabled && g_Camera_fix_enabled ? "enabled" : "disabled"); +} + +// -- Cheats -- +static ULONGLONG lastScanWTDTick = 0; // Last time tick was called +static void EnableCheats(Cheat cheat) { + if (WorldTimedilationaddress && !WorldTimeDilationHook) { + WorldTimeDilationHook = safetyhook::create_mid(WorldTimedilationaddress + 0x19, + [](SafetyHookContext& ctx) { // From AWorldSettings retrieved from world->K2_GetWorldSettings() + ctx.xmm0.f32[0] *= g_TimeDilation_fix_enabled ? g_WorldTimeDilationValue : 1.f; + + ULONGLONG now = GetTickCount64(); + if (now - lastScanWTDTick >= DEFAULT_DELAY_BETWEEN_WTD_TICK) { // Delay between each tick to avoid ProcessEvent stuttering + lastScanWTDTick = now; + + if (UKismetSystemLibrary::IsValid(letterBoxWidget)) + letterBoxWidget->SetVisibility(g_fix_enabled && g_Letterboxing_fix_enabled ? + ESlateVisibility::Collapsed : ESlateVisibility::Visible); + } + + // Apply visual effects only in hook main thread to ensure they will be applied correctly + if (!g_Console_Enabled) return; // It relies on dev console being reactivated + if (gPendingFog.exchange(false)) + ApplyVisualEffect(GameFixes::Fog, g_fix_enabled && g_Fog_fix_enabled); + if (gPendingCA.exchange(false)) + ApplyVisualEffect(GameFixes::ChromaticAberrations, g_fix_enabled && g_CA_fix_enabled); + if (gPendingVignetting.exchange(false)) + ApplyVisualEffect(GameFixes::Vignetting, g_fix_enabled && g_Vignetting_fix_enabled); + }); + } + // Enemies time dilation + if (Timedilationaddress && !TimeDilationHook) { + TimeDilationHook = safetyhook::create_mid(Timedilationaddress, + [](SafetyHookContext& ctx) { + if (!ctx.rbx) return; + + UObject* object = (UObject*)ctx.rbx; + if (!object || !object->Class) return; + }); + } + + if (cheat == Cheat::TimeDilation) logger->info("Time dilation cheat {}", g_TimeDilation_fix_enabled ? "enabled" : "disabled"); + if (cheat == Cheat::GodMode) logger->info("God mode cheat {}", g_GodMode_fix_enabled ? "enabled" : "disabled"); + if (cheat == Cheat::Stealth) logger->info("Stealth cheat {}", g_Stealth_fix_enabled ? "enabled" : "disabled"); +} + +// UE Console creation +static void EnableConsole() { + if (g_Console_Enabled || !GObjectsaddress || !AppendStringaddress || !ProcessEventaddress) + return; + + logger->info("-------------- Console re-enabling --------------"); + ReactivateDevConsole(logger); +} + +// Standard dll entry +BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) { + if (reason == DLL_PROCESS_DETACH) { + logger->info("Plugin {} unloaded.", PLUGIN_NAME); + spdlog::drop_all(); + } + return TRUE; +} \ No newline at end of file