diff --git a/data/plugins/GTAIV.EFLC.FusionFix.ini b/data/plugins/GTAIV.EFLC.FusionFix.ini index 627416d0..9027ea95 100644 --- a/data/plugins/GTAIV.EFLC.FusionFix.ini +++ b/data/plugins/GTAIV.EFLC.FusionFix.ini @@ -9,6 +9,7 @@ OverrideCascadeRanges = 1 // increases shadow view distance ShadowBlendRange = 0.3 // controls the size of the cascade blending regions | [0.0; 1.0] ForceShadowFilter = 0 // 0 : shadow filter tied to definition | 1 : force 4 sample filter | 2 : force 16 sample filter HighResolutionShadows = 0 // doubles cascaded shadowmap resolution, very GPU intensive +HighResolutionNightShadows = 0 // increases night shadows resolution, extremely GPU intensive [SHADOWFILTERSHARP] // CE-like shadows ShadowSoftness = 1.5 // controls shadow blur @@ -18,9 +19,8 @@ ShadowBias = 5.0 // controls shadow bias, adjust ac ShadowSoftness = 3.0 // controls shadow blur ShadowBias = 8.0 // controls shadow bias, adjust according to softness -[NIGHTSHADOWS] // WARNING: enabling any of these options is not recommended -HeadlightShadows = 0 // 0: headlights do not cast shadows, like consoles | 1: forces all headlights to cast shadows, like PC -VehicleNightShadows = 0 // 1: enables shadows cast by vehicles from artificial lights, do not use with HeadlightShadows = 1 +[NIGHTSHADOWS] // WARNING: enabling this option is not recommended +VehicleNightShadows = 0 // 1: with Headlight Shadows option, casts vehicle night shadows and disables player shadow(to avoid bugs), without Headlight Shadows enables shadows cast by vehicles from artificial lights [FRAMELIMIT] FrameLimitType = 2 // 1: realtime (thread-lock) | 2: accurate (sleep-yield), uses less resources diff --git a/data/update/TBoGT/common/data/frontend_menus.xml b/data/update/TBoGT/common/data/frontend_menus.xml index 47a8b54b..b1a4c41e 100644 --- a/data/update/TBoGT/common/data/frontend_menus.xml +++ b/data/update/TBoGT/common/data/frontend_menus.xml @@ -1116,6 +1116,7 @@ + + + @@ -1216,6 +1217,7 @@ + diff --git a/data/update/common/data/frontend_menus.xml b/data/update/common/data/frontend_menus.xml index 735a109e..f5dc9bb8 100644 --- a/data/update/common/data/frontend_menus.xml +++ b/data/update/common/data/frontend_menus.xml @@ -786,6 +786,7 @@ + @@ -814,6 +815,7 @@ + diff --git a/external/modupdater b/external/modupdater index f0b371be..5fb99622 160000 --- a/external/modupdater +++ b/external/modupdater @@ -1 +1 @@ -Subproject commit f0b371be2fc8e450bbeae948a63ded9971142c8e +Subproject commit 5fb9962268d57d1ba549e273e020186e6e0eb97f diff --git a/source/comvars.ixx b/source/comvars.ixx index db7829ba..4fbdb1e0 100644 --- a/source/comvars.ixx +++ b/source/comvars.ixx @@ -1042,6 +1042,7 @@ export int bMenuNeedsUpdate2 = 0; export bool bEnableSnow = false; export bool bEnableHall = false; export bool bFixAutoExposure = true; +export bool bHeadlightShadows = false; export inline LONG getWindowWidth() { diff --git a/source/consoleshadows.ixx b/source/consoleshadows.ixx index b8c08c4f..926f9208 100644 --- a/source/consoleshadows.ixx +++ b/source/consoleshadows.ixx @@ -10,11 +10,10 @@ import comvars; void* fnAE3DE0 = nullptr; void* fnAE3310 = nullptr; -bool bHeadlightShadows = true; bool bVehicleNightShadows = false; int __cdecl sub_AE3DE0(int a1, int a2) { - if (bVehicleNightShadows && !bHeadlightShadows) + if (bVehicleNightShadows) injector::cstd::call(fnAE3310, a1, 0, 0, 0, a2); return injector::cstd::call(fnAE3DE0, a1, a2); } @@ -29,6 +28,29 @@ void __stdcall grcSetRenderStateHook() } } +namespace CShadows +{ + injector::hook_back hbStoreStaticShadow; + + void __cdecl StoreStaticShadowPlayerDriving(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, int a14, int a15) + { + if (!bHeadlightShadows) + { + a3 &= ~3; + a3 &= ~4; + } + + return hbStoreStaticShadow.fun(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + } + + void __cdecl StoreStaticShadowNPC(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, int a14, int a15) + { + a3 &= ~3; + a3 &= ~4; + + return hbStoreStaticShadow.fun(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + } +} class ConsoleShadows { public: @@ -37,7 +59,6 @@ public: FusionFix::onInitEventAsync() += []() { CIniReader iniReader(""); - bHeadlightShadows = iniReader.ReadInteger("NIGHTSHADOWS", "HeadlightShadows", 1) != 0; bVehicleNightShadows = iniReader.ReadInteger("NIGHTSHADOWS", "VehicleNightShadows", 0) != 0; // Render dynamic shadows casted by vehicles from point lights. @@ -57,46 +78,68 @@ public: sh_grcSetRendersState = safetyhook::create_inline(pattern.get_first(0), grcSetRenderStateHook); } - // Enable player/ped shadows while in vehicles - if (bVehicleNightShadows && !bHeadlightShadows) + // Headlight shadows { - auto pattern = hook::pattern("75 14 F6 86 ? ? ? ? ? 74 0B 80 7C 24 ? ? 0F 84 ? ? ? ? C6 44 24"); - if (!pattern.empty()) + auto pattern = hook::pattern("68 04 05 00 00 6A 02 6A 00"); + if (!pattern.count(2).empty()) { - injector::WriteMemory(pattern.get_first(0), 0xEB, true); - pattern = hook::pattern("75 12 8B 86 ? ? ? ? C1 E8 0B 25 ? ? ? ? 89 44 24 0C 85 D2"); - injector::WriteMemory(pattern.get_first(0), 0xEB, true); + CShadows::hbStoreStaticShadow.fun = injector::MakeCALL(pattern.count(2).get(0).get(9), CShadows::StoreStaticShadowPlayerDriving).get(); + CShadows::hbStoreStaticShadow.fun = injector::MakeCALL(pattern.count(2).get(1).get(9), CShadows::StoreStaticShadowPlayerDriving).get(); } - else + + pattern = hook::pattern("68 04 01 00 00 6A 02 6A 00"); + if (!pattern.count(2).empty()) { - pattern = hook::pattern("75 17 F6 86 ? ? ? ? ? 74 0E 80 7C 24 ? ? 0F 84"); - injector::WriteMemory(pattern.get_first(0), 0xEB, true); - pattern = hook::pattern("75 0F 8B 86 ? ? ? ? C1 E8 0B 24 01 88 44 24 0E"); - injector::WriteMemory(pattern.get_first(0), 0xEB, true); + CShadows::hbStoreStaticShadow.fun = injector::MakeCALL(pattern.count(2).get(0).get(9), CShadows::StoreStaticShadowNPC).get(); + CShadows::hbStoreStaticShadow.fun = injector::MakeCALL(pattern.count(2).get(1).get(9), CShadows::StoreStaticShadowNPC).get(); } + + FusionFixSettings.SetCallback("PREF_HEADLIGHTSHADOWS", [](int32_t value) + { + bHeadlightShadows = value; + }); + bHeadlightShadows = FusionFixSettings("PREF_HEADLIGHTSHADOWS"); - } - - // Disable headlight shadows to avoid flickering/self-shadowing. - if (!bHeadlightShadows) - { - auto pattern = hook::pattern("74 76 FF 75 30 FF 75 2C FF 75 28 83 EC 0C 80 7D 38 00"); + pattern = hook::pattern("E8 ? ? ? ? 85 C0 74 29 6A 00"); if (!pattern.empty()) { - injector::WriteMemory(pattern.get_first(0), 0xEB, true); - pattern = hook::pattern("68 ? ? ? ? 6A 02 6A 00 E8 ? ? ? ? 83 C4 40 8B E5 5D C3 68"); - injector::WriteMemory(pattern.count(2).get(1).get(1), 0x100, true); - pattern = hook::pattern("8B E5 5D C3 68 ? ? ? ? 6A 02 6A 00 E8 ? ? ? ? 83 C4 40 8B E5 5D C3"); - injector::WriteMemory(pattern.count(2).get(1).get(5), 0x100, true); - } - else - { - pattern = hook::pattern("0F 84 ? ? ? ? 80 7D 28 00 74 4C 8B 45 20 8B 0D"); - injector::WriteMemory(pattern.get_first(0), 0xE990, true); - pattern = hook::pattern("68 ? ? ? ? 6A 02 6A 00 E8 ? ? ? ? 83 C4 40 5B 8B E5 5D C3 8B 15"); - injector::WriteMemory(pattern.get_first(1), 0x100, true); - pattern = hook::pattern("8D 44 24 50 50 68 ? ? ? ? 6A 02 6A 00 E8 ? ? ? ? 83 C4 40 5B 8B E5 5D C3"); - injector::WriteMemory(pattern.get_first(6), 0x100, true); + static auto getLocalPlayerPed = (int (*)())injector::GetBranchDestination(pattern.get_first(0)).as_int(); + static auto FindPlayerCar = (int (*)())injector::GetBranchDestination(pattern.get_first(11)).as_int(); + + static auto loc_AE3867 = (uintptr_t)hook::get_pattern("8B 74 24 14 FF 44 24 10"); + static auto loc_AE376B = (uintptr_t)hook::get_pattern("85 D2 75 4C 0F B6 46 62 50"); + static auto loc_AE374F = (uintptr_t)hook::get_pattern("C6 44 24 ? ? 83 F8 04 75 12"); + + pattern = hook::pattern("83 F8 03 75 14 F6 86"); + struct ShadowsHook + { + void operator()(injector::reg_pack& regs) + { + if (bHeadlightShadows && bVehicleNightShadows) + { + auto car = FindPlayerCar(); + + // Disable player/car shadows + if (regs.esi && (regs.esi == car || (regs.esi == getLocalPlayerPed() && car && *(uint32_t*)(car + 0xFA0)))) + { + *(uintptr_t*)(regs.esp - 4) = loc_AE3867; + return; + } + } + + // Enable player/ped shadows while in vehicles + if (bHeadlightShadows && bVehicleNightShadows && (regs.eax == 3 || regs.eax == 4)) + { + *(uintptr_t*)(regs.esp - 4) = loc_AE376B; + return; + } + + if ((*(uint8_t*)(regs.esi + 620) & 4) == 0) + { + *(uintptr_t*)(regs.esp - 4) = loc_AE374F; + } + } + }; injector::MakeInline(pattern.get_first(0), pattern.get_first(14)); } } }; diff --git a/source/extrainfo.ixx b/source/extrainfo.ixx index 3e5fa97a..58ffef94 100644 --- a/source/extrainfo.ixx +++ b/source/extrainfo.ixx @@ -49,11 +49,21 @@ public: if (imgNum >= imgArrSize) extra += FF_WARN1[0] ? FF_WARN1 : L"; ~r~WARNING: 255 IMG limit exceeded, will cause streaming issues."; static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); - if (LamppostShadows->get()) + if (LamppostShadows->get() || bHeadlightShadows) { extra += L"~n~"; extra += L" "; - auto FF_WARN2 = CText::getText("FF_WARN2"); + auto FF_WARN2 = std::wstring(CText::getText("FF_WARN2")); + auto HeadlightShadow = CText::getText("HeadlightShadow"); + + if (bHeadlightShadows) + { + auto pos = FF_WARN2.find(L": "); + if (pos != std::wstring::npos) { + FF_WARN2.replace(pos, 2, L": " + std::wstring(HeadlightShadow) + L" / "); + } + } + if (FF_WARN2[0]) extra += FF_WARN2; else diff --git a/source/fixes.ixx b/source/fixes.ixx index a296ff7f..e7888a3c 100644 --- a/source/fixes.ixx +++ b/source/fixes.ixx @@ -56,32 +56,6 @@ public: return r; } - static inline SafetyHookInline shsub_925DB0{}; - static int __cdecl sub_925DB0(int a1, int a2, int flags) - { - static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); - if (!LamppostShadows->get()) - { - if (!Natives::IsInteriorScene()) - return -1; - } - - return shsub_925DB0.ccall(a1, a2, flags); - } - - static inline SafetyHookInline shsub_D77A00{}; - static void __fastcall sub_D77A00(void* _this, void* edx) - { - static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); - if (!LamppostShadows->get()) - { - if (!Natives::IsInteriorScene()) - return; - } - - return shsub_D77A00.fastcall(_this, edx); - } - Fixes() { FusionFix::onInitEventAsync() += []() @@ -436,36 +410,6 @@ public: if (!pattern.empty()) injector::MakeNOP(pattern.get_first(0), 8, true); } - - // Lampposts shadows workaround - { - auto pattern = hook::pattern("80 3D ? ? ? ? ? 75 04 83 C8 FF"); - shsub_925DB0 = safetyhook::create_inline(pattern.get_first(), sub_925DB0); - - pattern = find_pattern("83 EC 3C 80 3D ? ? ? ? ? 56 8B F1", "83 EC 3C 53 33 DB"); - shsub_D77A00 = safetyhook::create_inline(pattern.get_first(0), sub_D77A00); - - pattern = find_pattern("8B 55 20 F6 C1 06"); - if (!pattern.empty()) - { - static auto ShadowsHook2 = safetyhook::create_mid(pattern.get_first(0), [](SafetyHookContext& regs) - { - static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); - if (!LamppostShadows->get()) - { - if (Natives::IsInteriorScene()) - { - if ((*(uint32_t*)(regs.edi + 0x4C) & 0x8000000) != 0) // new flag to detect affected lampposts - { - regs.ecx &= ~3; - regs.ecx &= ~4; - *(uint32_t*)(regs.esp + 0x18) = regs.ecx; - } - } - } - }); - } - } // Render LOD lights during cutscenes (console behavior) { diff --git a/source/settings.ixx b/source/settings.ixx index 1ac52029..fa57ed49 100644 --- a/source/settings.ixx +++ b/source/settings.ixx @@ -228,6 +228,7 @@ public: { 0, "PREF_UPDATE", "UPDATE", "CheckForUpdates", "", 0, nullptr, 0, 1 }, { 0, "PREF_BLOCKONLOSTFOCUS", "MAIN", "BlockOnLostFocus", "", 0, nullptr, 0, 1 }, { 0, "PREF_LAMPPOSTSHADOWS", "SHADOWS", "LamppostShadows", "", 0, nullptr, 0, 1 }, + { 0, "PREF_HEADLIGHTSHADOWS", "SHADOWS", "HeadlightShadows", "", 0, nullptr, 0, 1 }, // Enums are at capacity, to use more enums, replace multiplayer ones. On/Off toggles should still be possible to add. }; diff --git a/source/shadows.ixx b/source/shadows.ixx index c00a5ff7..f979a627 100644 --- a/source/shadows.ixx +++ b/source/shadows.ixx @@ -8,6 +8,7 @@ import ; import common; import comvars; import settings; +import natives; int32_t bExtraDynamicShadows; std::string curModelName; @@ -77,6 +78,7 @@ void __cdecl CBaseModelInfo__setFlagsHook(void* pModel, int dwFlags, int a3) return injector::cstd::call(CBaseModelInfo__setFlags, pModel, dwFlags, a3); } +bool bHighResolutionNightShadows = false; int GetNightShadowQuality() { switch (FusionFixSettings.Get("PREF_SHADOW_DENSITY")) @@ -85,13 +87,13 @@ int GetNightShadowQuality() return 0; break; case 1: //MO_MED - return 256; + return 256 * (bHighResolutionNightShadows ? 2 : 1); break; case 2: //MO_HIGH - return 512; + return 512 * (bHighResolutionNightShadows ? 2 : 1); break; case 3: //MO_VHIGH - return 1024; + return 1024 * (bHighResolutionNightShadows ? 2 : 1); break; default: return 0; @@ -101,6 +103,32 @@ int GetNightShadowQuality() class Shadows { + static inline SafetyHookInline shsub_925DB0{}; + static int __cdecl sub_925DB0(int a1, int a2, int flags) + { + static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); + if (!LamppostShadows->get() && !bHeadlightShadows) + { + if (!Natives::IsInteriorScene()) + return -1; + } + + return shsub_925DB0.ccall(a1, a2, flags); + } + + static inline SafetyHookInline shsub_D77A00{}; + static void __fastcall sub_D77A00(void* _this, void* edx) + { + static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); + if (!LamppostShadows->get()) + { + if (!Natives::IsInteriorScene()) + return; + } + + return shsub_D77A00.fastcall(_this, edx); + } + public: Shadows() { @@ -113,6 +141,7 @@ public: bDynamicShadowForTrees = iniReader.ReadInteger("SHADOWS", "DynamicShadowForTrees", 1) != 0; bool bOverrideCascadeRanges = iniReader.ReadInteger("SHADOWS", "OverrideCascadeRanges", 1) != 0; bool bHighResolutionShadows = iniReader.ReadInteger("SHADOWS", "HighResolutionShadows", 0) != 0; + bHighResolutionNightShadows = iniReader.ReadInteger("SHADOWS", "HighResolutionNightShadows", 0) != 0; if (bExtraDynamicShadows || bDynamicShadowForTrees) { @@ -242,6 +271,36 @@ public: // Fix night shadow resolution pattern = find_pattern("8B 0D ? ? ? ? 85 C9 7E 1B", "8B 0D ? ? ? ? 33 C0 85 C9 7E 1B"); static auto shsub_925E70 = safetyhook::create_inline(pattern.get_first(0), GetNightShadowQuality); + + // Lampposts shadows workaround + { + auto pattern = hook::pattern("80 3D ? ? ? ? ? 75 04 83 C8 FF"); + shsub_925DB0 = safetyhook::create_inline(pattern.get_first(), sub_925DB0); + + pattern = find_pattern("83 EC 3C 80 3D ? ? ? ? ? 56 8B F1", "83 EC 3C 53 33 DB"); + shsub_D77A00 = safetyhook::create_inline(pattern.get_first(0), sub_D77A00); + + pattern = find_pattern("8B 55 20 F6 C1 06"); + if (!pattern.empty()) + { + static auto ShadowsHook2 = safetyhook::create_mid(pattern.get_first(0), [](SafetyHookContext& regs) + { + static auto LamppostShadows = FusionFixSettings.GetRef("PREF_LAMPPOSTSHADOWS"); + if (!LamppostShadows->get()) + { + if (Natives::IsInteriorScene()) + { + if ((*(uint32_t*)(regs.edi + 0x4C) & 0x8000000) != 0) // new flag to detect affected lampposts + { + regs.ecx &= ~3; + regs.ecx &= ~4; + *(uint32_t*)(regs.esp + 0x18) = regs.ecx; + } + } + } + }); + } + } } }; } diff --git a/text/americanFF.txt b/text/americanFF.txt index 9568c5f3..4c5ef310 100644 --- a/text/americanFF.txt +++ b/text/americanFF.txt @@ -82,6 +82,9 @@ Tree Alpha [Shadow Filter] Shadow Filter +[HeadlightShadow] +Headlight Shadows + [FF_WARN0] ~p~IMG Files: diff --git a/text/frenchFF.txt b/text/frenchFF.txt index c6fb4a57..eb1150b1 100644 --- a/text/frenchFF.txt +++ b/text/frenchFF.txt @@ -82,6 +82,9 @@ Alpha des arbres [Shadow Filter] Filtre d'ombre +[HeadlightShadow] +Ombres des phares + [FF_WARN0] ~p~Fichiers IMG: diff --git a/text/germanFF.txt b/text/germanFF.txt index 5d1038c7..088a10ad 100644 --- a/text/germanFF.txt +++ b/text/germanFF.txt @@ -82,6 +82,9 @@ Baumtransparenz [Shadow Filter] Schattenfilter +[HeadlightShadow] +Scheinwerfer-Schatten + [FF_WARN0] ~p~IMG-Dateien: diff --git a/text/italianFF.txt b/text/italianFF.txt index 42276098..58f57797 100644 --- a/text/italianFF.txt +++ b/text/italianFF.txt @@ -82,6 +82,9 @@ Alfa degli alberi [Shadow Filter] Filtro delle ombre +[HeadlightShadow] +Ombre dei fari + [FF_WARN0] ~p~IMG Files: diff --git a/text/japaneseFF.txt b/text/japaneseFF.txt index 4fe2318d..27eb9a53 100644 --- a/text/japaneseFF.txt +++ b/text/japaneseFF.txt @@ -82,6 +82,9 @@ FPSリミッター [Shadow Filter] シャドウフィルター +[HeadlightShadow] +ヘッドライトの影 + [FF_WARN0] ~p~IMG ファイル: diff --git a/text/russianFF.txt b/text/russianFF.txt index f0a61448..a9d58242 100644 --- a/text/russianFF.txt +++ b/text/russianFF.txt @@ -82,6 +82,9 @@ Fusion Fix: Проверять Обновления [Shadow Filter] Фильтрация Теней +[HeadlightShadow] +Тени от фар + [FF_WARN0] ~p~IMG Архивы: diff --git a/text/spanishFF.txt b/text/spanishFF.txt index 4a202f3a..3427693c 100644 --- a/text/spanishFF.txt +++ b/text/spanishFF.txt @@ -82,6 +82,9 @@ Transparencia de los árboles [Shadow Filter] Filtro de sombras +[HeadlightShadow] +Sombras de los faros + [FF_WARN0] ~p~Archivos IMG: