aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Barham <me@cpdt.dev>2022-02-22 08:33:53 +1000
committerGitHub <noreply@github.com>2022-02-21 19:33:53 -0300
commit8c9f34283f8670dda98959d0785d682e6f652a93 (patch)
tree5e9d8c10fb5d1df2816fac43e4db3b80cc7f93a0
parentae9df9bc734c6c0364028c71db5e7236e7c344dd (diff)
downloadNorthstarLauncher-8c9f34283f8670dda98959d0785d682e6f652a93.tar.gz
NorthstarLauncher-8c9f34283f8670dda98959d0785d682e6f652a93.zip
Advanced chat: custom messages and client hooks (#74)
Co-authored-by: Emma Miler <27428383+emma-miler@users.noreply.github.com>
-rw-r--r--NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj6
-rw-r--r--NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters18
-rw-r--r--NorthstarDedicatedTest/chatcommand.cpp12
-rw-r--r--NorthstarDedicatedTest/chatcommand.h2
-rw-r--r--NorthstarDedicatedTest/clientchathooks.cpp86
-rw-r--r--NorthstarDedicatedTest/clientchathooks.h5
-rw-r--r--NorthstarDedicatedTest/dllmain.cpp9
-rw-r--r--NorthstarDedicatedTest/localchatwriter.cpp460
-rw-r--r--NorthstarDedicatedTest/localchatwriter.h50
-rw-r--r--NorthstarDedicatedTest/serverauthentication.cpp127
-rw-r--r--NorthstarDedicatedTest/serverauthentication.h2
-rw-r--r--NorthstarDedicatedTest/serverchathooks.cpp195
-rw-r--r--NorthstarDedicatedTest/serverchathooks.h29
-rw-r--r--NorthstarDedicatedTest/squirrel.cpp7
-rw-r--r--NorthstarDedicatedTest/squirrel.h64
15 files changed, 956 insertions, 116 deletions
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
index 9fa7e9df..5235dfc9 100644
--- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
+++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
@@ -112,6 +112,9 @@
<ClInclude Include="bitbuf.h" />
<ClInclude Include="buildainfile.h" />
<ClInclude Include="chatcommand.h" />
+ <ClInclude Include="clientchathooks.h" />
+ <ClInclude Include="localchatwriter.h" />
+ <ClInclude Include="serverchathooks.h" />
<ClInclude Include="clientauthhooks.h" />
<ClInclude Include="concommand.h" />
<ClInclude Include="configurables.h" />
@@ -556,6 +559,7 @@
<ClCompile Include="buildainfile.cpp" />
<ClCompile Include="chatcommand.cpp" />
<ClCompile Include="clientauthhooks.cpp" />
+ <ClCompile Include="clientchathooks.cpp" />
<ClCompile Include="concommand.cpp" />
<ClCompile Include="configurables.cpp" />
<ClCompile Include="context.cpp" />
@@ -569,6 +573,7 @@
<ClCompile Include="hookutils.cpp" />
<ClCompile Include="keyvalues.cpp" />
<ClCompile Include="latencyflex.cpp" />
+ <ClCompile Include="localchatwriter.cpp" />
<ClCompile Include="maxplayers.cpp" />
<ClCompile Include="languagehooks.cpp" />
<ClCompile Include="memalloc.cpp" />
@@ -594,6 +599,7 @@
<ClCompile Include="scriptsrson.cpp" />
<ClCompile Include="serverauthentication.cpp" />
<ClCompile Include="miscserverscript.cpp" />
+ <ClCompile Include="serverchathooks.cpp" />
<ClCompile Include="sigscanning.cpp" />
<ClCompile Include="sourceconsole.cpp" />
<ClCompile Include="sourceinterface.cpp" />
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
index 67679462..11205541 100644
--- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
+++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
@@ -1449,6 +1449,15 @@
<ClInclude Include="configurables.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
+ <ClInclude Include="serverchathooks.h">
+ <Filter>Header Files\Server</Filter>
+ </ClInclude>
+ <ClInclude Include="clientchathooks.h">
+ <Filter>Header Files\Client</Filter>
+ </ClInclude>
+ <ClInclude Include="localchatwriter.h">
+ <Filter>Header Files\Client</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
@@ -1583,6 +1592,15 @@
<ClCompile Include="configurables.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
+ <ClCompile Include="serverchathooks.cpp">
+ <Filter>Source Files\Server</Filter>
+ </ClCompile>
+ <ClCompile Include="clientchathooks.cpp">
+ <Filter>Source Files\Client</Filter>
+ </ClCompile>
+ <ClCompile Include="localchatwriter.cpp">
+ <Filter>Source Files\Client</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="audio_asm.asm">
diff --git a/NorthstarDedicatedTest/chatcommand.cpp b/NorthstarDedicatedTest/chatcommand.cpp
index 0540a9a0..cd6a998c 100644
--- a/NorthstarDedicatedTest/chatcommand.cpp
+++ b/NorthstarDedicatedTest/chatcommand.cpp
@@ -2,6 +2,7 @@
#include "chatcommand.h"
#include "concommand.h"
#include "dedicated.h"
+#include "localchatwriter.h"
// note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work
// if isIngameChat is false, we use network chat instead
@@ -20,6 +21,14 @@ void ConCommand_say_team(const CCommand& args)
ClientSayText(nullptr, args.ArgS(), true, true);
}
+void ConCommand_log(const CCommand& args)
+{
+ if (args.ArgC() >= 2)
+ {
+ LocalChatWriter(LocalChatWriter::GameContext).WriteLine(args.ArgS());
+ }
+}
+
void InitialiseChatCommands(HMODULE baseAddress)
{
if (IsDedicated())
@@ -28,4 +37,5 @@ void InitialiseChatCommands(HMODULE baseAddress)
ClientSayText = (ClientSayTextType)((char*)baseAddress + 0x54780);
RegisterConCommand("say", ConCommand_say, "Enters a message in public chat", FCVAR_CLIENTDLL);
RegisterConCommand("say_team", ConCommand_say_team, "Enters a message in team chat", FCVAR_CLIENTDLL);
-} \ No newline at end of file
+ RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL);
+}
diff --git a/NorthstarDedicatedTest/chatcommand.h b/NorthstarDedicatedTest/chatcommand.h
index 1c095fb7..546d0126 100644
--- a/NorthstarDedicatedTest/chatcommand.h
+++ b/NorthstarDedicatedTest/chatcommand.h
@@ -1,3 +1,3 @@
#pragma once
-void InitialiseChatCommands(HMODULE baseAddress); \ No newline at end of file
+void InitialiseChatCommands(HMODULE baseAddress);
diff --git a/NorthstarDedicatedTest/clientchathooks.cpp b/NorthstarDedicatedTest/clientchathooks.cpp
new file mode 100644
index 00000000..81413002
--- /dev/null
+++ b/NorthstarDedicatedTest/clientchathooks.cpp
@@ -0,0 +1,86 @@
+#include "pch.h"
+#include "clientchathooks.h"
+#include <rapidjson/document.h>
+#include "squirrel.h"
+#include "serverchathooks.h"
+#include "localchatwriter.h"
+
+typedef void(__fastcall* CHudChat__AddGameLineType)(void* self, const char* message, int fromPlayerId, bool isteam, bool isdead);
+CHudChat__AddGameLineType CHudChat__AddGameLine;
+
+struct ChatTags
+{
+ bool whisper;
+ bool team;
+ bool dead;
+};
+
+static void CHudChat__AddGameLineHook(void* self, const char* message, int inboxId, bool isTeam, bool isDead)
+{
+ // This hook is called for each HUD, but we only want our logic to run once.
+ if (!IsFirstHud(self))
+ {
+ return;
+ }
+
+ int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK;
+ bool isAnonymous = senderId == 0;
+ bool isCustom = isAnonymous || (inboxId & CUSTOM_MESSAGE_INDEX_BIT);
+
+ // Type is set to 0 for non-custom messages, custom messages have a type encoded as the first byte
+ int type = 0;
+ const char* payload = message;
+ if (isCustom)
+ {
+ type = message[0];
+ payload = message + 1;
+ }
+
+ g_ClientSquirrelManager->setupfunc("CHudChat_ProcessMessageStartThread");
+ g_ClientSquirrelManager->pusharg((int)senderId - 1);
+ g_ClientSquirrelManager->pusharg(payload);
+ g_ClientSquirrelManager->pusharg(isTeam);
+ g_ClientSquirrelManager->pusharg(isDead);
+ g_ClientSquirrelManager->pusharg(type);
+ g_ClientSquirrelManager->call(5);
+}
+
+// void NSChatWrite( int context, string str )
+static SQRESULT SQ_ChatWrite(void* sqvm)
+{
+ int context = ClientSq_getinteger(sqvm, 1);
+ const char* str = ClientSq_getstring(sqvm, 2);
+
+ LocalChatWriter((LocalChatWriter::Context)context).Write(str);
+ return SQRESULT_NOTNULL;
+}
+
+// void NSChatWriteRaw( int context, string str )
+static SQRESULT SQ_ChatWriteRaw(void* sqvm)
+{
+ int context = ClientSq_getinteger(sqvm, 1);
+ const char* str = ClientSq_getstring(sqvm, 2);
+
+ LocalChatWriter((LocalChatWriter::Context)context).InsertText(str);
+ return SQRESULT_NOTNULL;
+}
+
+// void NSChatWriteLine( int context, string str )
+static SQRESULT SQ_ChatWriteLine(void* sqvm)
+{
+ int context = ClientSq_getinteger(sqvm, 1);
+ const char* str = ClientSq_getstring(sqvm, 2);
+
+ LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str);
+ return SQRESULT_NOTNULL;
+}
+
+void InitialiseClientChatHooks(HMODULE baseAddress)
+{
+ HookEnabler hook;
+ ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x22E580, &CHudChat__AddGameLineHook, reinterpret_cast<LPVOID*>(&CHudChat__AddGameLine));
+
+ g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
+ g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
+ g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
+}
diff --git a/NorthstarDedicatedTest/clientchathooks.h b/NorthstarDedicatedTest/clientchathooks.h
new file mode 100644
index 00000000..79a1b3e2
--- /dev/null
+++ b/NorthstarDedicatedTest/clientchathooks.h
@@ -0,0 +1,5 @@
+#pragma once
+#include "pch.h"
+#include "serverchathooks.h"
+
+void InitialiseClientChatHooks(HMODULE baseAddress);
diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp
index 4cc0a84d..dcff9f4a 100644
--- a/NorthstarDedicatedTest/dllmain.cpp
+++ b/NorthstarDedicatedTest/dllmain.cpp
@@ -34,6 +34,9 @@
#include "audio.h"
#include "buildainfile.h"
#include "configurables.h"
+#include "serverchathooks.h"
+#include "clientchathooks.h"
+#include "localchatwriter.h"
#include <string.h>
#include "pch.h"
@@ -120,13 +123,14 @@ bool InitialiseNorthstar()
AddDllLoadCallback("client.dll", InitialiseScriptMainMenuPromos);
AddDllLoadCallback("client.dll", InitialiseMiscClientFixes);
AddDllLoadCallback("client.dll", InitialiseClientPrintHooks);
+ AddDllLoadCallback("client.dll", InitialiseClientChatHooks);
+ AddDllLoadCallback("client.dll", InitialiseLocalChatWriter);
}
AddDllLoadCallback("engine.dll", InitialiseEngineSpewFuncHooks);
AddDllLoadCallback("server.dll", InitialiseServerSquirrel);
AddDllLoadCallback("engine.dll", InitialiseBanSystem);
AddDllLoadCallback("engine.dll", InitialiseServerAuthentication);
- AddDllLoadCallback("server.dll", InitialiseServerAuthenticationServerDLL);
AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer);
AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand);
AddDllLoadCallback("server.dll", InitialiseMiscServerFixes);
@@ -138,6 +142,9 @@ bool InitialiseNorthstar()
AddDllLoadCallback("engine.dll", InitialiseEngineRpakFilesystem);
AddDllLoadCallback("engine.dll", InitialiseKeyValues);
+ AddDllLoadCallback("engine.dll", InitialiseServerChatHooks_Engine);
+ AddDllLoadCallback("server.dll", InitialiseServerChatHooks_Server);
+
// maxplayers increase
AddDllLoadCallback("engine.dll", InitialiseMaxPlayersOverride_Engine);
AddDllLoadCallback("client.dll", InitialiseMaxPlayersOverride_Client);
diff --git a/NorthstarDedicatedTest/localchatwriter.cpp b/NorthstarDedicatedTest/localchatwriter.cpp
new file mode 100644
index 00000000..fada3c33
--- /dev/null
+++ b/NorthstarDedicatedTest/localchatwriter.cpp
@@ -0,0 +1,460 @@
+#include "pch.h"
+#include "localchatwriter.h"
+
+class vgui_BaseRichText_vtable;
+
+class vgui_BaseRichText
+{
+ public:
+ vgui_BaseRichText_vtable* vtable;
+};
+
+class vgui_BaseRichText_vtable
+{
+ public:
+ char unknown1[1880];
+
+ void(__fastcall* InsertChar)(vgui_BaseRichText* self, wchar_t ch);
+
+ // yes these are swapped from the Source 2013 code, who knows why
+ void(__fastcall* InsertStringWide)(vgui_BaseRichText* self, const wchar_t* wszText);
+ void(__fastcall* InsertStringAnsi)(vgui_BaseRichText* self, const char* text);
+
+ void(__fastcall* SelectNone)(vgui_BaseRichText* self);
+ void(__fastcall* SelectAllText)(vgui_BaseRichText* self);
+ void(__fastcall* SelectNoText)(vgui_BaseRichText* self);
+ void(__fastcall* CutSelected)(vgui_BaseRichText* self);
+ void(__fastcall* CopySelected)(vgui_BaseRichText* self);
+ void(__fastcall* SetPanelInteractive)(vgui_BaseRichText* self, bool bInteractive);
+ void(__fastcall* SetUnusedScrollbarInvisible)(vgui_BaseRichText* self, bool bInvis);
+
+ void* unknown2;
+
+ void(__fastcall* GotoTextStart)(vgui_BaseRichText* self);
+ void(__fastcall* GotoTextEnd)(vgui_BaseRichText* self);
+
+ void* unknown3[3];
+
+ void(__fastcall* SetVerticalScrollbar)(vgui_BaseRichText* self, bool state);
+ void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars);
+ void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, vgui_Color col);
+ void(__fastcall* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent);
+ void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction);
+ void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self);
+ void(__fastcall* InsertPossibleURLString)(
+ vgui_BaseRichText* self, const char* text, vgui_Color URLTextColor, vgui_Color normalTextColor);
+ void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength);
+ void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain);
+ void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self);
+ int(__fastcall* GetNumLines)(vgui_BaseRichText* self);
+};
+
+class CGameSettings
+{
+ public:
+ char unknown1[92];
+ int isChatEnabled;
+};
+
+// Not sure what this actually refers to but chatFadeLength and chatFadeSustain
+// have their value at the same offset
+class CGameFloatVar
+{
+ public:
+ char unknown1[88];
+ float value;
+};
+
+class CHudChat
+{
+ public:
+ char unknown1[720];
+
+ vgui_Color m_sameTeamColor;
+ vgui_Color m_enemyTeamColor;
+ vgui_Color m_mainTextColor;
+ vgui_Color m_networkNameColor;
+
+ char unknown2[12];
+
+ int m_unknownContext;
+
+ char unknown3[8];
+
+ vgui_BaseRichText* m_richText;
+
+ CHudChat* next;
+ CHudChat* previous;
+};
+
+CGameSettings** gGameSettings;
+CGameFloatVar** gChatFadeLength;
+CGameFloatVar** gChatFadeSustain;
+
+// Linked list of CHudChats
+CHudChat** gHudChatList;
+
+typedef void(__fastcall* ConvertANSIToUnicodeType)(LPCSTR ansi, int ansiCharLength, LPWSTR unicode, int unicodeCharLength);
+ConvertANSIToUnicodeType ConvertANSIToUnicode;
+
+LocalChatWriter::SwatchColor swatchColors[4] = {
+ LocalChatWriter::MainTextColor,
+ LocalChatWriter::SameTeamNameColor,
+ LocalChatWriter::EnemyTeamNameColor,
+ LocalChatWriter::NetworkNameColor,
+};
+
+vgui_Color darkColors[8] = {vgui_Color{0, 0, 0, 255}, vgui_Color{205, 49, 49, 255}, vgui_Color{13, 188, 121, 255},
+ vgui_Color{229, 229, 16, 255}, vgui_Color{36, 114, 200, 255}, vgui_Color{188, 63, 188, 255},
+ vgui_Color{17, 168, 205, 255}, vgui_Color{229, 229, 229, 255}};
+
+vgui_Color lightColors[8] = {vgui_Color{102, 102, 102, 255}, vgui_Color{241, 76, 76, 255}, vgui_Color{35, 209, 139, 255},
+ vgui_Color{245, 245, 67, 255}, vgui_Color{59, 142, 234, 255}, vgui_Color{214, 112, 214, 255},
+ vgui_Color{41, 184, 219, 255}, vgui_Color{255, 255, 255, 255}};
+
+class AnsiEscapeParser
+{
+ public:
+ explicit AnsiEscapeParser(LocalChatWriter* writer) : m_writer(writer) {}
+
+ void HandleVal(unsigned long val)
+ {
+ switch (m_next)
+ {
+ case Next::ControlType:
+ m_next = HandleControlType(val);
+ break;
+ case Next::ForegroundType:
+ m_next = HandleForegroundType(val);
+ break;
+ case Next::Foreground8Bit:
+ m_next = HandleForeground8Bit(val);
+ break;
+ case Next::ForegroundR:
+ m_next = HandleForegroundR(val);
+ break;
+ case Next::ForegroundG:
+ m_next = HandleForegroundG(val);
+ break;
+ case Next::ForegroundB:
+ m_next = HandleForegroundB(val);
+ break;
+ }
+ }
+
+ private:
+ enum class Next
+ {
+ ControlType,
+ ForegroundType,
+ Foreground8Bit,
+ ForegroundR,
+ ForegroundG,
+ ForegroundB
+ };
+
+ LocalChatWriter* m_writer;
+ Next m_next = Next::ControlType;
+ vgui_Color m_expandedColor{0, 0, 0, 0};
+
+ Next HandleControlType(unsigned long val)
+ {
+ // Reset
+ if (val == 0 || val == 39)
+ {
+ m_writer->InsertSwatchColorChange(LocalChatWriter::MainTextColor);
+ return Next::ControlType;
+ }
+
+ // Dark foreground color
+ if (val >= 30 && val < 38)
+ {
+ m_writer->InsertColorChange(darkColors[val - 30]);
+ return Next::ControlType;
+ }
+
+ // Light foreground color
+ if (val >= 90 && val < 98)
+ {
+ m_writer->InsertColorChange(lightColors[val - 90]);
+ return Next::ControlType;
+ }
+
+ // Game swatch color
+ if (val >= 110 && val < 114)
+ {
+ m_writer->InsertSwatchColorChange(swatchColors[val - 110]);
+ return Next::ControlType;
+ }
+
+ // Expanded foreground color
+ if (val == 38)
+ {
+ return Next::ForegroundType;
+ }
+
+ return Next::ControlType;
+ }
+
+ Next HandleForegroundType(unsigned long val)
+ {
+ // Next values are r,g,b
+ if (val == 2)
+ {
+ m_expandedColor = {0, 0, 0, 255};
+ return Next::ForegroundR;
+ }
+ // Next value is 8-bit swatch color
+ if (val == 5)
+ {
+ return Next::Foreground8Bit;
+ }
+
+ // Invalid
+ return Next::ControlType;
+ }
+
+ Next HandleForeground8Bit(unsigned long val)
+ {
+ if (val < 8)
+ {
+ m_writer->InsertColorChange(darkColors[val]);
+ }
+ else if (val < 16)
+ {
+ m_writer->InsertColorChange(lightColors[val - 8]);
+ }
+ else if (val < 232)
+ {
+ unsigned char code = val - 16;
+ unsigned char blue = code % 6;
+ unsigned char green = ((code - blue) / 6) % 6;
+ unsigned char red = (code - blue - (green * 6)) / 36;
+ m_writer->InsertColorChange(
+ vgui_Color{(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
+ }
+ else if (val < UCHAR_MAX)
+ {
+ unsigned char brightness = (val - 232) * 10 + 8;
+ m_writer->InsertColorChange(vgui_Color{brightness, brightness, brightness, 255});
+ }
+
+ return Next::ControlType;
+ }
+
+ Next HandleForegroundR(unsigned long val)
+ {
+ if (val >= UCHAR_MAX)
+ return Next::ControlType;
+
+ m_expandedColor.r = (unsigned char)val;
+ return Next::ForegroundG;
+ }
+
+ Next HandleForegroundG(unsigned long val)
+ {
+ if (val >= UCHAR_MAX)
+ return Next::ControlType;
+
+ m_expandedColor.g = (unsigned char)val;
+ return Next::ForegroundB;
+ }
+
+ Next HandleForegroundB(unsigned long val)
+ {
+ if (val >= UCHAR_MAX)
+ return Next::ControlType;
+
+ m_expandedColor.b = (unsigned char)val;
+ m_writer->InsertColorChange(m_expandedColor);
+ return Next::ControlType;
+ }
+};
+
+LocalChatWriter::LocalChatWriter(Context context) : m_context(context) {}
+
+void LocalChatWriter::Write(const char* str)
+{
+ char writeBuffer[256];
+
+ while (true)
+ {
+ const char* startOfEscape = strstr(str, "\033[");
+
+ if (startOfEscape == NULL)
+ {
+ // No more escape sequences, write the remaining text and exit
+ InsertText(str);
+ break;
+ }
+
+ if (startOfEscape != str)
+ {
+ // There is some text before the escape sequence, just print that
+
+ size_t copyChars = startOfEscape - str;
+ if (copyChars > 255)
+ copyChars = 255;
+ strncpy(writeBuffer, str, copyChars);
+ writeBuffer[copyChars] = 0;
+
+ InsertText(writeBuffer);
+ }
+
+ const char* escape = startOfEscape + 2;
+ str = ApplyAnsiEscape(escape);
+ }
+}
+
+void LocalChatWriter::WriteLine(const char* str)
+{
+ InsertChar(L'\n');
+ InsertSwatchColorChange(MainTextColor);
+ Write(str);
+}
+
+void LocalChatWriter::InsertChar(wchar_t ch)
+{
+ for (CHudChat* hud = *gHudChatList; hud != NULL; hud = hud->next)
+ {
+ if (hud->m_unknownContext != (int)m_context)
+ continue;
+
+ hud->m_richText->vtable->InsertChar(hud->m_richText, ch);
+ }
+
+ if (ch != L'\n')
+ {
+ InsertDefaultFade();
+ }
+}
+
+void LocalChatWriter::InsertText(const char* str)
+{
+ WCHAR messageUnicode[288];
+ ConvertANSIToUnicode(str, -1, messageUnicode, 274);
+
+ for (CHudChat* hud = *gHudChatList; hud != NULL; hud = hud->next)
+ {
+ if (hud->m_unknownContext != (int)m_context)
+ continue;
+
+ hud->m_richText->vtable->InsertStringWide(hud->m_richText, messageUnicode);
+ }
+
+ InsertDefaultFade();
+}
+
+void LocalChatWriter::InsertText(const wchar_t* str)
+{
+ for (CHudChat* hud = *gHudChatList; hud != NULL; hud = hud->next)
+ {
+ if (hud->m_unknownContext != (int)m_context)
+ continue;
+
+ hud->m_richText->vtable->InsertStringWide(hud->m_richText, str);
+ }
+
+ InsertDefaultFade();
+}
+
+void LocalChatWriter::InsertColorChange(vgui_Color color)
+{
+ for (CHudChat* hud = *gHudChatList; hud != NULL; hud = hud->next)
+ {
+ if (hud->m_unknownContext != (int)m_context)
+ continue;
+
+ hud->m_richText->vtable->InsertColorChange(hud->m_richText, color);
+ }
+}
+
+static vgui_Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
+{
+ switch (swatchColor)
+ {
+ case LocalChatWriter::MainTextColor:
+ return hud->m_mainTextColor;
+ case LocalChatWriter::SameTeamNameColor:
+ return hud->m_sameTeamColor;
+ case LocalChatWriter::EnemyTeamNameColor:
+ return hud->m_enemyTeamColor;
+ case LocalChatWriter::NetworkNameColor:
+ return hud->m_networkNameColor;
+ }
+ return vgui_Color{0, 0, 0, 0};
+}
+
+void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor)
+{
+ for (CHudChat* hud = *gHudChatList; hud != NULL; hud = hud->next)
+ {
+ if (hud->m_unknownContext != (int)m_context)
+ continue;
+ hud->m_richText->vtable->InsertColorChange(hud->m_richText, GetHudSwatchColor(hud, swatchColor));
+ }
+}
+
+const char* LocalChatWriter::ApplyAnsiEscape(const char* escape)
+{
+ AnsiEscapeParser decoder(this);
+ while (true)
+ {
+ char* afterControlType = NULL;
+ unsigned long controlType = strtoul(escape, &afterControlType, 10);
+
+ // Malformed cases:
+ // afterControlType = NULL: strtoul errored
+ // controlType = 0 and escape doesn't actually start with 0: wasn't a number
+ if (afterControlType == NULL || (controlType == 0 && escape[0] != '0'))
+ {
+ return escape;
+ }
+
+ decoder.HandleVal(controlType);
+
+ // m indicates the end of the sequence
+ if (afterControlType[0] == 'm')
+ {
+ return afterControlType + 1;
+ }
+
+ // : or ; indicates more values remain, anything else is malformed
+ if (afterControlType[0] != ':' && afterControlType[0] != ';')
+ {
+ return afterControlType;
+ }
+
+ escape = afterControlType + 1;
+ }
+}
+
+void LocalChatWriter::InsertDefaultFade()
+{
+ float fadeLength = 0.f;
+ float fadeSustain = 0.f;
+ if ((*gGameSettings)->isChatEnabled)
+ {
+ fadeLength = (*gChatFadeLength)->value;
+ fadeSustain = (*gChatFadeSustain)->value;
+ }
+
+ for (CHudChat* hud = *gHudChatList; hud != NULL; hud = hud->next)
+ {
+ if (hud->m_unknownContext != (int)m_context)
+ continue;
+ hud->m_richText->vtable->InsertFade(hud->m_richText, fadeSustain, fadeLength);
+ }
+}
+
+bool IsFirstHud(void* hud) { return hud == *gHudChatList; }
+
+void InitialiseLocalChatWriter(HMODULE baseAddress)
+{
+ gGameSettings = (CGameSettings**)((char*)baseAddress + 0x11BAA48);
+ gChatFadeLength = (CGameFloatVar**)((char*)baseAddress + 0x11BAB78);
+ gChatFadeSustain = (CGameFloatVar**)((char*)baseAddress + 0x11BAC08);
+ gHudChatList = (CHudChat**)((char*)baseAddress + 0x11BA9E8);
+
+ ConvertANSIToUnicode = (ConvertANSIToUnicodeType)((char*)baseAddress + 0x7339A0);
+}
diff --git a/NorthstarDedicatedTest/localchatwriter.h b/NorthstarDedicatedTest/localchatwriter.h
new file mode 100644
index 00000000..b9ca3220
--- /dev/null
+++ b/NorthstarDedicatedTest/localchatwriter.h
@@ -0,0 +1,50 @@
+#pragma once
+#include "pch.h"
+
+struct vgui_Color
+{
+ unsigned char r;
+ unsigned char g;
+ unsigned char b;
+ unsigned char a;
+};
+
+class LocalChatWriter
+{
+ public:
+ enum Context
+ {
+ NetworkContext = 0,
+ GameContext = 1
+ };
+ enum SwatchColor
+ {
+ MainTextColor,
+ SameTeamNameColor,
+ EnemyTeamNameColor,
+ NetworkNameColor
+ };
+
+ explicit LocalChatWriter(Context context);
+
+ // Custom chat writing with ANSI escape codes
+ void Write(const char* str);
+ void WriteLine(const char* str);
+
+ // Low-level RichText access
+ void InsertChar(wchar_t ch);
+ void InsertText(const char* str);
+ void InsertText(const wchar_t* str);
+ void InsertColorChange(vgui_Color color);
+ void InsertSwatchColorChange(SwatchColor color);
+
+ private:
+ Context m_context;
+
+ const char* ApplyAnsiEscape(const char* escape);
+ void InsertDefaultFade();
+};
+
+bool IsFirstHud(void* hud);
+
+void InitialiseLocalChatWriter(HMODULE baseAddress);
diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp
index 4618955d..57646218 100644
--- a/NorthstarDedicatedTest/serverauthentication.cpp
+++ b/NorthstarDedicatedTest/serverauthentication.cpp
@@ -13,7 +13,6 @@
#include <filesystem>
#include <thread>
#include "configurables.h"
-#include "squirrel.h"
const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!";
@@ -263,6 +262,21 @@ void ServerAuthenticationManager::WritePersistentData(void* player)
}
}
+bool ServerAuthenticationManager::CheckPlayerChatRatelimit(void* player)
+{
+ if (Plat_FloatTime() - m_additionalPlayerData[player].lastSayTextLimitStart >= 1.0)
+ {
+ m_additionalPlayerData[player].lastSayTextLimitStart = Plat_FloatTime();
+ m_additionalPlayerData[player].sayTextLimitCount = 0;
+ }
+
+ if (m_additionalPlayerData[player].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->m_nValue)
+ return false;
+
+ m_additionalPlayerData[player].sayTextLimitCount++;
+ return true;
+}
+
// auth hooks
// store these in vars so we can use them in CBaseClient::Connect
@@ -517,69 +531,6 @@ bool ProcessConnectionlessPacketHook(void* a1, netpacket_t* packet)
return ProcessConnectionlessPacket(a1, packet);
}
-void ReplaceStringInPlace(std::string& subject, const std::string& search, const std::string& replace)
-{
- size_t pos = 0;
- while ((pos = subject.find(search, pos)) != std::string::npos)
- {
- subject.replace(pos, search.length(), replace);
- pos += replace.length();
- }
-}
-
-std::string currentMessage;
-int currentPlayerId;
-int currentChannelId;
-bool shouldBlock;
-bool isProcessed = true;
-
-SQRESULT setMessage(void* sqvm)
-{
- currentMessage = ServerSq_getstring(sqvm, 1);
- currentPlayerId = ServerSq_getinteger(sqvm, 2);
- currentChannelId = ServerSq_getinteger(sqvm, 3);
- shouldBlock = ServerSq_getbool(sqvm, 4);
- return SQRESULT_NOTNULL;
-}
-
-void CServerGameDLL__OnReceivedSayTextMessageHook(void* self, unsigned int senderClientIndex, const char* message, int channelId)
-{
- void* sender = GetPlayerByIndex(senderClientIndex - 1); // senderClientIndex starts at 1
-
- // check chat ratelimits
- if (Plat_FloatTime() - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastSayTextLimitStart >= 1.0)
- {
- g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastSayTextLimitStart = Plat_FloatTime();
- g_ServerAuthenticationManager->m_additionalPlayerData[sender].sayTextLimitCount = 0;
- }
-
- if (g_ServerAuthenticationManager->m_additionalPlayerData[sender].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->m_nValue)
- return;
-
- g_ServerAuthenticationManager->m_additionalPlayerData[sender].sayTextLimitCount++;
-
- bool shouldDoChathooks = strstr(GetCommandLineA(), "-enablechathooks");
- if (shouldDoChathooks)
- {
-
- currentMessage = message;
- currentPlayerId = senderClientIndex - 1; // Stupid fix cause of index offsets
- currentChannelId = channelId;
- shouldBlock = false;
- isProcessed = false;
-
- g_ServerSquirrelManager->ExecuteCode("CServerGameDLL_ProcessMessageStartThread()");
- if (!shouldBlock && currentPlayerId + 1 == senderClientIndex) // stop player id spoofing from server
- {
- CServerGameDLL__OnReceivedSayTextMessage(self, currentPlayerId + 1, currentMessage.c_str(), currentChannelId);
- }
- }
- else
- {
- CServerGameDLL__OnReceivedSayTextMessage(self, senderClientIndex, message, channelId);
- }
-}
-
void ResetPdataCommand(const CCommand& args)
{
if (*sv_m_State == server_state_t::ss_active)
@@ -683,51 +634,3 @@ void InitialiseServerAuthentication(HMODULE baseAddress)
*((char*)ptr + 14) = (char)0x90;
}
}
-
-SQRESULT getMessageServer(void* sqvm)
-{
- ServerSq_pushstring(sqvm, currentMessage.c_str(), -1);
- return SQRESULT_NOTNULL;
-}
-SQRESULT getPlayerServer(void* sqvm)
-{
- ServerSq_pushinteger(sqvm, currentPlayerId);
- return SQRESULT_NOTNULL;
-}
-SQRESULT getChannelServer(void* sqvm)
-{
- ServerSq_pushinteger(sqvm, currentChannelId);
- return SQRESULT_NOTNULL;
-}
-SQRESULT getShouldProcessMessage(void* sqvm)
-{
- ServerSq_pushbool(sqvm, !isProcessed);
- return SQRESULT_NOTNULL;
-}
-SQRESULT pushMessage(void* sqvm)
-{
- currentMessage = ServerSq_getstring(sqvm, 1);
- currentPlayerId = ServerSq_getinteger(sqvm, 2);
- currentChannelId = ServerSq_getinteger(sqvm, 3);
- shouldBlock = ServerSq_getbool(sqvm, 4);
- isProcessed = true;
- return SQRESULT_NOTNULL;
-}
-
-void InitialiseServerAuthenticationServerDLL(HMODULE baseAddress)
-{
- HookEnabler hook;
- ENABLER_CREATEHOOK(
- hook, (char*)baseAddress + 0x1595C0, &CServerGameDLL__OnReceivedSayTextMessageHook,
- reinterpret_cast<LPVOID*>(&CServerGameDLL__OnReceivedSayTextMessage));
-
- g_ServerSquirrelManager->AddFuncRegistration(
- "void", "NSSetMessage", "string message, int playerId, int channelId, bool shouldBlock", "", setMessage);
-
- g_ServerSquirrelManager->AddFuncRegistration("string", "NSChatGetCurrentMessage", "", "", getMessageServer);
- g_ServerSquirrelManager->AddFuncRegistration("int", "NSChatGetCurrentPlayer", "", "", getPlayerServer);
- g_ServerSquirrelManager->AddFuncRegistration("int", "NSChatGetCurrentChannel", "", "", getChannelServer);
- g_ServerSquirrelManager->AddFuncRegistration("bool", "NSShouldProcessMessage", "", "", getShouldProcessMessage);
- g_ServerSquirrelManager->AddFuncRegistration(
- "void", "NSPushMessage", "string message, int playerId, int channelId, bool shouldBlock", "", pushMessage);
-}
diff --git a/NorthstarDedicatedTest/serverauthentication.h b/NorthstarDedicatedTest/serverauthentication.h
index faa7ae96..8ff5099e 100644
--- a/NorthstarDedicatedTest/serverauthentication.h
+++ b/NorthstarDedicatedTest/serverauthentication.h
@@ -96,13 +96,13 @@ class ServerAuthenticationManager
bool AuthenticatePlayer(void* player, int64_t uid, char* authToken);
bool RemovePlayerAuthData(void* player);
void WritePersistentData(void* player);
+ bool CheckPlayerChatRatelimit(void* player);
};
typedef void (*CBaseClient__DisconnectType)(void* self, uint32_t unknownButAlways1, const char* reason, ...);
extern CBaseClient__DisconnectType CBaseClient__Disconnect;
void InitialiseServerAuthentication(HMODULE baseAddress);
-void InitialiseServerAuthenticationServerDLL(HMODULE baseAddress);
extern ServerAuthenticationManager* g_ServerAuthenticationManager;
extern ConVar* Cvar_ns_player_auth_port; \ No newline at end of file
diff --git a/NorthstarDedicatedTest/serverchathooks.cpp b/NorthstarDedicatedTest/serverchathooks.cpp
new file mode 100644
index 00000000..93f7f383
--- /dev/null
+++ b/NorthstarDedicatedTest/serverchathooks.cpp
@@ -0,0 +1,195 @@
+#include "pch.h"
+#include "serverchathooks.h"
+#include <rapidjson/document.h>
+#include <rapidjson/stringbuffer.h>
+#include <rapidjson/writer.h>
+#include "serverauthentication.h"
+#include "squirrel.h"
+#include "miscserverscript.h"
+
+class CServerGameDLL;
+class CBasePlayer;
+
+class CRecipientFilter
+{
+ char unknown[58];
+};
+
+CServerGameDLL* gServer;
+
+typedef void(__fastcall* CServerGameDLL__OnReceivedSayTextMessageType)(
+ CServerGameDLL* self, unsigned int senderPlayerId, const char* text, int channelId);
+CServerGameDLL__OnReceivedSayTextMessageType CServerGameDLL__OnReceivedSayTextMessage;
+CServerGameDLL__OnReceivedSayTextMessageType CServerGameDLL__OnReceivedSayTextMessageHookBase;
+
+typedef CBasePlayer*(__fastcall* UTIL_PlayerByIndexType)(int playerIndex);
+UTIL_PlayerByIndexType UTIL_PlayerByIndex;
+
+typedef void(__fastcall* CRecipientFilter__ConstructType)(CRecipientFilter* self);
+CRecipientFilter__ConstructType CRecipientFilter__Construct;
+
+typedef void(__fastcall* CRecipientFilter__DestructType)(CRecipientFilter* self);
+CRecipientFilter__DestructType CRecipientFilter__Destruct;
+
+typedef void(__fastcall* CRecipientFilter__AddAllPlayersType)(CRecipientFilter* self);
+CRecipientFilter__AddAllPlayersType CRecipientFilter__AddAllPlayers;
+
+typedef void(__fastcall* CRecipientFilter__AddRecipientType)(CRecipientFilter* self, const CBasePlayer* player);
+CRecipientFilter__AddRecipientType CRecipientFilter__AddRecipient;
+
+typedef void(__fastcall* CRecipientFilter__MakeReliableType)(CRecipientFilter* self);
+CRecipientFilter__MakeReliableType CRecipientFilter__MakeReliable;
+
+typedef void(__fastcall* UserMessageBeginType)(CRecipientFilter* filter, const char* messagename);
+UserMessageBeginType UserMessageBegin;
+
+typedef void(__fastcall* MessageEndType)();
+MessageEndType MessageEnd;
+
+typedef void(__fastcall* MessageWriteByteType)(int iValue);
+MessageWriteByteType MessageWriteByte;
+
+typedef void(__fastcall* MessageWriteStringType)(const char* sz);
+MessageWriteStringType MessageWriteString;
+
+typedef void(__fastcall* MessageWriteBoolType)(bool bValue);
+MessageWriteBoolType MessageWriteBool;
+
+bool isSkippingHook = false;
+
+static void CServerGameDLL__OnReceivedSayTextMessageHook(CServerGameDLL* self, unsigned int senderPlayerId, const char* text, bool isTeam)
+{
+ // MiniHook doesn't allow calling the base function outside of anywhere but the hook function.
+ // To allow bypassing the hook, isSkippingHook can be set.
+ if (isSkippingHook)
+ {
+ isSkippingHook = false;
+ CServerGameDLL__OnReceivedSayTextMessageHookBase(self, senderPlayerId, text, isTeam);
+ return;
+ }
+
+ void* sender = GetPlayerByIndex(senderPlayerId - 1);
+
+ // check chat ratelimits
+ if (!g_ServerAuthenticationManager->CheckPlayerChatRatelimit(sender))
+ {
+ return;
+ }
+
+ g_ServerSquirrelManager->setupfunc("CServerGameDLL_ProcessMessageStartThread");
+ g_ServerSquirrelManager->pusharg((int)senderPlayerId - 1);
+ g_ServerSquirrelManager->pusharg(text);
+ g_ServerSquirrelManager->pusharg(isTeam);
+ g_ServerSquirrelManager->call(3);
+}
+
+void ChatSendMessage(unsigned int playerIndex, const char* text, bool isteam)
+{
+ isSkippingHook = true;
+ CServerGameDLL__OnReceivedSayTextMessage(
+ gServer,
+ // Ensure the first bit isn't set, since this indicates a custom message
+ (playerIndex + 1) & CUSTOM_MESSAGE_INDEX_MASK, text, isteam);
+}
+
+void ChatBroadcastMessage(int fromPlayerIndex, int toPlayerIndex, const char* text, bool isTeam, bool isDead, CustomMessageType messageType)
+{
+ CBasePlayer* toPlayer = NULL;
+ if (toPlayerIndex >= 0)
+ {
+ toPlayer = UTIL_PlayerByIndex(toPlayerIndex + 1);
+ if (toPlayer == NULL)
+ return;
+ }
+
+ // Build a new string where the first byte is the message type
+ char sendText[256];
+ sendText[0] = (char)messageType;
+ strncpy(sendText + 1, text, 255);
+ sendText[255] = 0;
+
+ // Anonymous custom messages use playerId=0, non-anonymous ones use a player ID with the first bit set
+ unsigned int fromPlayerId = fromPlayerIndex < 0 ? 0 : ((fromPlayerIndex + 1) | CUSTOM_MESSAGE_INDEX_BIT);
+
+ CRecipientFilter filter;
+ CRecipientFilter__Construct(&filter);
+ if (toPlayer == NULL)
+ {
+ CRecipientFilter__AddAllPlayers(&filter);
+ }
+ else
+ {
+ CRecipientFilter__AddRecipient(&filter, toPlayer);
+ }
+ CRecipientFilter__MakeReliable(&filter);
+
+ UserMessageBegin(&filter, "SayText");
+ MessageWriteByte(fromPlayerId);
+ MessageWriteString(sendText);
+ MessageWriteBool(isTeam);
+ MessageWriteBool(isDead);
+ MessageEnd();
+
+ CRecipientFilter__Destruct(&filter);
+}
+
+SQRESULT SQ_SendMessage(void* sqvm)
+{
+ int playerIndex = ServerSq_getinteger(sqvm, 1);
+ const char* text = ServerSq_getstring(sqvm, 2);
+ bool isTeam = ServerSq_getbool(sqvm, 3);
+
+ ChatSendMessage(playerIndex, text, isTeam);
+
+ return SQRESULT_NULL;
+}
+
+SQRESULT SQ_BroadcastMessage(void* sqvm)
+{
+ int fromPlayerIndex = ServerSq_getinteger(sqvm, 1);
+ int toPlayerIndex = ServerSq_getinteger(sqvm, 2);
+ const char* text = ServerSq_getstring(sqvm, 3);
+ bool isTeam = ServerSq_getbool(sqvm, 4);
+ bool isDead = ServerSq_getbool(sqvm, 5);
+ int messageType = ServerSq_getinteger(sqvm, 6);
+
+ if (messageType < 1)
+ {
+ ServerSq_pusherror(sqvm, fmt::format("Invalid message type {}", messageType).c_str());
+ return SQRESULT_ERROR;
+ }
+
+ ChatBroadcastMessage(fromPlayerIndex, toPlayerIndex, text, isTeam, isDead, (CustomMessageType)messageType);
+
+ return SQRESULT_NULL;
+}
+
+void InitialiseServerChatHooks_Engine(HMODULE baseAddress) { gServer = (CServerGameDLL*)((char*)baseAddress + 0x13F0AA98); }
+
+void InitialiseServerChatHooks_Server(HMODULE baseAddress)
+{
+ CServerGameDLL__OnReceivedSayTextMessage = (CServerGameDLL__OnReceivedSayTextMessageType)((char*)baseAddress + 0x1595C0);
+ UTIL_PlayerByIndex = (UTIL_PlayerByIndexType)((char*)baseAddress + 0x26AA10);
+ CRecipientFilter__Construct = (CRecipientFilter__ConstructType)((char*)baseAddress + 0x1E9440);
+ CRecipientFilter__Destruct = (CRecipientFilter__DestructType)((char*)baseAddress + 0x1E9700);
+ CRecipientFilter__AddAllPlayers = (CRecipientFilter__AddAllPlayersType)((char*)baseAddress + 0x1E9940);
+ CRecipientFilter__AddRecipient = (CRecipientFilter__AddRecipientType)((char*)baseAddress + 0x1E9b30);
+ CRecipientFilter__MakeReliable = (CRecipientFilter__MakeReliableType)((char*)baseAddress + 0x1EA4E0);
+
+ UserMessageBegin = (UserMessageBeginType)((char*)baseAddress + 0x15C520);
+ MessageEnd = (MessageEndType)((char*)baseAddress + 0x158880);
+ MessageWriteByte = (MessageWriteByteType)((char*)baseAddress + 0x158A90);
+ MessageWriteString = (MessageWriteStringType)((char*)baseAddress + 0x158D00);
+ MessageWriteBool = (MessageWriteBoolType)((char*)baseAddress + 0x158A00);
+
+ HookEnabler hook;
+ ENABLER_CREATEHOOK(
+ hook, CServerGameDLL__OnReceivedSayTextMessage, &CServerGameDLL__OnReceivedSayTextMessageHook,
+ reinterpret_cast<LPVOID*>(&CServerGameDLL__OnReceivedSayTextMessageHookBase));
+
+ // Chat sending functions
+ g_ServerSquirrelManager->AddFuncRegistration("void", "NSSendMessage", "int playerIndex, string text, bool isTeam", "", SQ_SendMessage);
+ g_ServerSquirrelManager->AddFuncRegistration(
+ "void", "NSBroadcastMessage", "int fromPlayerIndex, int toPlayerIndex, string text, bool isTeam, bool isDead, int messageType", "",
+ SQ_BroadcastMessage);
+}
diff --git a/NorthstarDedicatedTest/serverchathooks.h b/NorthstarDedicatedTest/serverchathooks.h
new file mode 100644
index 00000000..f3425ae6
--- /dev/null
+++ b/NorthstarDedicatedTest/serverchathooks.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "pch.h"
+#include <rapidjson/document.h>
+#include <rapidjson/stringbuffer.h>
+
+enum class CustomMessageType : char
+{
+ Chat = 1,
+ Whisper = 2
+};
+
+constexpr unsigned char CUSTOM_MESSAGE_INDEX_BIT = 0b10000000;
+constexpr unsigned char CUSTOM_MESSAGE_INDEX_MASK = ~CUSTOM_MESSAGE_INDEX_BIT;
+
+// Send a vanilla chat message as if it was from the player.
+void ChatSendMessage(unsigned int playerIndex, const char* text, bool isteam);
+
+// Send a custom message.
+// fromPlayerIndex: set to -1 for a [SERVER] message, or another value to send from a specific player
+// toPlayerIndex: set to -1 to send to all players, or another value to send to a single player
+// isTeam: display a [TEAM] badge
+// isDead: display a [DEAD] badge
+// messageType: send a specific message type
+void ChatBroadcastMessage(
+ int fromPlayerIndex, int toPlayerIndex, const char* text, bool isTeam, bool isDead, CustomMessageType messageType);
+
+void InitialiseServerChatHooks_Engine(HMODULE baseAddress);
+
+void InitialiseServerChatHooks_Server(HMODULE baseAddress);
diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp
index 20f99967..19c561c0 100644
--- a/NorthstarDedicatedTest/squirrel.cpp
+++ b/NorthstarDedicatedTest/squirrel.cpp
@@ -84,6 +84,9 @@ sq_getfloatType ServerSq_getfloat;
sq_getboolType ClientSq_getbool;
sq_getboolType ServerSq_getbool;
+sq_getType ClientSq_sq_get;
+sq_getType ServerSq_sq_get;
+
template <ScriptContext context> void ExecuteCodeCommand(const CCommand& args);
// inits
@@ -136,6 +139,8 @@ void InitialiseClientSquirrel(HMODULE baseAddress)
ClientSq_getfloat = (sq_getfloatType)((char*)baseAddress + 0x6100);
ClientSq_getbool = (sq_getboolType)((char*)baseAddress + 0x6130);
+ ClientSq_sq_get = (sq_getType)((char*)baseAddress + 0x7C30);
+
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x26130, &CreateNewVMHook<ScriptContext::CLIENT>,
reinterpret_cast<LPVOID*>(&ClientCreateNewVM)); // client createnewvm function
@@ -175,6 +180,8 @@ void InitialiseServerSquirrel(HMODULE baseAddress)
ServerSq_getfloat = (sq_getfloatType)((char*)baseAddress + 0x60E0);
ServerSq_getbool = (sq_getboolType)((char*)baseAddress + 0x6110);
+ ServerSq_sq_get = (sq_getType)((char*)baseAddress + 0x7C00);
+
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x1FE90, &SQPrintHook<ScriptContext::SERVER>,
reinterpret_cast<LPVOID*>(&ServerSQPrint)); // server print function
diff --git a/NorthstarDedicatedTest/squirrel.h b/NorthstarDedicatedTest/squirrel.h
index 8e851266..b01618f2 100644
--- a/NorthstarDedicatedTest/squirrel.h
+++ b/NorthstarDedicatedTest/squirrel.h
@@ -124,6 +124,10 @@ typedef SQBool (*sq_getboolType)(void*, SQInteger stackpos);
extern sq_getboolType ClientSq_getbool;
extern sq_getboolType ServerSq_getbool;
+typedef SQRESULT (*sq_getType)(void* sqvm, SQInteger idx);
+extern sq_getType ServerSq_sq_get;
+extern sq_getType ClientSq_sq_get;
+
template <ScriptContext context> class SquirrelManager
{
private:
@@ -193,6 +197,66 @@ template <ScriptContext context> class SquirrelManager
}
}
+ int setupfunc(const char* funcname)
+ {
+ int result = -2;
+ if (context == ScriptContext::CLIENT || context == ScriptContext::UI)
+ {
+ ClientSq_pushroottable(sqvm2);
+ ClientSq_pushstring(sqvm2, funcname, -1);
+ result = ClientSq_sq_get(sqvm2, -2);
+ ClientSq_pushroottable(sqvm2);
+ }
+ else if (context == ScriptContext::SERVER)
+ {
+ ServerSq_pushroottable(sqvm2);
+ ServerSq_pushstring(sqvm2, funcname, -1);
+ result = ServerSq_sq_get(sqvm2, -2);
+ ServerSq_pushroottable(sqvm2);
+ }
+ return result;
+ }
+
+ void pusharg(int arg)
+ {
+ if (context == ScriptContext::CLIENT || context == ScriptContext::UI)
+ ClientSq_pushinteger(sqvm2, arg);
+ else if (context == ScriptContext::SERVER)
+ ServerSq_pushinteger(sqvm2, arg);
+ }
+ void pusharg(const char* arg)
+ {
+ if (context == ScriptContext::CLIENT || context == ScriptContext::UI)
+ ClientSq_pushstring(sqvm2, arg, -1);
+ else if (context == ScriptContext::SERVER)
+ ServerSq_pushstring(sqvm2, arg, -1);
+ }
+ void pusharg(float arg)
+ {
+ if (context == ScriptContext::CLIENT || context == ScriptContext::UI)
+ ClientSq_pushfloat(sqvm2, arg);
+ else if (context == ScriptContext::SERVER)
+ ServerSq_pushfloat(sqvm2, arg);
+ }
+ void pusharg(bool arg)
+ {
+ if (context == ScriptContext::CLIENT || context == ScriptContext::UI)
+ ClientSq_pushbool(sqvm2, arg);
+ else if (context == ScriptContext::SERVER)
+ ServerSq_pushbool(sqvm2, arg);
+ }
+
+ int call(int args)
+ {
+ int result = -2;
+ if (context == ScriptContext::CLIENT || context == ScriptContext::UI)
+ result = ClientSq_call(sqvm2, args + 1, false, false);
+ else if (context == ScriptContext::SERVER)
+ result = ServerSq_call(sqvm2, args + 1, false, false);
+
+ return result;
+ }
+
void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func)
{
SQFuncRegistration* reg = new SQFuncRegistration;