From 363062ac2399aff87d135f439599288127166364 Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:19:46 +0300 Subject: [PATCH 1/3] Enhance vehicle color synchronization and rendering consistency - Updated CVehicleSA::SetColor to synchronize GTA's palette indices with MTA's RGB cache, ensuring accurate color representation during rendering. - Modified CClientVehicle::Create to apply MTA colors before vehicle rendering, preventing default palette colors from being used prematurely. - Adjusted CMultiplayerSA::InitHooks to ensure correct color slot indexing for vehicle color hooks, addressing potential desynchronization issues. - Implemented safeguards in SaveVehColors to prevent color reuse from previous vehicles, ensuring accurate color data during vehicle creation. These changes improve the visual fidelity and consistency of vehicle colors across different game states. --- Client/game_sa/CVehicleSA.cpp | 8 +++++++ .../mods/deathmatch/logic/CClientVehicle.cpp | 5 ++++ Client/multiplayer_sa/CMultiplayerSA.cpp | 23 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index 6cc75f5808..927d4bbedd 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -1090,6 +1090,14 @@ void CVehicleSA::SetColor(SharedUtil::SColor color1, SharedUtil::SColor color2, m_RGBColors[2] = color3; m_RGBColors[3] = color4; + // Keep GTA's per-vehicle palette indices in sync for SetupRender and other engine paths that read + // m_colour1-4 instead of MTA's RGB cache (e.g. after stream-in recreation). + CVehicleSAInterface* pInterface = GetVehicleInterface(); + pInterface->m_colour1 = CVehicleColor::GetPaletteIndexFromRGB(color1); + pInterface->m_colour2 = CVehicleColor::GetPaletteIndexFromRGB(color2); + pInterface->m_colour3 = CVehicleColor::GetPaletteIndexFromRGB(color3); + pInterface->m_colour4 = CVehicleColor::GetPaletteIndexFromRGB(color4); + // Some colors result in black for unknown reason for (uint i = 0; i < NUMELMS(m_RGBColors); i++) { diff --git a/Client/mods/deathmatch/logic/CClientVehicle.cpp b/Client/mods/deathmatch/logic/CClientVehicle.cpp index af916f2f00..062b3393b1 100644 --- a/Client/mods/deathmatch/logic/CClientVehicle.cpp +++ b/Client/mods/deathmatch/logic/CClientVehicle.cpp @@ -2582,6 +2582,11 @@ void CClientVehicle::Create() // Put our pointer in its custom data m_pVehicle->SetStoredPointer(this); + // Apply MTA colors before any other setup that may render the vehicle. Without this, the first + // SetupRender after stream-in can run while CVehicleSA still holds GTA's default palette colors. + if (m_bColorSaved) + m_pVehicle->SetColor(m_Color.GetRGBColor(0), m_Color.GetRGBColor(1), m_Color.GetRGBColor(2), m_Color.GetRGBColor(3), 0); + /*if ( DoesNeedToWaitForGroundToLoad() ) { // waiting for ground to load diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index 3586ec56b7..2426185944 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -682,6 +682,20 @@ void CMultiplayerSA::InitHooks() HookInstall(HOOKPOS_VehColCB, (DWORD)HOOK_VehColCB, 29); HookInstall(HOOKPOS_VehCol, (DWORD)HOOK_VehCol, 9); + + // CVehicleModelInfo::SetEditableMaterialsCB stores ms_currentCol[n] (a palette index) in esi before + // applying the color. MTA's HOOK_VehColCB indexes vehColors[esi], so esi must be the color slot + // (0-3), not a palette index. Quaternary used ms_currentCol[3]==0, so slot 4 was read as slot 1. + static const std::uint8_t aSetVehicleColorSlot[] = { + 0xC7, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x90, // mov esi, 0; nop (primary) + 0xC7, 0xC6, 0x01, 0x00, 0x00, 0x00, 0x90, // mov esi, 1; nop (secondary) + 0xC7, 0xC6, 0x02, 0x00, 0x00, 0x00, 0x90, // mov esi, 2; nop (tertiary) + 0xC7, 0xC6, 0x03, 0x00, 0x00, 0x00, 0x90, // mov esi, 3; nop (quaternary) + }; + MemCpy((void*)0x4C8338, aSetVehicleColorSlot + 0, 7); + MemCpy((void*)0x4C8350, aSetVehicleColorSlot + 7, 7); + MemCpy((void*)0x4C8362, aSetVehicleColorSlot + 14, 7); + MemCpy((void*)0x4C8376, aSetVehicleColorSlot + 21, 7); HookInstall(HOOKPOS_PreFxRender, (DWORD)HOOK_PreFxRender, 5); HookInstall(HOOKPOS_PostColorFilterRender, (DWORD)HOOK_PostColorFilterRender, 5); HookInstall(HOOKPOS_PreHUDRender, (DWORD)HOOK_PreHUDRender, 5); @@ -6528,6 +6542,15 @@ void _cdecl SaveVehColors(DWORD dwThis) { pVehicle->GetColor(&vehColors[0], &vehColors[1], &vehColors[2], &vehColors[3], true); } + else + { + // vehColors is shared between all vehicles; do not reuse the previous vehicle's colors when + // SetupRender runs for a non-MTA vehicle (or before the pool entry exists during creation). + vehColors[0] = 0; + vehColors[1] = 0; + vehColors[2] = 0; + vehColors[3] = 0; + } } static void __declspec(naked) HOOK_VehCol() From 1b5d38507c614f70da35b31317347386845da7e3 Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:30:18 +0300 Subject: [PATCH 2/3] Refactor vehicle color slot handling in CMultiplayerSA - Updated the color slot indexing logic in CMultiplayerSA::InitHooks to ensure accurate mapping between GTA's palette indices and MTA's RGB cache. - Adjusted memory copy operations to correct offsets for setting vehicle color slots, enhancing synchronization and preventing potential desynchronization issues. These changes improve the consistency of vehicle color rendering across different game states. --- Client/multiplayer_sa/CMultiplayerSA.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index 2426185944..acdd1fb6b4 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -683,19 +683,25 @@ void CMultiplayerSA::InitHooks() HookInstall(HOOKPOS_VehColCB, (DWORD)HOOK_VehColCB, 29); HookInstall(HOOKPOS_VehCol, (DWORD)HOOK_VehCol, 9); - // CVehicleModelInfo::SetEditableMaterialsCB stores ms_currentCol[n] (a palette index) in esi before - // applying the color. MTA's HOOK_VehColCB indexes vehColors[esi], so esi must be the color slot - // (0-3), not a palette index. Quaternary used ms_currentCol[3]==0, so slot 4 was read as slot 1. + // CVehicleModelInfo::SetEditableMaterialsCB (0x4C8220) matches each editable material against one of + // 4 marker colors, then does "movzx esi, ms_currentCol[n]" to fetch the chosen GTA palette index + // (0-255) for that slot. MTA's HOOK_VehColCB (0x4C838D, installed above) instead indexes its own + // vehColors[esi] RGB cache, which requires esi to be the slot number (0-3), not a palette index. + // Quaternary's ms_currentCol[3] is very often 0 for standard 2-color vehicles, so esi silently + // aliased to vehColors[0] (primary), and tertiary was similarly at the mercy of whatever palette + // index the player/model happened to use. Each of the 4 movzx instructions (verified via disassembly + // of gta_sa.exe 1.0 US, since MTA only supports this build) is replaced with "mov esi, ; nop" + // of the exact same 7-byte length, so the surrounding cmp/jne/jmp branch logic is untouched. static const std::uint8_t aSetVehicleColorSlot[] = { 0xC7, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x90, // mov esi, 0; nop (primary) 0xC7, 0xC6, 0x01, 0x00, 0x00, 0x00, 0x90, // mov esi, 1; nop (secondary) 0xC7, 0xC6, 0x02, 0x00, 0x00, 0x00, 0x90, // mov esi, 2; nop (tertiary) 0xC7, 0xC6, 0x03, 0x00, 0x00, 0x00, 0x90, // mov esi, 3; nop (quaternary) }; - MemCpy((void*)0x4C8338, aSetVehicleColorSlot + 0, 7); + MemCpy((void*)0x4C833F, aSetVehicleColorSlot + 0, 7); MemCpy((void*)0x4C8350, aSetVehicleColorSlot + 7, 7); - MemCpy((void*)0x4C8362, aSetVehicleColorSlot + 14, 7); - MemCpy((void*)0x4C8376, aSetVehicleColorSlot + 21, 7); + MemCpy((void*)0x4C8361, aSetVehicleColorSlot + 14, 7); + MemCpy((void*)0x4C8372, aSetVehicleColorSlot + 21, 7); HookInstall(HOOKPOS_PreFxRender, (DWORD)HOOK_PreFxRender, 5); HookInstall(HOOKPOS_PostColorFilterRender, (DWORD)HOOK_PostColorFilterRender, 5); HookInstall(HOOKPOS_PreHUDRender, (DWORD)HOOK_PreHUDRender, 5); From 0b8d7c2ea01a1d6294c62fac54e5ff116e2b108a Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:20:09 +0300 Subject: [PATCH 3/3] Refactor vehicle color handling in CVehicleSA and CClientVehicle - Removed outdated color synchronization logic from CVehicleSA::SetColor to streamline color setting. - Eliminated premature color application in CClientVehicle::Create to prevent default palette colors from being used before rendering. - Updated comments for clarity on vehicle color handling. These changes enhance the efficiency of vehicle color management and improve rendering consistency. --- Client/game_sa/CVehicleSA.cpp | 8 -------- .../mods/deathmatch/logic/CClientVehicle.cpp | 5 ----- Client/multiplayer_sa/CMultiplayerSA.cpp | 20 +++++++++---------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index 927d4bbedd..6cc75f5808 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -1090,14 +1090,6 @@ void CVehicleSA::SetColor(SharedUtil::SColor color1, SharedUtil::SColor color2, m_RGBColors[2] = color3; m_RGBColors[3] = color4; - // Keep GTA's per-vehicle palette indices in sync for SetupRender and other engine paths that read - // m_colour1-4 instead of MTA's RGB cache (e.g. after stream-in recreation). - CVehicleSAInterface* pInterface = GetVehicleInterface(); - pInterface->m_colour1 = CVehicleColor::GetPaletteIndexFromRGB(color1); - pInterface->m_colour2 = CVehicleColor::GetPaletteIndexFromRGB(color2); - pInterface->m_colour3 = CVehicleColor::GetPaletteIndexFromRGB(color3); - pInterface->m_colour4 = CVehicleColor::GetPaletteIndexFromRGB(color4); - // Some colors result in black for unknown reason for (uint i = 0; i < NUMELMS(m_RGBColors); i++) { diff --git a/Client/mods/deathmatch/logic/CClientVehicle.cpp b/Client/mods/deathmatch/logic/CClientVehicle.cpp index 062b3393b1..af916f2f00 100644 --- a/Client/mods/deathmatch/logic/CClientVehicle.cpp +++ b/Client/mods/deathmatch/logic/CClientVehicle.cpp @@ -2582,11 +2582,6 @@ void CClientVehicle::Create() // Put our pointer in its custom data m_pVehicle->SetStoredPointer(this); - // Apply MTA colors before any other setup that may render the vehicle. Without this, the first - // SetupRender after stream-in can run while CVehicleSA still holds GTA's default palette colors. - if (m_bColorSaved) - m_pVehicle->SetColor(m_Color.GetRGBColor(0), m_Color.GetRGBColor(1), m_Color.GetRGBColor(2), m_Color.GetRGBColor(3), 0); - /*if ( DoesNeedToWaitForGroundToLoad() ) { // waiting for ground to load diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index acdd1fb6b4..32afffccec 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -683,15 +683,15 @@ void CMultiplayerSA::InitHooks() HookInstall(HOOKPOS_VehColCB, (DWORD)HOOK_VehColCB, 29); HookInstall(HOOKPOS_VehCol, (DWORD)HOOK_VehCol, 9); - // CVehicleModelInfo::SetEditableMaterialsCB (0x4C8220) matches each editable material against one of - // 4 marker colors, then does "movzx esi, ms_currentCol[n]" to fetch the chosen GTA palette index - // (0-255) for that slot. MTA's HOOK_VehColCB (0x4C838D, installed above) instead indexes its own - // vehColors[esi] RGB cache, which requires esi to be the slot number (0-3), not a palette index. - // Quaternary's ms_currentCol[3] is very often 0 for standard 2-color vehicles, so esi silently - // aliased to vehColors[0] (primary), and tertiary was similarly at the mercy of whatever palette - // index the player/model happened to use. Each of the 4 movzx instructions (verified via disassembly - // of gta_sa.exe 1.0 US, since MTA only supports this build) is replaced with "mov esi, ; nop" - // of the exact same 7-byte length, so the surrounding cmp/jne/jmp branch logic is untouched. + // CVehicleModelInfo::SetEditableMaterialsCB (0x4C8220) uses "movzx esi, ms_currentCol[n]" before + // applying a matched paint slot. MTA's HOOK_VehColCB (0x4C838D, installed above) treats esi as a + // vehColors slot index (0-3), not a GTA palette index (0-255). HOOK_VehCol (CVehicle::SetupRender) + // already forces ms_currentCol to {0,1,2,3}, so on that path esi is already correct and this patch + // is a no-op. SetEditableMaterialsCB can still run on paths that bypass SetupRender, leaving + // ms_currentCol holding palette indices; esi then indexes vehColors out of bounds or reads the wrong + // slot (notably tertiary/quaternary on default 2-color vehicles where ms_currentCol[3] is often 0). + // Replace each movzx with "mov esi, ; nop" (7 bytes, verified on gta_sa.exe 1.0 US) so esi is + // always the intended color slot regardless of which path invoked the callback. static const std::uint8_t aSetVehicleColorSlot[] = { 0xC7, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x90, // mov esi, 0; nop (primary) 0xC7, 0xC6, 0x01, 0x00, 0x00, 0x00, 0x90, // mov esi, 1; nop (secondary) @@ -6551,7 +6551,7 @@ void _cdecl SaveVehColors(DWORD dwThis) else { // vehColors is shared between all vehicles; do not reuse the previous vehicle's colors when - // SetupRender runs for a non-MTA vehicle (or before the pool entry exists during creation). + // SetupRender runs for a non-MTA vehicle. vehColors[0] = 0; vehColors[1] = 0; vehColors[2] = 0;