From bba2add7130189938fc3592632ede1e919012b13 Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:57:01 -0600 Subject: [PATCH 1/7] Disable Chat Support Continuously enforces the disabled chat state to prevent users from re-enabling it through the alliances menu. --- src/Misc/InGameChat.cpp | 39 ++++++++++++++++++++++++++++++++++ src/Spawner/Spawner.Config.cpp | 1 + src/Spawner/Spawner.Config.h | 2 ++ 3 files changed, 42 insertions(+) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index 8b124dc8..80bb5cbb 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -40,6 +40,45 @@ struct GlobalPacket_NetMessage }; #pragma pack(pop) +// Continuously enforce DisableChat by resetting ChatMask every frame, +// preventing players from re-enabling chat via the alliances menu. +DEFINE_HOOK(0x55DDA5, MainLoop_AfterRender__DisableChat, 0x5) +{ + if (!Spawner::Enabled) + return 0; + + if (Spawner::GetConfig()->DisableChat) + { + for (int i = 0; i < 8; i++) + Game::ChatMask[i] = false; + } + + return 0; +} + +// Don't send message to others when DisableChat is active. +// Mirrors: hack 0x0055EF38, 0x0055EF3E in chat_disable.asm +DEFINE_HOOK(0x55EF38, MessageSend_DisableChat, 0x6) +{ + if (Spawner::Enabled && Spawner::GetConfig()->DisableChat) + return 0x55F056; // skip the send + + return 0; // execute original: cmp edi, ebx; mov [esp+0x14], ebx +} + +// After receiving a message, don't play sound if AddMessage returned NULL +// (i.e. the message was suppressed). Mirrors: hack 0x0048D97E in chat_disable.asm +DEFINE_HOOK(0x48D97E, NetworkCallBack_NetMessage_Sound, 0x5) +{ + if (!Spawner::Enabled) + return 0; + + if (!R->EAX()) + return 0x48D99A; // skip sound + + return 0; // execute original: mov eax, [0x8871E0] +} + DEFINE_HOOK(0x48D92B, NetworkCallBack_NetMessage_Print, 0x5) { if (!Spawner::Enabled) diff --git a/src/Spawner/Spawner.Config.cpp b/src/Spawner/Spawner.Config.cpp index 430d242f..f659cb78 100644 --- a/src/Spawner/Spawner.Config.cpp +++ b/src/Spawner/Spawner.Config.cpp @@ -116,6 +116,7 @@ void SpawnerConfig::LoadFromINIFile(CCINIClass* pINI) ContinueWithoutHumans = pINI->ReadBool(pSettingsSection, "ContinueWithoutHumans", ContinueWithoutHumans); DefeatedBecomesObserver = pINI->ReadBool(pSettingsSection, "DefeatedBecomesObserver", DefeatedBecomesObserver); Observer_ShowAIOnSidebar = pINI->ReadBool(pSettingsSection, "Observer.ShowAIOnSidebar", Observer_ShowAIOnSidebar); + DisableChat = pINI->ReadBool(pSettingsSection, "DisableChat", DisableChat); } } diff --git a/src/Spawner/Spawner.Config.h b/src/Spawner/Spawner.Config.h index a390baaf..31be4fe1 100644 --- a/src/Spawner/Spawner.Config.h +++ b/src/Spawner/Spawner.Config.h @@ -145,6 +145,7 @@ class SpawnerConfig bool ContinueWithoutHumans; bool DefeatedBecomesObserver; bool Observer_ShowAIOnSidebar; + bool DisableChat; SpawnerConfig() // default values // Game Mode Options @@ -238,6 +239,7 @@ class SpawnerConfig , ContinueWithoutHumans { false } , DefeatedBecomesObserver { false } , Observer_ShowAIOnSidebar { false } + , DisableChat { false } { } void LoadFromINIFile(CCINIClass* pINI); From f2178774cf22f7cb9df6e3ec494b073af691c1ec Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:29:57 -0600 Subject: [PATCH 2/7] Try to prevent players from re-enabling chat through the alliance menu. --- src/Misc/InGameChat.cpp | 18 +++++++----------- src/Spawner/Spawner.Hook.cpp | 5 +++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index 80bb5cbb..d8c6ffa0 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include // This corrects the processing of Unicode player names @@ -40,20 +41,15 @@ struct GlobalPacket_NetMessage }; #pragma pack(pop) -// Continuously enforce DisableChat by resetting ChatMask every frame, -// preventing players from re-enabling chat via the alliances menu. -DEFINE_HOOK(0x55DDA5, MainLoop_AfterRender__DisableChat, 0x5) +void __fastcall MainLoop_AfterRender_DisableChat(MessageListClass* pMessageList, DWORD) { - if (!Spawner::Enabled) - return 0; + pMessageList->Manage(); - if (Spawner::GetConfig()->DisableChat) - { - for (int i = 0; i < 8; i++) - Game::ChatMask[i] = false; - } + if (!Spawner::Enabled || !Spawner::GetConfig()->DisableChat) + return; - return 0; + for (int i = 0; i < 8; ++i) + Game::ChatMask[i] = false; } // Don't send message to others when DisableChat is active. diff --git a/src/Spawner/Spawner.Hook.cpp b/src/Spawner/Spawner.Hook.cpp index 79113992..51868aef 100644 --- a/src/Spawner/Spawner.Hook.cpp +++ b/src/Spawner/Spawner.Hook.cpp @@ -27,6 +27,8 @@ #include #include +void __fastcall MainLoop_AfterRender_DisableChat(class MessageListClass* pMessageList, DWORD); + DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6) { if (Spawner::Enabled) @@ -61,6 +63,9 @@ DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6) Patch::Apply_LJMP(0x55D0DF, 0x55D0E8); // AuxLoop_Cooperative_EndgameCrashFix } + // Disable chat from alliances menu when DisableChat is enabled. + Patch::Apply_CALL(0x55DDA5, MainLoop_AfterRender_DisableChat); // MainLoop_AfterRender + // Set ConnTimeout Patch::Apply_TYPED(0x6843C7, { Spawner::GetConfig()->ConnTimeout }); // Scenario_Load_Wait From 6657538a618f5837cffb6d8be8557c2560087088 Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:42:58 -0600 Subject: [PATCH 3/7] Adds feedback when chat is disabled Provides visual feedback to the user when chat is disabled via the spawner configuration, preventing them from unknowingly attempting to send messages. The feedback is throttled to prevent spamming the chat window. --- src/Misc/InGameChat.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index d8c6ffa0..4e7f5bd4 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -56,8 +56,20 @@ void __fastcall MainLoop_AfterRender_DisableChat(MessageListClass* pMessageList, // Mirrors: hack 0x0055EF38, 0x0055EF3E in chat_disable.asm DEFINE_HOOK(0x55EF38, MessageSend_DisableChat, 0x6) { + static int LastDisableChatFeedbackFrame = -1000; + if (Spawner::Enabled && Spawner::GetConfig()->DisableChat) + { + const int currentFrame = Unsorted::CurrentFrame; + + if (currentFrame - LastDisableChatFeedbackFrame >= 90) + { + MessageListClass::Instance.PrintMessage(L"Chat is disabled. Message not sent."); + LastDisableChatFeedbackFrame = currentFrame; + } + return 0x55F056; // skip the send + } return 0; // execute original: cmp edi, ebx; mov [esp+0x14], ebx } From 6a66d0a635c516d32cf49f34a62eed18c3b17406 Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:15:49 -0600 Subject: [PATCH 4/7] Chat toggle non-interactive now --- src/Misc/InGameChat.cpp | 42 ++++++++++++++++++++++++++++-------- src/Spawner/Spawner.Hook.cpp | 5 ----- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index 4e7f5bd4..faa023a2 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -41,15 +41,9 @@ struct GlobalPacket_NetMessage }; #pragma pack(pop) -void __fastcall MainLoop_AfterRender_DisableChat(MessageListClass* pMessageList, DWORD) +static bool inline IsDisableChatEnabled() { - pMessageList->Manage(); - - if (!Spawner::Enabled || !Spawner::GetConfig()->DisableChat) - return; - - for (int i = 0; i < 8; ++i) - Game::ChatMask[i] = false; + return Spawner::Enabled && Spawner::GetConfig()->DisableChat; } // Don't send message to others when DisableChat is active. @@ -58,7 +52,7 @@ DEFINE_HOOK(0x55EF38, MessageSend_DisableChat, 0x6) { static int LastDisableChatFeedbackFrame = -1000; - if (Spawner::Enabled && Spawner::GetConfig()->DisableChat) + if (IsDisableChatEnabled()) { const int currentFrame = Unsorted::CurrentFrame; @@ -87,6 +81,33 @@ DEFINE_HOOK(0x48D97E, NetworkCallBack_NetMessage_Sound, 0x5) return 0; // execute original: mov eax, [0x8871E0] } +// In diplomacy dialog, make chat checkbox non-interactive for each player, +// matching the existing Player_MuteSWLaunches disabled-checkbox behavior. +// Hook point is after `push 0` (lParam), so jumping to 0x657FC0 preserves stack layout. +DEFINE_HOOK(0x657F95, RadarClass_Diplomacy_DisableChatToggleUI, 0x2) +{ + return IsDisableChatEnabled() + ? 0x657FC0 + : 0; +} + +// Continuously enforce DisableChat by resetting ChatMask every frame, +// preventing re-enabling chat from the alliance menu. +DEFINE_HOOK(0x55DDA5, MainLoop_AfterRender_DisableChat, 0x5) +{ + auto const Original = reinterpret_cast(0x5D4430); + GET(void*, pThis, ECX); + Original(pThis); + + if (IsDisableChatEnabled()) + { + for (int i = 0; i < 8; ++i) + Game::ChatMask[i] = false; + } + + return 0x55DDAA; +} + DEFINE_HOOK(0x48D92B, NetworkCallBack_NetMessage_Print, 0x5) { if (!Spawner::Enabled) @@ -94,6 +115,9 @@ DEFINE_HOOK(0x48D92B, NetworkCallBack_NetMessage_Print, 0x5) enum { SkipMessage = 0x48DAD3, PrintMessage = 0x48D937 }; + if (IsDisableChatEnabled()) + return SkipMessage; + const int houseIndex = GlobalPacket_NetMessage::Instance.HouseIndex; if (houseIndex < 8 && Game::ChatMask[houseIndex]) diff --git a/src/Spawner/Spawner.Hook.cpp b/src/Spawner/Spawner.Hook.cpp index 51868aef..79113992 100644 --- a/src/Spawner/Spawner.Hook.cpp +++ b/src/Spawner/Spawner.Hook.cpp @@ -27,8 +27,6 @@ #include #include -void __fastcall MainLoop_AfterRender_DisableChat(class MessageListClass* pMessageList, DWORD); - DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6) { if (Spawner::Enabled) @@ -63,9 +61,6 @@ DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6) Patch::Apply_LJMP(0x55D0DF, 0x55D0E8); // AuxLoop_Cooperative_EndgameCrashFix } - // Disable chat from alliances menu when DisableChat is enabled. - Patch::Apply_CALL(0x55DDA5, MainLoop_AfterRender_DisableChat); // MainLoop_AfterRender - // Set ConnTimeout Patch::Apply_TYPED(0x6843C7, { Spawner::GetConfig()->ConnTimeout }); // Scenario_Load_Wait From 87295469bd9ad3910d28e759329545bf581c0c9b Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:56:59 -0600 Subject: [PATCH 5/7] Force chat toggle to disabled state in UI --- src/Misc/InGameChat.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index faa023a2..afc9fe55 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -22,6 +22,7 @@ #include #include #include +#include // This corrects the processing of Unicode player names // and prohibits incoming messages from players with whom chat is disabled @@ -39,6 +40,13 @@ struct GlobalPacket_NetMessage byte Color; byte CRC; }; + +struct DiplomacyChatToggleState +{ + DEFINE_REFERENCE(DiplomacyChatToggleState, Instance, 0xA8D108u); + + byte ByHouse[8]; +}; #pragma pack(pop) static bool inline IsDisableChatEnabled() @@ -91,6 +99,19 @@ DEFINE_HOOK(0x657F95, RadarClass_Diplomacy_DisableChatToggleUI, 0x2) : 0; } +// The non-interactive branch (loc_657FC0) sets BM_SETCHECK(1) before disabling. +// Force it back to OFF for DisableChat so visuals match the intended locked state. +DEFINE_HOOK(0x657FDB, RadarClass_Diplomacy_ForceDisabledChatVisualOff, 0x5) +{ + if (IsDisableChatEnabled()) + { + GET(HWND, hWnd, EBP); + SendMessageA(hWnd, BM_SETCHECK, BST_UNCHECKED, 0); + } + + return 0; +} + // Continuously enforce DisableChat by resetting ChatMask every frame, // preventing re-enabling chat from the alliance menu. DEFINE_HOOK(0x55DDA5, MainLoop_AfterRender_DisableChat, 0x5) @@ -102,7 +123,10 @@ DEFINE_HOOK(0x55DDA5, MainLoop_AfterRender_DisableChat, 0x5) if (IsDisableChatEnabled()) { for (int i = 0; i < 8; ++i) + { + DiplomacyChatToggleState::Instance.ByHouse[i] = 0; Game::ChatMask[i] = false; + } } return 0x55DDAA; From 0cb41624ff14e0b0c69fe8a1b33d4af5130b95ed Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Wed, 25 Feb 2026 09:25:17 -0600 Subject: [PATCH 6/7] Sonnet Clean-Up --- src/Misc/InGameChat.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index afc9fe55..350f0ea1 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -41,12 +41,6 @@ struct GlobalPacket_NetMessage byte CRC; }; -struct DiplomacyChatToggleState -{ - DEFINE_REFERENCE(DiplomacyChatToggleState, Instance, 0xA8D108u); - - byte ByHouse[8]; -}; #pragma pack(pop) static bool inline IsDisableChatEnabled() @@ -123,10 +117,7 @@ DEFINE_HOOK(0x55DDA5, MainLoop_AfterRender_DisableChat, 0x5) if (IsDisableChatEnabled()) { for (int i = 0; i < 8; ++i) - { - DiplomacyChatToggleState::Instance.ByHouse[i] = 0; Game::ChatMask[i] = false; - } } return 0x55DDAA; From 34406e8dc3d9e22c0998817d5fea22a45230e91a Mon Sep 17 00:00:00 2001 From: RAZER <79311432+CnCRAZER@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:51:29 -0500 Subject: [PATCH 7/7] Reset disabled chat feedback throttle for new matches Ensures that the feedback message throttle is reset when the frame counter restarts, allowing the "Chat is disabled" notification to trigger correctly in subsequent matches. --- src/Misc/InGameChat.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Misc/InGameChat.cpp b/src/Misc/InGameChat.cpp index 350f0ea1..d0d81cdf 100644 --- a/src/Misc/InGameChat.cpp +++ b/src/Misc/InGameChat.cpp @@ -58,6 +58,9 @@ DEFINE_HOOK(0x55EF38, MessageSend_DisableChat, 0x6) { const int currentFrame = Unsorted::CurrentFrame; + if (currentFrame < LastDisableChatFeedbackFrame) + LastDisableChatFeedbackFrame = -1000; // new match started + if (currentFrame - LastDisableChatFeedbackFrame >= 90) { MessageListClass::Instance.PrintMessage(L"Chat is disabled. Message not sent.");