aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDedicatedTest
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDedicatedTest')
-rw-r--r--NorthstarDedicatedTest/ExploitFixes.cpp237
-rw-r--r--NorthstarDedicatedTest/ExploitFixes.h3
-rw-r--r--NorthstarDedicatedTest/bansystem.cpp6
-rw-r--r--NorthstarDedicatedTest/dllmain.cpp2
-rw-r--r--NorthstarDedicatedTest/miscserverfixes.cpp108
-rw-r--r--NorthstarDedicatedTest/miscserverfixes.h3
-rw-r--r--NorthstarDedicatedTest/rpakfilesystem.cpp42
-rw-r--r--NorthstarDedicatedTest/rpakfilesystem.h8
-rw-r--r--NorthstarDedicatedTest/serverauthentication.cpp14
-rw-r--r--NorthstarDedicatedTest/squirrel.cpp44
-rw-r--r--NorthstarDedicatedTest/squirrel.h28
11 files changed, 278 insertions, 217 deletions
diff --git a/NorthstarDedicatedTest/ExploitFixes.cpp b/NorthstarDedicatedTest/ExploitFixes.cpp
index af7d48ac..44b62d55 100644
--- a/NorthstarDedicatedTest/ExploitFixes.cpp
+++ b/NorthstarDedicatedTest/ExploitFixes.cpp
@@ -5,9 +5,6 @@
#include "NSMem.h"
#include "cvar.h"
-typedef char(__fastcall* function_containing_emit_t)(uint64_t a1, uint64_t a2);
-function_containing_emit_t function_containing_emit;
-ConVar* sv_cheats;
ConVar* ns_exploitfixes_log;
#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
#define BLOCKED_INFO(s) \
@@ -23,36 +20,15 @@ ConVar* ns_exploitfixes_log;
return false; \
}())
-// Make sure 3 or less floats are valid
-bool ValidateFloats(float a, float b = 0, float c = 0)
+struct Float3
{
- return !isnan(a) && !isnan(b) && !isnan(c);
-}
-
-struct Vector
-{
- float x, y, z;
+ float vals[3];
- Vector(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {}
-
- bool IsValid()
+ void MakeValid()
{
- return ValidateFloats(x, y, z);
- }
-};
-
-struct Angle
-{
- float pitch, yaw, roll;
-
- Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {}
-
- bool IsInvalid()
- {
- if (!ValidateFloats(pitch, yaw, roll))
- return false;
-
- return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180);
+ for (auto& val : vals)
+ if (isnan(val))
+ val = 0;
}
};
@@ -232,11 +208,11 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
DWORD command_number;
DWORD tick_count;
float command_time;
- Angle worldViewAngles;
+ Float3 worldViewAngles;
BYTE gap18[4];
- Angle localViewAngles;
- Angle attackangles;
- Vector move;
+ Float3 localViewAngles;
+ Float3 attackangles;
+ Float3 move;
DWORD buttons;
BYTE impulse;
short weaponselect;
@@ -244,8 +220,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
BYTE gap4C[24];
char headoffset;
BYTE gap65[11];
- Vector cameraPos;
- Angle cameraAngles;
+ Float3 cameraPos;
+ Float3 cameraAngles;
BYTE gap88[4];
int tickSomething;
DWORD dword90;
@@ -260,31 +236,19 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
std::string BLOCK_PREFIX =
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
- if (cmd->worldViewAngles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid worldViewAngles");
- goto INVALID_CMD;
- }
-
- if (cmd->attackangles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid attackangles");
- goto INVALID_CMD;
- }
+ // Fix invalid player angles
+ cmd->worldViewAngles.MakeValid();
+ cmd->attackangles.MakeValid();
+ cmd->localViewAngles.MakeValid();
- if (cmd->localViewAngles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid localViewAngles");
- goto INVALID_CMD;
- }
+ // Fix invalid camera angles
+ cmd->cameraPos.MakeValid();
+ cmd->cameraAngles.MakeValid();
- if (cmd->cameraAngles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid cameraAngles");
- goto INVALID_CMD;
- }
+ // Fix invaid movement vector
+ cmd->move.MakeValid();
- if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
+ if (cmd->tick_count == 0 || cmd->command_time <= 0)
{
BLOCKED_INFO(
"Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
@@ -292,26 +256,14 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
goto INVALID_CMD; // No simulation of bogus-timed cmds
}
- if (!cmd->move.IsValid())
- {
- BLOCKED_INFO("Invalid move vector");
- goto INVALID_CMD;
- }
-
- if (!cmd->cameraPos.IsValid())
- {
- BLOCKED_INFO("Invalid cameraPos"); // IIRC this can crash spectating clients or anyone watching replays
- goto INVALID_CMD;
- }
-
return;
-INVALID_CMD:
+INVALID_CMD:
// Fix any gameplay-affecting cmd properties
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems
- cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = Angle(0, 0, 0);
+ cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0};
cmd->tick_count = cmd->frameTime = 0;
- cmd->move = cmd->cameraPos = Vector(0, 0, 0);
+ cmd->move = cmd->cameraPos = {0, 0, 0};
cmd->buttons = 0;
cmd->meleetarget = 0;
}
@@ -320,7 +272,11 @@ INVALID_CMD:
// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients
// especially since we have script commands this could theoretically be awful
#include "gameutils.h"
-KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
+
+typedef void (*IsValveModType)();
+IsValveModType IsValveMod;
+
+bool IsValveModHook()
{
return !CommandLine()->CheckParm("-norestrictservercommands");
}
@@ -365,6 +321,83 @@ static void* GetEntByIndexHook(int idx)
return GetEntByIndex(idx);
}
+// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b
+// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
+// out of the bounds of the output buffer.
+KHOOK(
+ LZSS_SafeUncompress,
+ ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 33 ED 41 8B F9"),
+ uint32_t,
+ __fastcall,
+ (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
+{
+ static constexpr int LZSS_LOOKSHIFT = 4;
+
+ uint32_t totalBytes = 0;
+ int getCmdByte = 0, cmdByte = 0;
+
+ struct lzss_header_t
+ {
+ uint32_t id, actualSize;
+ };
+
+ lzss_header_t header = *(lzss_header_t*)pInput;
+
+ if (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || header.actualSize > unBufSize)
+ return 0;
+
+ pInput += sizeof(lzss_header_t);
+
+ for (;;)
+ {
+ if (!getCmdByte)
+ cmdByte = *pInput++;
+
+ getCmdByte = (getCmdByte + 1) & 0x07;
+
+ if (cmdByte & 0x01)
+ {
+ int position = *pInput++ << LZSS_LOOKSHIFT;
+ position |= (*pInput >> LZSS_LOOKSHIFT);
+ position += 1;
+
+ int count = (*pInput++ & 0x0F) + 1;
+ if (count == 1)
+ break;
+
+ // Ensure reference chunk exists entirely within our buffer
+ if (position > totalBytes)
+ return 0;
+
+ totalBytes += count;
+ if (totalBytes > unBufSize)
+ return 0;
+
+ unsigned char* pSource = pOutput - position;
+ for (int i = 0; i < count; i++)
+ *pOutput++ = *pSource++;
+ }
+ else
+ {
+ totalBytes++;
+ if (totalBytes > unBufSize)
+ return 0;
+
+ *pOutput++ = *pInput++;
+ }
+ cmdByte = cmdByte >> 1;
+ }
+
+ if (totalBytes == header.actualSize)
+ {
+ return totalBytes;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
//////////////////////////////////////////////////
void DoBytePatches()
@@ -412,16 +445,49 @@ void DoBytePatches()
}
}
-char function_containing_emit_hook(uint64_t unknown_value, uint64_t command_ptr)
+KHOOK(
+ SpecialClientCommand,
+ ("server.dll", "48 89 5C 24 ? 48 89 74 24 ? 55 57 41 56 48 8D 6C 24 ? 48 81 EC ? ? ? ? 83 3A 00"),
+ bool,
+ __fastcall,
+ (void* player, CCommand* command))
{
- char* command_string = *(char**)(command_ptr + 1040); // From decompile
- if (!sv_cheats->m_Value.m_nValue && !_strnicmp(command_string, "emit", 5))
+ static ConVar* sv_cheats = g_pCVar->FindVar("sv_cheats");
+
+ if (sv_cheats->GetBool())
+ return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on
+
+ // These are mostly from Portal 2 (sigh)
+ constexpr const char* blockedCommands[] = {
+ "emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
+
+ // These both execute a command for every single entity for some reason, nice one valve
+ "pre_go_to_hub",
+ "pre_go_to_calibration",
+
+ "end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
+ "load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
+ };
+
+ if (command->ArgC() > 0)
{
- spdlog::info("Blocking command \"emit\" because sv_cheats was 0");
- return 1;
+ std::string cmdStr = command->Arg(0);
+ for (char& c : cmdStr)
+ c = tolower(c);
+
+ for (const char* blockedCommand : blockedCommands)
+ {
+ if (cmdStr.find(blockedCommand) != std::string::npos)
+ {
+ // Block this command
+ spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
+ return true;
+ }
+ }
}
- return function_containing_emit(unknown_value, command_ptr);
+
+ return oSpecialClientCommand(player, command);
}
void ExploitFixes::LoadCallback(HMODULE baseAddress)
@@ -448,9 +514,12 @@ void ExploitFixes::LoadCallback(HMODULE baseAddress)
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
HookEnabler hook;
-
- sv_cheats = g_pCVar->FindVar("sv_cheats");
- ENABLER_CREATEHOOK(
- hook, (char*)baseAddress + 0x5889A0, &function_containing_emit_hook, reinterpret_cast<LPVOID*>(&function_containing_emit));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast<LPVOID*>(&GetEntByIndex));
}
+
+void ExploitFixes::LoadCallbackEngine(HMODULE baseAddress)
+{
+ spdlog::info("ExploitFixes::LoadCallbackEngine ...");
+ HookEnabler hook;
+ ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1C6360, &IsValveModHook, reinterpret_cast<LPVOID*>(&IsValveMod));
+} \ No newline at end of file
diff --git a/NorthstarDedicatedTest/ExploitFixes.h b/NorthstarDedicatedTest/ExploitFixes.h
index 7a407a3d..1cafe758 100644
--- a/NorthstarDedicatedTest/ExploitFixes.h
+++ b/NorthstarDedicatedTest/ExploitFixes.h
@@ -6,4 +6,5 @@
namespace ExploitFixes
{
void LoadCallback(HMODULE unused);
-} \ No newline at end of file
+ void LoadCallbackEngine(HMODULE baseAddress);
+} // namespace ExploitFixes \ No newline at end of file
diff --git a/NorthstarDedicatedTest/bansystem.cpp b/NorthstarDedicatedTest/bansystem.cpp
index 93cf9c65..9c2507fd 100644
--- a/NorthstarDedicatedTest/bansystem.cpp
+++ b/NorthstarDedicatedTest/bansystem.cpp
@@ -20,7 +20,7 @@ void ServerBanSystem::OpenBanlist()
{
std::string line;
while (std::getline(enabledModsStream, line))
- m_vBannedUids.push_back(strtoll(line.c_str(), nullptr, 10));
+ m_vBannedUids.push_back(strtoull(line.c_str(), nullptr, 10));
enabledModsStream.close();
}
@@ -74,7 +74,7 @@ void BanPlayerCommand(const CCommand& args)
if (!strcmp((char*)player + 0x16, args.Arg(1)) || !strcmp((char*)player + 0xF500, args.Arg(1)))
{
- g_ServerBanSystem->BanUID(strtoll((char*)player + 0xF500, nullptr, 10));
+ g_ServerBanSystem->BanUID(strtoull((char*)player + 0xF500, nullptr, 10));
CBaseClient__Disconnect(player, 1, "Banned from server");
break;
}
@@ -87,7 +87,7 @@ void UnbanPlayerCommand(const CCommand& args)
return;
// assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything
- g_ServerBanSystem->UnbanUID(strtoll(args.Arg(1), nullptr, 10));
+ g_ServerBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
}
void ClearBanlistCommand(const CCommand& args)
diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp
index 266bf10f..67b350de 100644
--- a/NorthstarDedicatedTest/dllmain.cpp
+++ b/NorthstarDedicatedTest/dllmain.cpp
@@ -264,7 +264,6 @@ bool InitialiseNorthstar()
AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer);
AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand);
AddDllLoadCallback("server.dll", InitialiseMiscServerFixes);
- AddDllLoadCallback("engine.dll", InitialiseMiscEngineServerFixes);
AddDllLoadCallback("server.dll", InitialiseBuildAINFileHooks);
AddDllLoadCallback("engine.dll", InitializeCHostStateHooks);
@@ -289,6 +288,7 @@ bool InitialiseNorthstar()
// activate exploit fixes
AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback);
+ AddDllLoadCallback("engine.dll", ExploitFixes::LoadCallbackEngine);
// run callbacks for any libraries that are already loaded by now
CallAllPendingDLLLoadCallbacks();
diff --git a/NorthstarDedicatedTest/miscserverfixes.cpp b/NorthstarDedicatedTest/miscserverfixes.cpp
index ddbfcfbb..e85950c4 100644
--- a/NorthstarDedicatedTest/miscserverfixes.cpp
+++ b/NorthstarDedicatedTest/miscserverfixes.cpp
@@ -23,110 +23,4 @@ void InitialiseMiscServerFixes(HMODULE baseAddress)
{
NSMem::BytePatch(ba + 0x153920, "C3");
}
-}
-
-typedef unsigned int(__fastcall* CLZSS__SafeUncompressType)(
- void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize);
-CLZSS__SafeUncompressType CLZSS__SafeUncompress;
-
-struct lzss_header_t
-{
- unsigned int id;
- unsigned int actualSize;
-};
-
-static constexpr int LZSS_LOOKSHIFT = 4;
-
-// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
-// out of the bounds of the output buffer.
-static unsigned int CLZSS__SafeUncompressHook(void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize)
-{
- unsigned int totalBytes = 0;
- int getCmdByte = 0;
- int cmdByte = 0;
-
- lzss_header_t header = *(lzss_header_t*)pInput;
-
- if (pInput == NULL)
- {
- return 0;
- }
- if (header.id != 0x53535a4c)
- {
- return 0;
- }
- if (header.actualSize == 0)
- {
- return 0;
- }
- if (header.actualSize > unBufSize)
- {
- return 0;
- }
-
- pInput += sizeof(lzss_header_t);
-
- for (;;)
- {
- if (!getCmdByte)
- {
- cmdByte = *pInput++;
- }
- getCmdByte = (getCmdByte + 1) & 0x07;
-
- if (cmdByte & 0x01)
- {
- int position = *pInput++ << LZSS_LOOKSHIFT;
- position |= (*pInput >> LZSS_LOOKSHIFT);
- position += 1;
- int count = (*pInput++ & 0x0F) + 1;
- if (count == 1)
- {
- break;
- }
-
- // Ensure reference chunk exists entirely within our buffer
- if (position > totalBytes)
- {
- return 0;
- }
-
- totalBytes += count;
- if (totalBytes > unBufSize)
- {
- return 0;
- }
-
- unsigned char* pSource = pOutput - position;
- for (int i = 0; i < count; i++)
- {
- *pOutput++ = *pSource++;
- }
- }
- else
- {
- totalBytes++;
- if (totalBytes > unBufSize)
- {
- return 0;
- }
- *pOutput++ = *pInput++;
- }
- cmdByte = cmdByte >> 1;
- }
-
- if (totalBytes != header.actualSize)
- {
- return 0;
- }
-
- return totalBytes;
-
- return 0;
-}
-
-void InitialiseMiscEngineServerFixes(HMODULE baseAddress)
-{
- HookEnabler hook;
- ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x432a10, &CLZSS__SafeUncompressHook, reinterpret_cast<LPVOID*>(&CLZSS__SafeUncompress));
-}
+} \ No newline at end of file
diff --git a/NorthstarDedicatedTest/miscserverfixes.h b/NorthstarDedicatedTest/miscserverfixes.h
index 3fdf56a8..d1c05a6b 100644
--- a/NorthstarDedicatedTest/miscserverfixes.h
+++ b/NorthstarDedicatedTest/miscserverfixes.h
@@ -1,2 +1 @@
-void InitialiseMiscServerFixes(HMODULE baseAddress);
-void InitialiseMiscEngineServerFixes(HMODULE baseAddress); \ No newline at end of file
+void InitialiseMiscServerFixes(HMODULE baseAddress); \ No newline at end of file
diff --git a/NorthstarDedicatedTest/rpakfilesystem.cpp b/NorthstarDedicatedTest/rpakfilesystem.cpp
index d52f9ef1..b3105996 100644
--- a/NorthstarDedicatedTest/rpakfilesystem.cpp
+++ b/NorthstarDedicatedTest/rpakfilesystem.cpp
@@ -43,11 +43,37 @@ void PakLoadManager::LoadPakAsync(const char* path, bool bMarkForUnload)
void PakLoadManager::UnloadPaks()
{
for (int pakHandle : m_pakHandlesToUnload)
+ {
g_pakLoadApi->UnloadPak(pakHandle, nullptr);
+ // remove pak from loadedPaks and loadedPaksInv
+ RemoveLoadedPak(pakHandle);
+ }
m_pakHandlesToUnload.clear();
}
+bool PakLoadManager::IsPakLoaded(int32_t pakHandle)
+{
+ return loadedPaks.find(pakHandle) != loadedPaks.end();
+}
+
+bool PakLoadManager::IsPakLoaded(size_t hash)
+{
+ return loadedPaksInv.find(hash) != loadedPaksInv.end();
+}
+
+void PakLoadManager::AddLoadedPak(int32_t pakHandle, size_t hash)
+{
+ loadedPaks[pakHandle] = hash;
+ loadedPaksInv[hash] = pakHandle;
+}
+
+void PakLoadManager::RemoveLoadedPak(int32_t pakHandle)
+{
+ loadedPaksInv.erase(loadedPaks[pakHandle]);
+ loadedPaks.erase(pakHandle);
+}
+
void HandlePakAliases(char** map)
{
// convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift
@@ -152,6 +178,13 @@ void* LoadPakSyncHook(char* path, void* unknownSingleton, int flags)
LoadPakAsyncType LoadPakAsyncOriginal;
int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callback0, void* callback1)
{
+ size_t hash = STR_HASH(path);
+ // if the hash is already in the map, dont load the pak, it has already been loaded
+ if (g_PakLoadManager->IsPakLoaded(hash))
+ {
+ return -1;
+ }
+
HandlePakAliases(&path);
bool bNeedToFreePakName = false;
@@ -176,6 +209,9 @@ int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callba
int ret = LoadPakAsyncOriginal(path, unknownSingleton, flags, callback0, callback1);
spdlog::info("LoadPakAsync {} {}", path, ret);
+ // add the hash to the map
+ g_PakLoadManager->AddLoadedPak(ret, hash);
+
if (bNeedToFreePakName)
delete[] path;
@@ -185,6 +221,12 @@ int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callba
UnloadPakType UnloadPakOriginal;
void* UnloadPakHook(int pakHandle, void* callback)
{
+ if (g_PakLoadManager->IsPakLoaded(pakHandle))
+ {
+ // remove the entry
+ g_PakLoadManager->RemoveLoadedPak(pakHandle);
+ }
+
static bool bShouldUnloadPaks = true;
if (bShouldUnloadPaks)
{
diff --git a/NorthstarDedicatedTest/rpakfilesystem.h b/NorthstarDedicatedTest/rpakfilesystem.h
index 3c104822..12ef983e 100644
--- a/NorthstarDedicatedTest/rpakfilesystem.h
+++ b/NorthstarDedicatedTest/rpakfilesystem.h
@@ -9,8 +9,16 @@ class PakLoadManager
void LoadPakAsync(const char* path, bool bMarkForUnload);
void UnloadPaks();
+ bool IsPakLoaded(int32_t pakHandle);
+ bool IsPakLoaded(size_t hash);
+ void AddLoadedPak(int32_t pakHandle, size_t hash);
+ void RemoveLoadedPak(int32_t pakHandle);
+
private:
std::vector<int> m_pakHandlesToUnload;
+ // these size_t s are the asset path hashed with STR_HASH
+ std::unordered_map<int32_t, size_t> loadedPaks {};
+ std::unordered_map<size_t, int32_t> loadedPaksInv {};
};
extern PakLoadManager* g_PakLoadManager; \ No newline at end of file
diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp
index 8d7c063b..0672f3c6 100644
--- a/NorthstarDedicatedTest/serverauthentication.cpp
+++ b/NorthstarDedicatedTest/serverauthentication.cpp
@@ -16,6 +16,9 @@
const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!";
+// This convar defines whether to log all client commands
+ConVar* Cvar_ns_should_log_all_clientcommands;
+
// hook types
typedef void* (*CBaseServer__ConnectClientType)(
@@ -243,7 +246,8 @@ bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid,
// set persistent data as ready, we use 0x3 internally to mark the client as using local persistence
*((char*)player + 0x4a0) = (char)0x3;
- if (!CVar_ns_auth_allow_insecure->GetBool()) // no auth data and insecure connections aren't allowed, so dc the client
+ // no auth data and insecure connections aren't allowed, so dc the client
+ if (!CVar_ns_auth_allow_insecure->GetBool() && strncmp(GetCurrentPlaylistName(), "solo", 5) != 0)
return false;
// insecure connections are allowed, try reading from disk
@@ -477,6 +481,12 @@ CCommand__TokenizeType CCommand__Tokenize;
char CGameClient__ExecuteStringCommandHook(void* self, uint32_t unknown, const char* pCommandString)
{
+ // Only log clientcommands if the convar `ns_should_log_all_clientcommands` equals 1
+ if (Cvar_ns_should_log_all_clientcommands->GetBool())
+ {
+ spdlog::info("{} (UID: {}) executed command: \"{}\"", (char*)self + 0x16, (char*)self + 0xF500, pCommandString);
+ }
+
if (CVar_sv_quota_stringcmdspersecond->GetInt() != -1)
{
// note: this isn't super perfect, legit clients can trigger it in lobby, mostly good enough tho imo
@@ -669,6 +679,8 @@ void InitialiseServerAuthentication(HMODULE baseAddress)
"100",
FCVAR_GAMEDLL,
"Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget");
+ Cvar_ns_should_log_all_clientcommands =
+ new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands");
Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, "");
Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, "");
Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, "");
diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp
index 573fb29e..2fd558cd 100644
--- a/NorthstarDedicatedTest/squirrel.cpp
+++ b/NorthstarDedicatedTest/squirrel.cpp
@@ -8,6 +8,7 @@
#include "dedicated.h"
#include "rcon_shared.h"
#include "sv_rcon.h"
+#include "gameutils.h"
// hook forward declarations
typedef SQInteger (*SQPrintType)(void* sqvm, char* fmt, ...);
@@ -36,6 +37,10 @@ CallScriptInitCallbackType ClientCallScriptInitCallback;
CallScriptInitCallbackType ServerCallScriptInitCallback;
template <ScriptContext context> char CallScriptInitCallbackHook(void* sqvm, const char* callback);
+RegisterSquirrelFuncType ClientRegisterSquirrelFunc;
+RegisterSquirrelFuncType ServerRegisterSquirrelFunc;
+template <ScriptContext context> int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown);
+
// core sqvm funcs
sq_compilebufferType ClientSq_compilebuffer;
sq_compilebufferType ServerSq_compilebuffer;
@@ -46,9 +51,6 @@ sq_pushroottableType ServerSq_pushroottable;
sq_callType ClientSq_call;
sq_callType ServerSq_call;
-RegisterSquirrelFuncType ClientRegisterSquirrelFunc;
-RegisterSquirrelFuncType ServerRegisterSquirrelFunc;
-
// sq stack array funcs
sq_newarrayType ClientSq_newarray;
sq_newarrayType ServerSq_newarray;
@@ -164,6 +166,11 @@ void InitialiseClientSquirrel(HMODULE baseAddress)
(char*)baseAddress + 0x10190,
&CallScriptInitCallbackHook<ScriptContext::CLIENT>,
reinterpret_cast<LPVOID*>(&ClientCallScriptInitCallback)); // client callscriptinitcallback function
+ ENABLER_CREATEHOOK(
+ hook,
+ (char*)baseAddress + 0x108E0,
+ &RegisterSquirrelFuncHook<ScriptContext::CLIENT>,
+ reinterpret_cast<LPVOID*>(&ClientRegisterSquirrelFunc)); // client registersquirrelfunc function
}
void InitialiseServerSquirrel(HMODULE baseAddress)
@@ -219,6 +226,12 @@ void InitialiseServerSquirrel(HMODULE baseAddress)
&CallScriptInitCallbackHook<ScriptContext::SERVER>,
reinterpret_cast<LPVOID*>(&ServerCallScriptInitCallback)); // server callscriptinitcallback function
+ ENABLER_CREATEHOOK(
+ hook,
+ (char*)baseAddress + 0x1DD10,
+ &RegisterSquirrelFuncHook<ScriptContext::SERVER>,
+ reinterpret_cast<LPVOID*>(&ServerRegisterSquirrelFunc)); // server registersquirrelfunc function
+
// cheat and clientcmd_can_execute allows clients to execute this, but since it's unsafe we only allow it when cheats are enabled
// for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want
RegisterConCommand(
@@ -477,4 +490,29 @@ template <ScriptContext context> void ExecuteCodeCommand(const CCommand& args)
g_UISquirrelManager->ExecuteCode(args.ArgS());
else if (context == ScriptContext::SERVER)
g_ServerSquirrelManager->ExecuteCode(args.ArgS());
+}
+
+SQRESULT SQ_DevFuncStub(void* sqvm)
+{
+ spdlog::warn("Blocked execution of squirrel developer function for security reasons. To re-enable them use start parameter "
+ "-allowSquirrelDevFunctions.");
+ return SQRESULT_NULL;
+}
+
+template <ScriptContext context> int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown)
+{
+ static std::set<std::string> allowedDevFunctions = {
+ "Dev_CommandLineHasParm",
+ "Dev_CommandLineParmValue",
+ "Dev_CommandLineRemoveParm",
+ };
+
+ if ((funcReg->devLevel == 1) && (!CommandLine()->CheckParm("-allowSquirrelDevFunctions")) &&
+ (!allowedDevFunctions.count(funcReg->squirrelFuncName)))
+ funcReg->funcPtr = SQ_DevFuncStub;
+
+ if (context == ScriptContext::SERVER)
+ return ServerRegisterSquirrelFunc(sqvm, funcReg, unknown);
+ else
+ return ClientRegisterSquirrelFunc(sqvm, funcReg, unknown);
} \ No newline at end of file
diff --git a/NorthstarDedicatedTest/squirrel.h b/NorthstarDedicatedTest/squirrel.h
index cc39cc2a..d6dfcc57 100644
--- a/NorthstarDedicatedTest/squirrel.h
+++ b/NorthstarDedicatedTest/squirrel.h
@@ -37,25 +37,23 @@ struct SQFuncRegistration
const char* squirrelFuncName;
const char* cppFuncName;
const char* helpText;
- const char* returnValueType;
+ const char* returnTypeString;
const char* argTypes;
- int16_t somethingThatsZero;
- int16_t padding1;
- int32_t unknown1;
- int64_t unknown2;
- int32_t unknown3;
- int32_t padding2;
- int64_t unknown4;
- int64_t unknown5;
- int64_t unknown6;
- int32_t unknown7;
- int32_t padding3;
+ __int32 unknown1;
+ __int32 devLevel;
+ const char* shortNameMaybe;
+ __int32 unknown2;
+ __int32 returnTypeEnum;
+ __int32* externalBufferPointer;
+ __int64 externalBufferSize;
+ __int64 unknown3;
+ __int64 unknown4;
void* funcPtr;
SQFuncRegistration()
{
memset(this, 0, sizeof(SQFuncRegistration));
- this->padding2 = 32;
+ this->returnTypeEnum = 32;
}
};
@@ -277,8 +275,8 @@ template <ScriptContext context> class SquirrelManager
reg->helpText = new char[helpText.size() + 1];
strcpy((char*)reg->helpText, helpText.c_str());
- reg->returnValueType = new char[returnType.size() + 1];
- strcpy((char*)reg->returnValueType, returnType.c_str());
+ reg->returnTypeString = new char[returnType.size() + 1];
+ strcpy((char*)reg->returnTypeString, returnType.c_str());
reg->argTypes = new char[argTypes.size() + 1];
strcpy((char*)reg->argTypes, argTypes.c_str());