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: