diff options
Diffstat (limited to 'NorthstarDLL')
150 files changed, 0 insertions, 26604 deletions
diff --git a/NorthstarDLL/CMakeLists.txt b/NorthstarDLL/CMakeLists.txt deleted file mode 100644 index d238f61f..00000000 --- a/NorthstarDLL/CMakeLists.txt +++ /dev/null @@ -1,187 +0,0 @@ -# NorthstarDLL - -find_package(minhook REQUIRED) -find_package(libcurl REQUIRED) -find_package(minizip REQUIRED) - -add_library(NorthstarDLL SHARED - "resources.rc" - "client/audio.cpp" - "client/audio.h" - "client/chatcommand.cpp" - "client/clientauthhooks.cpp" - "client/clientruihooks.cpp" - "client/clientvideooverrides.cpp" - "client/debugoverlay.cpp" - "client/demofixes.cpp" - "client/diskvmtfixes.cpp" - "client/languagehooks.cpp" - "client/latencyflex.cpp" - "client/localchatwriter.cpp" - "client/localchatwriter.h" - "client/modlocalisation.cpp" - "client/r2client.cpp" - "client/r2client.h" - "client/rejectconnectionfixes.cpp" - "config/profile.cpp" - "config/profile.h" - "core/convar/concommand.cpp" - "core/convar/concommand.h" - "core/convar/convar.cpp" - "core/convar/convar.h" - "core/convar/cvar.cpp" - "core/convar/cvar.h" - "core/filesystem/filesystem.cpp" - "core/filesystem/filesystem.h" - "core/filesystem/rpakfilesystem.cpp" - "core/filesystem/rpakfilesystem.h" - "core/math/bitbuf.h" - "core/math/bits.cpp" - "core/math/bits.h" - "core/math/color.cpp" - "core/math/color.h" - "core/math/vector.h" - "core/hooks.cpp" - "core/hooks.h" - "core/macros.h" - "core/memalloc.cpp" - "core/memalloc.h" - "core/memory.cpp" - "core/memory.h" - "core/sourceinterface.cpp" - "core/sourceinterface.h" - "core/structs.h" - "core/tier0.cpp" - "core/tier0.h" - "dedicated/dedicated.cpp" - "dedicated/dedicated.h" - "dedicated/dedicatedlogtoclient.cpp" - "dedicated/dedicatedlogtoclient.h" - "dedicated/dedicatedmaterialsystem.cpp" - "engine/host.cpp" - "engine/hoststate.cpp" - "engine/hoststate.h" - "engine/r2engine.cpp" - "engine/r2engine.h" - "engine/runframe.cpp" - "logging/crashhandler.cpp" - "logging/crashhandler.h" - "logging/logging.cpp" - "logging/logging.h" - "logging/loghooks.cpp" - "logging/loghooks.h" - "logging/sourceconsole.cpp" - "logging/sourceconsole.h" - "masterserver/masterserver.cpp" - "masterserver/masterserver.h" - "mods/autodownload/moddownloader.h" - "mods/autodownload/moddownloader.cpp" - "mods/compiled/kb_act.cpp" - "mods/compiled/modkeyvalues.cpp" - "mods/compiled/modpdef.cpp" - "mods/compiled/modscriptsrson.cpp" - "mods/modmanager.cpp" - "mods/modmanager.h" - "mods/modsavefiles.cpp" - "mods/modsavefiles.h" - "plugins/plugin_abi.h" - "plugins/pluginbackend.cpp" - "plugins/pluginbackend.h" - "plugins/plugins.cpp" - "plugins/plugins.h" - "scripts/client/clientchathooks.cpp" - "scripts/client/cursorposition.cpp" - "scripts/client/scriptbrowserhooks.cpp" - "scripts/client/scriptmainmenupromos.cpp" - "scripts/client/scriptmodmenu.cpp" - "scripts/client/scriptoriginauth.cpp" - "scripts/client/scriptserverbrowser.cpp" - "scripts/client/scriptservertoclientstringcommand.cpp" - "scripts/server/miscserverfixes.cpp" - "scripts/server/miscserverscript.cpp" - "scripts/server/scriptuserinfo.cpp" - "scripts/scriptdatatables.cpp" - "scripts/scripthttprequesthandler.cpp" - "scripts/scripthttprequesthandler.h" - "scripts/scriptjson.cpp" - "scripts/scriptjson.h" - "scripts/scriptutility.cpp" - "server/auth/bansystem.cpp" - "server/auth/bansystem.h" - "server/auth/serverauthentication.cpp" - "server/auth/serverauthentication.h" - "server/alltalk.cpp" - "server/buildainfile.cpp" - "server/r2server.cpp" - "server/r2server.h" - "server/serverchathooks.cpp" - "server/serverchathooks.h" - "server/servernethooks.cpp" - "server/serverpresence.cpp" - "server/serverpresence.h" - "shared/exploit_fixes/exploitfixes.cpp" - "shared/exploit_fixes/exploitfixes_lzss.cpp" - "shared/exploit_fixes/exploitfixes_utf8parser.cpp" - "shared/exploit_fixes/ns_limits.cpp" - "shared/exploit_fixes/ns_limits.h" - "shared/keyvalues.cpp" - "shared/keyvalues.h" - "shared/maxplayers.cpp" - "shared/maxplayers.h" - "shared/misccommands.cpp" - "shared/misccommands.h" - "shared/playlist.cpp" - "shared/playlist.h" - "squirrel/squirrel.cpp" - "squirrel/squirrel.h" - "squirrel/squirrelautobind.cpp" - "squirrel/squirrelautobind.h" - "squirrel/squirrelclasstypes.h" - "squirrel/squirreldatatypes.h" - "util/printcommands.cpp" - "util/printcommands.h" - "util/printmaps.cpp" - "util/printmaps.h" - "util/utils.cpp" - "util/utils.h" - "util/version.cpp" - "util/version.h" - "util/wininfo.cpp" - "util/wininfo.h" - "dllmain.cpp" - "dllmain.h" - "ns_version.h" -) - -target_link_libraries(NorthstarDLL PRIVATE - minhook - libcurl - minizip - WS2_32.lib - Crypt32.lib - Cryptui.lib - dbghelp.lib - Wldap32.lib - Normaliz.lib - Bcrypt.lib - version.lib -) - -target_include_directories(NorthstarDLL PRIVATE - ${CMAKE_SOURCE_DIR}/NorthstarDLL - ${CMAKE_SOURCE_DIR}/thirdparty -) - -target_precompile_headers(NorthstarDLL PRIVATE pch.h) - -target_compile_definitions(NorthstarDLL PRIVATE - UNICODE - _UNICODE - CURL_STATICLIB -) - -set_target_properties(NorthstarDLL PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${NS_BINARY_DIR} - OUTPUT_NAME Northstar - LINK_FLAGS "/MANIFEST:NO /DEBUG" -) diff --git a/NorthstarDLL/client/audio.cpp b/NorthstarDLL/client/audio.cpp deleted file mode 100644 index aa32e390..00000000 --- a/NorthstarDLL/client/audio.cpp +++ /dev/null @@ -1,504 +0,0 @@ -#include "audio.h" -#include "dedicated/dedicated.h" -#include "core/convar/convar.h" - -#include "rapidjson/error/en.h" -#include <fstream> -#include <iostream> -#include <sstream> -#include <random> - -AUTOHOOK_INIT() - -static const char* pszAudioEventName; - -ConVar* Cvar_mileslog_enable; -ConVar* Cvar_ns_print_played_sounds; - -CustomAudioManager g_CustomAudioManager; - -EventOverrideData::EventOverrideData() -{ - spdlog::warn("Initialised struct EventOverrideData without any data!"); - LoadedSuccessfully = false; -} - -// Empty stereo 48000 WAVE file -unsigned char EMPTY_WAVE[45] = {0x52, 0x49, 0x46, 0x46, 0x25, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, - 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x88, 0x58, - 0x01, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00}; - -EventOverrideData::EventOverrideData(const std::string& data, const fs::path& path) -{ - if (data.length() <= 0) - { - spdlog::error("Failed reading audio override file {}: file is empty", path.string()); - return; - } - - fs::path samplesFolder = path; - samplesFolder = samplesFolder.replace_extension(); - - if (!fs::exists(samplesFolder)) - { - spdlog::error( - "Failed reading audio override file {}: samples folder doesn't exist; should be named the same as the definition file without " - "JSON extension.", - path.string()); - return; - } - - rapidjson_document dataJson; - dataJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(data); - - // fail if parse error - if (dataJson.HasParseError()) - { - spdlog::error( - "Failed reading audio override file {}: encountered parse error \"{}\" at offset {}", - path.string(), - GetParseError_En(dataJson.GetParseError()), - dataJson.GetErrorOffset()); - return; - } - - // fail if it's not a json obj (could be an array, string, etc) - if (!dataJson.IsObject()) - { - spdlog::error("Failed reading audio override file {}: file is not a JSON object", path.string()); - return; - } - - // fail if no event ids given - if (!dataJson.HasMember("EventId")) - { - spdlog::error("Failed reading audio override file {}: JSON object does not have the EventId property", path.string()); - return; - } - - // array of event ids - if (dataJson["EventId"].IsArray()) - { - for (auto& eventId : dataJson["EventId"].GetArray()) - { - if (!eventId.IsString()) - { - spdlog::error( - "Failed reading audio override file {}: EventId array has a value of invalid type, all must be strings", path.string()); - return; - } - - EventIds.push_back(eventId.GetString()); - } - } - // singular event id - else if (dataJson["EventId"].IsString()) - { - EventIds.push_back(dataJson["EventId"].GetString()); - } - // incorrect type - else - { - spdlog::error( - "Failed reading audio override file {}: EventId property is of invalid type (must be a string or an array of strings)", - path.string()); - return; - } - - if (dataJson.HasMember("EventIdRegex")) - { - // array of event id regex - if (dataJson["EventIdRegex"].IsArray()) - { - for (auto& eventId : dataJson["EventIdRegex"].GetArray()) - { - if (!eventId.IsString()) - { - spdlog::error( - "Failed reading audio override file {}: EventIdRegex array has a value of invalid type, all must be strings", - path.string()); - return; - } - - const std::string& regex = eventId.GetString(); - - try - { - EventIdsRegex.push_back({regex, std::regex(regex)}); - } - catch (...) - { - spdlog::error("Malformed regex \"{}\" in audio override file {}", regex, path.string()); - return; - } - } - } - // singular event id regex - else if (dataJson["EventIdRegex"].IsString()) - { - const std::string& regex = dataJson["EventIdRegex"].GetString(); - try - { - EventIdsRegex.push_back({regex, std::regex(regex)}); - } - catch (...) - { - spdlog::error("Malformed regex \"{}\" in audio override file {}", regex, path.string()); - return; - } - } - // incorrect type - else - { - spdlog::error( - "Failed reading audio override file {}: EventIdRegex property is of invalid type (must be a string or an array of strings)", - path.string()); - return; - } - } - - if (dataJson.HasMember("AudioSelectionStrategy")) - { - if (!dataJson["AudioSelectionStrategy"].IsString()) - { - spdlog::error("Failed reading audio override file {}: AudioSelectionStrategy property must be a string", path.string()); - return; - } - - std::string strategy = dataJson["AudioSelectionStrategy"].GetString(); - - if (strategy == "sequential") - { - Strategy = AudioSelectionStrategy::SEQUENTIAL; - } - else if (strategy == "random") - { - Strategy = AudioSelectionStrategy::RANDOM; - } - else - { - spdlog::error( - "Failed reading audio override file {}: AudioSelectionStrategy string must be either \"sequential\" or \"random\"", - path.string()); - return; - } - } - - // load samples - for (fs::directory_entry file : fs::recursive_directory_iterator(samplesFolder)) - { - if (file.is_regular_file() && file.path().extension().string() == ".wav") - { - std::string pathString = file.path().string(); - - // Open the file. - std::ifstream wavStream(pathString, std::ios::binary); - - if (wavStream.fail()) - { - spdlog::error("Failed reading audio sample {}", file.path().string()); - continue; - } - - // Get file size. - wavStream.seekg(0, std::ios::end); - size_t fileSize = wavStream.tellg(); - wavStream.close(); - - // Allocate enough memory for the file. - // blank out the memory for now, then read it later - uint8_t* data = new uint8_t[fileSize]; - memcpy(data, EMPTY_WAVE, sizeof(EMPTY_WAVE)); - Samples.push_back({fileSize, std::unique_ptr<uint8_t[]>(data)}); - - // thread off the file read - // should we spawn one thread per read? or should there be a cap to the number of reads at once? - std::thread readThread( - [pathString, fileSize, data] - { - std::shared_lock lock(g_CustomAudioManager.m_loadingMutex); - std::ifstream wavStream(pathString, std::ios::binary); - - // would be weird if this got hit, since it would've worked previously - if (wavStream.fail()) - { - spdlog::error("Failed async read of audio sample {}", pathString); - return; - } - - // read from after the header first to preserve the empty header, then read the header last - wavStream.seekg(0, std::ios::beg); - wavStream.read(reinterpret_cast<char*>(data), fileSize); - wavStream.close(); - - spdlog::info("Finished async read of audio sample {}", pathString); - }); - - readThread.detach(); - } - } - - /* - if (dataJson.HasMember("EnableOnLoopedSounds")) - { - if (!dataJson["EnableOnLoopedSounds"].IsBool()) - { - spdlog::error("Failed reading audio override file {}: EnableOnLoopedSounds property is of invalid type (must be a bool)", - path.string()); return; - } - - EnableOnLoopedSounds = dataJson["EnableOnLoopedSounds"].GetBool(); - } - */ - - if (Samples.size() == 0) - spdlog::warn("Audio override {} has no valid samples! Sounds will not play for this event.", path.string()); - - spdlog::info("Loaded audio override file {}", path.string()); - - LoadedSuccessfully = true; -} - -bool CustomAudioManager::TryLoadAudioOverride(const fs::path& defPath) -{ - if (IsDedicatedServer()) - return true; // silently fail - - std::ifstream jsonStream(defPath); - std::stringstream jsonStringStream; - - // fail if no audio json - if (jsonStream.fail()) - { - spdlog::warn("Unable to read audio override from file {}", defPath.string()); - return false; - } - - while (jsonStream.peek() != EOF) - jsonStringStream << (char)jsonStream.get(); - - jsonStream.close(); - - std::shared_ptr<EventOverrideData> data = std::make_shared<EventOverrideData>(jsonStringStream.str(), defPath); - - if (!data->LoadedSuccessfully) - return false; // no logging, the constructor has probably already logged - - for (const std::string& eventId : data->EventIds) - { - spdlog::info("Registering sound event {}", eventId); - m_loadedAudioOverrides.insert({eventId, data}); - } - - for (const auto& eventIdRegexData : data->EventIdsRegex) - { - spdlog::info("Registering sound event regex {}", eventIdRegexData.first); - m_loadedAudioOverridesRegex.insert({eventIdRegexData.first, data}); - } - - return true; -} - -typedef void (*MilesStopAll_Type)(); -MilesStopAll_Type MilesStopAll; - -void CustomAudioManager::ClearAudioOverrides() -{ - if (IsDedicatedServer()) - return; - - if (m_loadedAudioOverrides.size() > 0 || m_loadedAudioOverridesRegex.size() > 0) - { - // stop all miles sounds beforehand - // miles_stop_all - - MilesStopAll(); - - // this is cancer but it works - Sleep(50); - } - - // slightly (very) bad - // wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them - std::unique_lock lock(m_loadingMutex); - - m_loadedAudioOverrides.clear(); - m_loadedAudioOverridesRegex.clear(); -} - -template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g) -{ - std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1); - std::advance(start, dis(g)); - return start; -} - -template <typename Iter> Iter select_randomly(Iter start, Iter end) -{ - static std::random_device rd; - static std::mt19937 gen(rd()); - return select_randomly(start, end, gen); -} - -bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptr<EventOverrideData>& data) -{ - std::string eventNameString = eventName; - std::string eventNameStringBlacklistEntry = ("!" + eventNameString); - - for (const std::string& name : data->EventIds) - { - if (name == eventNameStringBlacklistEntry) - return false; // event blacklisted - - if (name == "*") - { - // check for bad sounds I guess? - // really feel like this should be an option but whatever - if (!!strstr(eventName, "_amb_") || !!strstr(eventName, "_emit_") || !!strstr(eventName, "amb_")) - return false; // would play static noise, I hate this - } - } - - return true; // good to go -} - -// clang-format off -AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110, -bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType)) -// clang-format on -{ - // Raw source, used for voice data only - if (audioType == 0) - return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); - - const char* eventName = pszAudioEventName; - - if (Cvar_ns_print_played_sounds->GetInt() > 0) - spdlog::info("[AUDIO] Playing event {}", eventName); - - auto iter = g_CustomAudioManager.m_loadedAudioOverrides.find(eventName); - std::shared_ptr<EventOverrideData> overrideData; - - if (iter == g_CustomAudioManager.m_loadedAudioOverrides.end()) - { - // override for that specific event not found, try wildcard - iter = g_CustomAudioManager.m_loadedAudioOverrides.find("*"); - - if (iter == g_CustomAudioManager.m_loadedAudioOverrides.end()) - { - // not found - - // try regex - for (const auto& item : g_CustomAudioManager.m_loadedAudioOverridesRegex) - for (const auto& regexData : item.second->EventIdsRegex) - if (std::regex_search(eventName, regexData.second)) - overrideData = item.second; - - if (!overrideData) - // not found either - return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); - else - { - // cache found pattern to improve performance - g_CustomAudioManager.m_loadedAudioOverrides[eventName] = overrideData; - } - } - else - overrideData = iter->second; - } - else - overrideData = iter->second; - - if (!ShouldPlayAudioEvent(eventName, overrideData)) - return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); - - void* data = 0; - unsigned int dataLength = 0; - - if (overrideData->Samples.size() == 0) - { - // 0 samples, turn off this particular event. - - // using a dummy empty wave file - data = EMPTY_WAVE; - dataLength = sizeof(EMPTY_WAVE); - } - else - { - std::pair<size_t, std::unique_ptr<uint8_t[]>>* dat = NULL; - - switch (overrideData->Strategy) - { - case AudioSelectionStrategy::RANDOM: - dat = &*select_randomly(overrideData->Samples.begin(), overrideData->Samples.end()); - break; - case AudioSelectionStrategy::SEQUENTIAL: - default: - dat = &overrideData->Samples[overrideData->CurrentIndex++]; - if (overrideData->CurrentIndex >= overrideData->Samples.size()) - overrideData->CurrentIndex = 0; // reset back to the first sample entry - break; - } - - if (!dat) - spdlog::warn("Could not get sample data from override struct for event {}! Shouldn't happen", eventName); - else - { - data = dat->second.get(); - dataLength = dat->first; - } - } - - if (!data) - { - spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName); - return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); - } - - audioBuffer = data; - audioBufferLength = dataLength; - - // most important change: set the sample class buffer so that the correct audio plays - *(void**)((uintptr_t)sample + 0xE8) = audioBuffer; - *(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength; - - // 64 - Auto-detect sample type - bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64); - if (!res) - spdlog::error("LoadSampleMetadata failed! The game will crash :("); - - return res; -} - -// clang-format off -AUTOHOOK(sub_1800294C0, mileswin64.dll + 0x294C0, -void*, __fastcall, (void* a1, void* a2)) -// clang-format on -{ - pszAudioEventName = reinterpret_cast<const char*>((*((__int64*)a2 + 6))); - return sub_1800294C0(a1, a2); -} - -// clang-format off -AUTOHOOK(MilesLog, client.dll + 0x57DAD0, -void, __fastcall, (int level, const char* string)) -// clang-format on -{ - if (!Cvar_mileslog_enable->GetBool()) - return; - - spdlog::info("[MSS] {} - {}", level, string); -} - -ON_DLL_LOAD_RELIESON("engine.dll", MilesLogFuncHooks, ConVar, (CModule module)) -{ - Cvar_mileslog_enable = new ConVar("mileslog_enable", "0", FCVAR_NONE, "Enables/disables whether the mileslog func should be logged"); -} - -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, ""); - MilesStopAll = module.Offset(0x580850).RCast<MilesStopAll_Type>(); -} diff --git a/NorthstarDLL/client/audio.h b/NorthstarDLL/client/audio.h deleted file mode 100644 index 15fd1a35..00000000 --- a/NorthstarDLL/client/audio.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include <vector> -#include <filesystem> -#include <regex> -#include <shared_mutex> - -enum class AudioSelectionStrategy -{ - INVALID = -1, - SEQUENTIAL, - RANDOM -}; - -class EventOverrideData -{ -public: - EventOverrideData(const std::string&, const fs::path&); - EventOverrideData(); - -public: - bool LoadedSuccessfully = false; - - std::vector<std::string> EventIds = {}; - std::vector<std::pair<std::string, std::regex>> EventIdsRegex = {}; - - std::vector<std::pair<size_t, std::unique_ptr<uint8_t[]>>> Samples = {}; - - AudioSelectionStrategy Strategy = AudioSelectionStrategy::SEQUENTIAL; - size_t CurrentIndex = 0; - - bool EnableOnLoopedSounds = false; -}; - -class CustomAudioManager -{ -public: - bool TryLoadAudioOverride(const fs::path&); - void ClearAudioOverrides(); - - std::shared_mutex m_loadingMutex; - std::unordered_map<std::string, std::shared_ptr<EventOverrideData>> m_loadedAudioOverrides = {}; - std::unordered_map<std::string, std::shared_ptr<EventOverrideData>> m_loadedAudioOverridesRegex = {}; -}; - -extern CustomAudioManager g_CustomAudioManager; diff --git a/NorthstarDLL/client/chatcommand.cpp b/NorthstarDLL/client/chatcommand.cpp deleted file mode 100644 index 9cf34e43..00000000 --- a/NorthstarDLL/client/chatcommand.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "core/convar/convar.h" -#include "core/convar/concommand.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 -void(__fastcall* ClientSayText)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat); - -void ConCommand_say(const CCommand& args) -{ - if (args.ArgC() >= 2) - ClientSayText(nullptr, args.ArgS(), true, false); -} - -void ConCommand_say_team(const CCommand& args) -{ - if (args.ArgC() >= 2) - ClientSayText(nullptr, args.ArgS(), true, true); -} - -void ConCommand_log(const CCommand& args) -{ - if (args.ArgC() >= 2) - { - LocalChatWriter(LocalChatWriter::GameContext).WriteLine(args.ArgS()); - } -} - -ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientChatCommand, ConCommand, (CModule module)) -{ - ClientSayText = - module.Offset(0x54780).RCast<void(__fastcall*)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat)>(); - 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); - RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL); -} diff --git a/NorthstarDLL/client/clientauthhooks.cpp b/NorthstarDLL/client/clientauthhooks.cpp deleted file mode 100644 index 35ae3aa7..00000000 --- a/NorthstarDLL/client/clientauthhooks.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "masterserver/masterserver.h" -#include "core/convar/convar.h" -#include "client/r2client.h" -#include "core/vanilla.h" - -AUTOHOOK_INIT() - -ConVar* Cvar_ns_has_agreed_to_send_token; - -// mirrored in script -const int NOT_DECIDED_TO_SEND_TOKEN = 0; -const int AGREED_TO_SEND_TOKEN = 1; -const int DISAGREED_TO_SEND_TOKEN = 2; - -// clang-format off -AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0, -void, __fastcall, (void* a1)) -// clang-format on -{ - // don't attempt to do Atlas auth if we are in vanilla compatibility mode - // this prevents users from joining untrustworthy servers (unless they use a concommand or something) - if (g_pVanillaCompatibility->GetVanillaCompatibility()) - { - AuthWithStryder(a1); - return; - } - - // game will call this forever, until it gets a valid auth key - // so, we need to manually invalidate our key until we're authed with northstar, then we'll allow game to auth with stryder - if (!g_pMasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN) - { - // if player has agreed to send token and we aren't already authing, try to auth - if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN && - !g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress) - g_pMasterServerManager->AuthenticateOriginWithMasterServer(g_pLocalPlayerUserID, g_pLocalPlayerOriginToken); - - // invalidate key so auth will fail - *g_pLocalPlayerOriginToken = 0; - } - - AuthWithStryder(a1); -} - -char* p3PToken; - -// clang-format off -AUTOHOOK(Auth3PToken, engine.dll + 0x183760, -char*, __fastcall, ()) -// clang-format on -{ - if (!g_pVanillaCompatibility->GetVanillaCompatibility() && g_pMasterServerManager->m_sOwnClientAuthToken[0]) - { - memset(p3PToken, 0x0, 1024); - strcpy(p3PToken, "Protocol 3: Protect the Pilot"); - } - - return Auth3PToken(); -} - -ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientAuthHooks, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - p3PToken = module.Offset(0x13979D80).RCast<char*>(); - - // this cvar will save to cfg once initially agreed with - Cvar_ns_has_agreed_to_send_token = new ConVar( - "ns_has_agreed_to_send_token", - "0", - FCVAR_ARCHIVE_PLAYERPROFILE, - "whether the user has agreed to send their origin token to the northstar masterserver"); -} diff --git a/NorthstarDLL/client/clientruihooks.cpp b/NorthstarDLL/client/clientruihooks.cpp deleted file mode 100644 index ad50d11a..00000000 --- a/NorthstarDLL/client/clientruihooks.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "core/convar/convar.h" - -AUTOHOOK_INIT() - -ConVar* Cvar_rui_drawEnable; - -// clang-format off -AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500, -bool, __fastcall, (void* a1, float* a2)) -// clang-format on -{ - if (!Cvar_rui_drawEnable->GetBool()) - return 0; - - return DrawRUIFunc(a1, a2); -} - -ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", RUI, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn"); -} diff --git a/NorthstarDLL/client/clientvideooverrides.cpp b/NorthstarDLL/client/clientvideooverrides.cpp deleted file mode 100644 index d8aa2754..00000000 --- a/NorthstarDLL/client/clientvideooverrides.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "mods/modmanager.h" - -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK_PROCADDRESS(BinkOpen, bink2w64.dll, BinkOpen, -void*, __fastcall, (const char* path, uint32_t flags)) -// clang-format on -{ - std::string filename(fs::path(path).filename().string()); - spdlog::info("BinkOpen {}", filename); - - // figure out which mod is handling the bink - Mod* fileOwner = nullptr; - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end()) - fileOwner = &mod; - } - - if (fileOwner) - { - // create new path - fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename); - return BinkOpen(binkPath.string().c_str(), flags); - } - else - return BinkOpen(path, flags); -} - -ON_DLL_LOAD_CLIENT("engine.dll", BinkVideo, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - // remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods - // note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it - module.Offset(0x459AD).NOP(6); -} diff --git a/NorthstarDLL/client/debugoverlay.cpp b/NorthstarDLL/client/debugoverlay.cpp deleted file mode 100644 index e231054d..00000000 --- a/NorthstarDLL/client/debugoverlay.cpp +++ /dev/null @@ -1,348 +0,0 @@ -#include "dedicated/dedicated.h" -#include "core/convar/cvar.h" -#include "core/math/vector.h" - -AUTOHOOK_INIT() - -enum OverlayType_t -{ - OVERLAY_BOX = 0, - OVERLAY_SPHERE, - OVERLAY_LINE, - OVERLAY_SMARTAMMO, - OVERLAY_TRIANGLE, - OVERLAY_SWEPT_BOX, - // [Fifty]: the 2 bellow i did not confirm, rest are good - OVERLAY_BOX2, - OVERLAY_CAPSULE -}; - -struct OverlayBase_t -{ - OverlayBase_t() - { - m_Type = OVERLAY_BOX; - m_nServerCount = -1; - m_nCreationTick = -1; - m_flEndTime = 0.0f; - m_pNextOverlay = NULL; - } - - OverlayType_t m_Type; // What type of overlay is it? - int m_nCreationTick; // Duration -1 means go away after this frame # - int m_nServerCount; // Latch server count, too - float m_flEndTime; // When does this box go away - OverlayBase_t* m_pNextOverlay; - __int64 m_pUnk; -}; - -struct OverlayLine_t : public OverlayBase_t -{ - OverlayLine_t() - { - m_Type = OVERLAY_LINE; - } - - Vector3 origin; - Vector3 dest; - int r; - int g; - int b; - int a; - bool noDepthTest; -}; - -struct OverlayBox_t : public OverlayBase_t -{ - OverlayBox_t() - { - m_Type = OVERLAY_BOX; - } - - Vector3 origin; - Vector3 mins; - Vector3 maxs; - QAngle angles; - int r; - int g; - int b; - int a; -}; - -struct OverlayTriangle_t : public OverlayBase_t -{ - OverlayTriangle_t() - { - m_Type = OVERLAY_TRIANGLE; - } - - Vector3 p1; - Vector3 p2; - Vector3 p3; - int r; - int g; - int b; - int a; - bool noDepthTest; -}; - -struct OverlaySweptBox_t : public OverlayBase_t -{ - OverlaySweptBox_t() - { - m_Type = OVERLAY_SWEPT_BOX; - } - - Vector3 start; - Vector3 end; - Vector3 mins; - Vector3 maxs; - QAngle angles; - int r; - int g; - int b; - int a; -}; - -struct OverlaySphere_t : public OverlayBase_t -{ - OverlaySphere_t() - { - m_Type = OVERLAY_SPHERE; - } - - Vector3 vOrigin; - float flRadius; - int nTheta; - int nPhi; - int r; - int g; - int b; - int a; - bool m_bWireframe; -}; - -typedef bool (*OverlayBase_t__IsDeadType)(OverlayBase_t* a1); -static OverlayBase_t__IsDeadType OverlayBase_t__IsDead; -typedef void (*OverlayBase_t__DestroyOverlayType)(OverlayBase_t* a1); -static OverlayBase_t__DestroyOverlayType OverlayBase_t__DestroyOverlay; - -static ConVar* Cvar_enable_debug_overlays; - -LPCRITICAL_SECTION s_OverlayMutex; - -// Render Line -typedef void (*RenderLineType)(const Vector3& v1, const Vector3& v2, Color c, bool bZBuffer); -static RenderLineType RenderLine; - -// Render box -typedef void (*RenderBoxType)( - const Vector3& vOrigin, const QAngle& angles, const Vector3& vMins, const Vector3& vMaxs, Color c, bool bZBuffer, bool bInsideOut); -static RenderBoxType RenderBox; - -// Render wireframe box -static RenderBoxType RenderWireframeBox; - -// Render swept box -typedef void (*RenderWireframeSweptBoxType)( - const Vector3& vStart, const Vector3& vEnd, const QAngle& angles, const Vector3& vMins, const Vector3& vMaxs, Color c, bool bZBuffer); -RenderWireframeSweptBoxType RenderWireframeSweptBox; - -// Render Triangle -typedef void (*RenderTriangleType)(const Vector3& p1, const Vector3& p2, const Vector3& p3, Color c, bool bZBuffer); -static RenderTriangleType RenderTriangle; - -// Render Axis -typedef void (*RenderAxisType)(const Vector3& vOrigin, float flScale, bool bZBuffer); -static RenderAxisType RenderAxis; - -// I dont know -typedef void (*RenderUnknownType)(const Vector3& vUnk, float flUnk, bool bUnk); -static RenderUnknownType RenderUnknown; - -// Render Sphere -typedef void (*RenderSphereType)(const Vector3& vCenter, float flRadius, int nTheta, int nPhi, Color c, bool bZBuffer); -static RenderSphereType RenderSphere; - -OverlayBase_t** s_pOverlays; - -int* g_nRenderTickCount; -int* g_nOverlayTickCount; - -// clang-format off -AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0, -void, __fastcall, (OverlayBase_t * pOverlay)) -// clang-format on -{ - EnterCriticalSection(s_OverlayMutex); - - switch (pOverlay->m_Type) - { - case OVERLAY_SMARTAMMO: - case OVERLAY_LINE: - { - OverlayLine_t* pLine = static_cast<OverlayLine_t*>(pOverlay); - RenderLine(pLine->origin, pLine->dest, Color(pLine->r, pLine->g, pLine->b, pLine->a), pLine->noDepthTest); - } - break; - case OVERLAY_BOX: - { - OverlayBox_t* pCurrBox = static_cast<OverlayBox_t*>(pOverlay); - if (pCurrBox->a > 0) - { - RenderBox( - pCurrBox->origin, - pCurrBox->angles, - pCurrBox->mins, - pCurrBox->maxs, - Color(pCurrBox->r, pCurrBox->g, pCurrBox->b, pCurrBox->a), - false, - false); - } - if (pCurrBox->a < 255) - { - RenderWireframeBox( - pCurrBox->origin, - pCurrBox->angles, - pCurrBox->mins, - pCurrBox->maxs, - Color(pCurrBox->r, pCurrBox->g, pCurrBox->b, 255), - false, - false); - } - } - break; - case OVERLAY_TRIANGLE: - { - OverlayTriangle_t* pTriangle = static_cast<OverlayTriangle_t*>(pOverlay); - RenderTriangle( - pTriangle->p1, - pTriangle->p2, - pTriangle->p3, - Color(pTriangle->r, pTriangle->g, pTriangle->b, pTriangle->a), - pTriangle->noDepthTest); - } - break; - case OVERLAY_SWEPT_BOX: - { - OverlaySweptBox_t* pBox = static_cast<OverlaySweptBox_t*>(pOverlay); - RenderWireframeSweptBox( - pBox->start, pBox->end, pBox->angles, pBox->mins, pBox->maxs, Color(pBox->r, pBox->g, pBox->b, pBox->a), false); - } - break; - case OVERLAY_SPHERE: - { - OverlaySphere_t* pSphere = static_cast<OverlaySphere_t*>(pOverlay); - RenderSphere( - pSphere->vOrigin, - pSphere->flRadius, - pSphere->nTheta, - pSphere->nPhi, - Color(pSphere->r, pSphere->g, pSphere->b, pSphere->a), - false); - } - break; - default: - { - spdlog::warn("Unimplemented overlay type {}", pOverlay->m_Type); - } - break; - } - - LeaveCriticalSection(s_OverlayMutex); -} - -// clang-format off -AUTOHOOK(DrawAllOverlays, engine.dll + 0xAB780, -void, __fastcall, (bool bRender)) -// clang-format on -{ - EnterCriticalSection(s_OverlayMutex); - - OverlayBase_t* pCurrOverlay = *s_pOverlays; // rbx - OverlayBase_t* pPrevOverlay = nullptr; // rsi - OverlayBase_t* pNextOverlay = nullptr; // rdi - - int m_nCreationTick; // eax - bool bShouldDraw; // zf - int m_pUnk; // eax - - while (pCurrOverlay) - { - if (OverlayBase_t__IsDead(pCurrOverlay)) - { - if (pPrevOverlay) - { - pPrevOverlay->m_pNextOverlay = pCurrOverlay->m_pNextOverlay; - } - else - { - *s_pOverlays = pCurrOverlay->m_pNextOverlay; - } - - pNextOverlay = pCurrOverlay->m_pNextOverlay; - OverlayBase_t__DestroyOverlay(pCurrOverlay); - pCurrOverlay = pNextOverlay; - } - else - { - if (pCurrOverlay->m_nCreationTick == -1) - { - m_pUnk = pCurrOverlay->m_pUnk; - - if (m_pUnk == -1) - { - bShouldDraw = true; - } - else - { - bShouldDraw = m_pUnk == *g_nOverlayTickCount; - } - } - else - { - bShouldDraw = pCurrOverlay->m_nCreationTick == *g_nRenderTickCount; - } - - if (bShouldDraw && bRender && (Cvar_enable_debug_overlays->GetBool() || pCurrOverlay->m_Type == OVERLAY_SMARTAMMO)) - { - DrawOverlay(pCurrOverlay); - } - - pPrevOverlay = pCurrOverlay; - pCurrOverlay = pCurrOverlay->m_pNextOverlay; - } - } - - LeaveCriticalSection(s_OverlayMutex); -} - -ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - OverlayBase_t__IsDead = module.Offset(0xACAC0).RCast<OverlayBase_t__IsDeadType>(); - OverlayBase_t__DestroyOverlay = module.Offset(0xAB680).RCast<OverlayBase_t__DestroyOverlayType>(); - - RenderLine = module.Offset(0x192A70).RCast<RenderLineType>(); - RenderBox = module.Offset(0x192520).RCast<RenderBoxType>(); - RenderWireframeBox = module.Offset(0x193DA0).RCast<RenderBoxType>(); - RenderWireframeSweptBox = module.Offset(0x1945A0).RCast<RenderWireframeSweptBoxType>(); - RenderTriangle = module.Offset(0x193940).RCast<RenderTriangleType>(); - RenderAxis = module.Offset(0x1924D0).RCast<RenderAxisType>(); - RenderSphere = module.Offset(0x194170).RCast<RenderSphereType>(); - RenderUnknown = module.Offset(0x1924E0).RCast<RenderUnknownType>(); - - s_OverlayMutex = module.Offset(0x10DB0A38).RCast<LPCRITICAL_SECTION>(); - - s_pOverlays = module.Offset(0x10DB0968).RCast<OverlayBase_t**>(); - - g_nRenderTickCount = module.Offset(0x10DB0984).RCast<int*>(); - g_nOverlayTickCount = module.Offset(0x10DB0980).RCast<int*>(); - - // not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory - Cvar_enable_debug_overlays = module.Offset(0x10DB0990).RCast<ConVar*>(); - Cvar_enable_debug_overlays->SetValue(false); - Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0"; - Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT); -} diff --git a/NorthstarDLL/client/demofixes.cpp b/NorthstarDLL/client/demofixes.cpp deleted file mode 100644 index 344764ba..00000000 --- a/NorthstarDLL/client/demofixes.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "core/convar/convar.h" - -ON_DLL_LOAD_CLIENT("engine.dll", EngineDemoFixes, (CModule module)) -{ - // allow demo recording on loopback - module.Offset(0x8E1B1).NOP(2); - module.Offset(0x56CC3).NOP(2); -} - -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientDemoFixes, ConVar, (CModule module)) -{ - // change default values of demo cvars to enable them by default, but not autorecord - // this is before Host_Init, the setvalue calls here will get overwritten by custom cfgs/launch options - ConVar* Cvar_demo_enableDemos = g_pCVar->FindVar("demo_enabledemos"); - Cvar_demo_enableDemos->m_pszDefaultValue = "1"; - Cvar_demo_enableDemos->SetValue(true); - - ConVar* Cvar_demo_writeLocalFile = g_pCVar->FindVar("demo_writeLocalFile"); - Cvar_demo_writeLocalFile->m_pszDefaultValue = "1"; - Cvar_demo_writeLocalFile->SetValue(true); - - ConVar* Cvar_demo_autoRecord = g_pCVar->FindVar("demo_autoRecord"); - Cvar_demo_autoRecord->m_pszDefaultValue = "0"; - Cvar_demo_autoRecord->SetValue(false); -} diff --git a/NorthstarDLL/client/diskvmtfixes.cpp b/NorthstarDLL/client/diskvmtfixes.cpp deleted file mode 100644 index 4ab951c0..00000000 --- a/NorthstarDLL/client/diskvmtfixes.cpp +++ /dev/null @@ -1,15 +0,0 @@ - -ON_DLL_LOAD_CLIENT("materialsystem_dx11.dll", DiskVMTFixes, (CModule module)) -{ - // in retail VMTs will never load if cache read is invalid due to a special case for them in KeyValues::LoadFromFile - // this effectively makes it impossible to load them from mods because we invalidate cache for doing this - // so uhh, stop that from happening - - // tbh idk why they even changed any of this what's the point it looks like it works fine who cares my god - - // matsystem KeyValues::LoadFromFile: patch special case on cache read failure for vmts - module.Offset(0x1281B9).Patch("EB"); - - // CMaterialSystem::FindMaterial: don't call function that crashes if previous patch is applied - module.Offset(0x5F55A).NOP(5); -} diff --git a/NorthstarDLL/client/languagehooks.cpp b/NorthstarDLL/client/languagehooks.cpp deleted file mode 100644 index 35ca5659..00000000 --- a/NorthstarDLL/client/languagehooks.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "core/tier0.h" - -#include <filesystem> -#include <regex> - -AUTOHOOK_INIT() - -typedef LANGID (*Tier0_DetectDefaultLanguageType)(); - -bool CheckLangAudioExists(char* lang) -{ - std::string path {"r2\\sound\\general_"}; - path += lang; - path += ".mstr"; - return fs::exists(path); -} - -std::vector<std::string> file_list(fs::path dir, std::regex ext_pattern) -{ - std::vector<std::string> result; - - if (!fs::exists(dir) || !fs::is_directory(dir)) - return result; - - using iterator = fs::directory_iterator; - - const iterator end; - for (iterator iter {dir}; iter != end; ++iter) - { - const std::string filename = iter->path().filename().string(); - std::smatch matches; - if (fs::is_regular_file(*iter) && std::regex_match(filename, matches, ext_pattern)) - { - result.push_back(std::move(matches.str(1))); - } - } - - return result; -} - -std::string GetAnyInstalledAudioLanguage() -{ - for (const auto& lang : file_list("r2\\sound\\", std::regex(".*?general_([a-z]+)_patch_1\\.mstr"))) - if (lang != "general" || lang != "") - return lang; - return "NO LANGUAGE DETECTED"; -} - -// clang-format off -AUTOHOOK(GetGameLanguage, tier0.dll + 0xF560, -char*, __fastcall, ()) -// clang-format on -{ - auto tier0Handle = GetModuleHandleA("tier0.dll"); - auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage"); - char* ingameLang1 = (char*)tier0Handle + 0xA9B60; // one of the globals we need to override if overriding lang (size: 256) - bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90); - - const char* forcedLanguage; - if (CommandLine()->CheckParm("-language", &forcedLanguage)) - { - if (!CheckLangAudioExists((char*)forcedLanguage)) - { - spdlog::info( - "User tried to force the language (-language) to \"{}\", but audio for this language doesn't exist and the game is bound " - "to error, falling back to next option...", - forcedLanguage); - } - else - { - spdlog::info("User forcing the language (-language) to: {}", forcedLanguage); - strncpy(ingameLang1, forcedLanguage, 256); - return ingameLang1; - } - } - - canOriginDictateLang = true; // let it try - { - auto lang = GetGameLanguage(); - if (!CheckLangAudioExists(lang)) - { - if (strcmp(lang, "russian") != - 0) // don't log for "russian" since it's the default and that means Origin detection just didn't change it most likely - spdlog::info( - "Origin detected language \"{}\", but we do not have audio for it installed, falling back to the next option", lang); - } - else - { - spdlog::info("Origin detected language: {}", lang); - return lang; - } - } - - Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than - // defaulting to Russian - canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language - auto lang = GetGameLanguage(); - spdlog::info("Detected system language: {}", lang); - if (!CheckLangAudioExists(lang)) - { - spdlog::warn("Caution, audio for this language does NOT exist. You might want to override your game language with -language " - "command line option."); - auto lang = GetAnyInstalledAudioLanguage(); - spdlog::warn("Falling back to the first installed audio language: {}", lang.c_str()); - strncpy(ingameLang1, lang.c_str(), 256); - return ingameLang1; - } - - return lang; -} - -ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/client/latencyflex.cpp b/NorthstarDLL/client/latencyflex.cpp deleted file mode 100644 index 25e38c7a..00000000 --- a/NorthstarDLL/client/latencyflex.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "core/convar/convar.h" - -AUTOHOOK_INIT() - -ConVar* Cvar_r_latencyflex; - -void (*m_winelfx_WaitAndBeginFrame)(); - -// clang-format off -AUTOHOOK(OnRenderStart, client.dll + 0x1952C0, -void, __fastcall, ()) -// clang-format on -{ - if (Cvar_r_latencyflex->GetBool() && m_winelfx_WaitAndBeginFrame) - m_winelfx_WaitAndBeginFrame(); - - OnRenderStart(); -} - -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module)) -{ - // Connect to the LatencyFleX service - // LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology. - // https://ishitatsuyuki.github.io/post/latencyflex/ - HMODULE pLfxModule; - - if (pLfxModule = LoadLibraryA("latencyflex_layer.dll")) - m_winelfx_WaitAndBeginFrame = - reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame"))); - else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll")) - m_winelfx_WaitAndBeginFrame = - reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame"))); - else - { - spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled."); - return; - } - - AUTOHOOK_DISPATCH() - - spdlog::info("LatencyFleX initialized."); - Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction."); -} diff --git a/NorthstarDLL/client/localchatwriter.cpp b/NorthstarDLL/client/localchatwriter.cpp deleted file mode 100644 index 35cc065f..00000000 --- a/NorthstarDLL/client/localchatwriter.cpp +++ /dev/null @@ -1,449 +0,0 @@ -#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, 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, Color URLTextColor, 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; -}; - -CGameSettings** gGameSettings; -CGameFloatVar** gChatFadeLength; -CGameFloatVar** gChatFadeSustain; - -CHudChat** CHudChat::allHuds; - -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, -}; - -Color darkColors[8] = { - Color {0, 0, 0, 255}, - Color {205, 49, 49, 255}, - Color {13, 188, 121, 255}, - Color {229, 229, 16, 255}, - Color {36, 114, 200, 255}, - Color {188, 63, 188, 255}, - Color {17, 168, 205, 255}, - Color {229, 229, 229, 255}}; - -Color lightColors[8] = { - Color {102, 102, 102, 255}, - Color {241, 76, 76, 255}, - Color {35, 209, 139, 255}, - Color {245, 245, 67, 255}, - Color {59, 142, 234, 255}, - Color {214, 112, 214, 255}, - Color {41, 184, 219, 255}, - 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; - 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.SetColor(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(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(Color {brightness, brightness, brightness, 255}); - } - - return Next::ControlType; - } - - Next HandleForegroundR(unsigned long val) - { - if (val >= UCHAR_MAX) - return Next::ControlType; - - m_expandedColor[0] = (unsigned char)val; - return Next::ForegroundG; - } - - Next HandleForegroundG(unsigned long val) - { - if (val >= UCHAR_MAX) - return Next::ControlType; - - m_expandedColor[1] = (unsigned char)val; - return Next::ForegroundB; - } - - Next HandleForegroundB(unsigned long val) - { - if (val >= UCHAR_MAX) - return Next::ControlType; - - m_expandedColor[2] = (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_s(writeBuffer, copyChars + 1, str, copyChars); - - 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 = *CHudChat::allHuds; 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) -{ - spdlog::info(str); - - WCHAR messageUnicode[288]; - ConvertANSIToUnicode(str, -1, messageUnicode, 274); - - for (CHudChat* hud = *CHudChat::allHuds; 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 = *CHudChat::allHuds; 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(Color color) -{ - for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) - { - if (hud->m_unknownContext != (int)m_context) - continue; - - hud->m_richText->vtable->InsertColorChange(hud->m_richText, color); - } -} - -static 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 Color(0, 0, 0, 0); -} - -void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor) -{ - for (CHudChat* hud = *CHudChat::allHuds; 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 = *CHudChat::allHuds; hud != NULL; hud = hud->next) - { - if (hud->m_unknownContext != (int)m_context) - continue; - hud->m_richText->vtable->InsertFade(hud->m_richText, fadeSustain, fadeLength); - } -} - -ON_DLL_LOAD_CLIENT("client.dll", LocalChatWriter, (CModule module)) -{ - gGameSettings = module.Offset(0x11BAA48).RCast<CGameSettings**>(); - gChatFadeLength = module.Offset(0x11BAB78).RCast<CGameFloatVar**>(); - gChatFadeSustain = module.Offset(0x11BAC08).RCast<CGameFloatVar**>(); - CHudChat::allHuds = module.Offset(0x11BA9E8).RCast<CHudChat**>(); - - ConvertANSIToUnicode = module.Offset(0x7339A0).RCast<ConvertANSIToUnicodeType>(); -} diff --git a/NorthstarDLL/client/localchatwriter.h b/NorthstarDLL/client/localchatwriter.h deleted file mode 100644 index acf6f87e..00000000 --- a/NorthstarDLL/client/localchatwriter.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once -#include "core/math/color.h" - -class vgui_BaseRichText; - -class CHudChat -{ -public: - static CHudChat** allHuds; - - char unknown1[720]; - - Color m_sameTeamColor; - Color m_enemyTeamColor; - Color m_mainTextColor; - Color m_networkNameColor; - - char unknown2[12]; - - int m_unknownContext; - - char unknown3[8]; - - vgui_BaseRichText* m_richText; - - CHudChat* next; - CHudChat* previous; -}; - -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(Color color); - void InsertSwatchColorChange(SwatchColor color); - -private: - Context m_context; - - const char* ApplyAnsiEscape(const char* escape); - void InsertDefaultFade(); -}; diff --git a/NorthstarDLL/client/modlocalisation.cpp b/NorthstarDLL/client/modlocalisation.cpp deleted file mode 100644 index 2b73876b..00000000 --- a/NorthstarDLL/client/modlocalisation.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "mods/modmanager.h" - -AUTOHOOK_INIT() - -void* g_pVguiLocalize; - -// clang-format off -AUTOHOOK(CLocalize__AddFile, localize.dll + 0x6D80, -bool, __fastcall, (void* pVguiLocalize, const char* path, const char* pathId, bool bIncludeFallbackSearchPaths)) -// clang-format on -{ - // save this for later - g_pVguiLocalize = pVguiLocalize; - - bool ret = CLocalize__AddFile(pVguiLocalize, path, pathId, bIncludeFallbackSearchPaths); - if (ret) - spdlog::info("Loaded localisation file {} successfully", path); - - return true; -} - -// clang-format off -AUTOHOOK(CLocalize__ReloadLocalizationFiles, localize.dll + 0xB830, -void, __fastcall, (void* pVguiLocalize)) -// clang-format on -{ - // load all mod localization manually, so we keep track of all files, not just previously loaded ones - for (Mod mod : g_pModManager->m_LoadedMods) - if (mod.m_bEnabled) - for (std::string& localisationFile : mod.LocalisationFiles) - CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); - - spdlog::info("reloading localization..."); - CLocalize__ReloadLocalizationFiles(pVguiLocalize); -} - -// clang-format off -AUTOHOOK(CEngineVGui__Init, engine.dll + 0x247E10, -void, __fastcall, (void* self)) -// clang-format on -{ - CEngineVGui__Init(self); // this loads r1_english, valve_english, dev_english - - // previously we did this in CLocalize::AddFile, but for some reason it won't properly overwrite localization from - // files loaded previously if done there, very weird but this works so whatever - for (Mod mod : g_pModManager->m_LoadedMods) - if (mod.m_bEnabled) - for (std::string& localisationFile : mod.LocalisationFiles) - CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); -} - -ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/client/r2client.cpp b/NorthstarDLL/client/r2client.cpp deleted file mode 100644 index c8e59d74..00000000 --- a/NorthstarDLL/client/r2client.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "r2client.h" - -char* g_pLocalPlayerUserID; -char* g_pLocalPlayerOriginToken; -GetBaseLocalClientType GetBaseLocalClient; - -ON_DLL_LOAD("engine.dll", R2EngineClient, (CModule module)) -{ - g_pLocalPlayerUserID = module.Offset(0x13F8E688).RCast<char*>(); - g_pLocalPlayerOriginToken = module.Offset(0x13979C80).RCast<char*>(); - - GetBaseLocalClient = module.Offset(0x78200).RCast<GetBaseLocalClientType>(); -} diff --git a/NorthstarDLL/client/r2client.h b/NorthstarDLL/client/r2client.h deleted file mode 100644 index ea263dbc..00000000 --- a/NorthstarDLL/client/r2client.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -extern char* g_pLocalPlayerUserID; -extern char* g_pLocalPlayerOriginToken; - -typedef void* (*GetBaseLocalClientType)(); -extern GetBaseLocalClientType GetBaseLocalClient; diff --git a/NorthstarDLL/client/rejectconnectionfixes.cpp b/NorthstarDLL/client/rejectconnectionfixes.cpp deleted file mode 100644 index 1b326a3c..00000000 --- a/NorthstarDLL/client/rejectconnectionfixes.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "engine/r2engine.h"
-
-AUTOHOOK_INIT()
-
-// this is called from when our connection is rejected, this is the only case we're hooking this for
-// clang-format off
-AUTOHOOK(COM_ExplainDisconnection, engine.dll + 0x1342F0,
-void,, (bool a1, const char* fmt, ...))
-// clang-format on
-{
- va_list va;
- va_start(va, fmt);
- char buf[4096];
- vsnprintf_s(buf, 4096, fmt, va);
- va_end(va);
-
- // slightly hacky comparison, but patching the function that calls this for reject would be worse
- if (!strncmp(fmt, "Connection rejected: ", 21))
- {
- // when COM_ExplainDisconnection is called from engine.dll + 19ff1c for connection rejected, it doesn't
- // call Host_Disconnect, which properly shuts down listen server
- // not doing this gets our client in a pretty weird state so we need to shut it down manually here
-
- // don't call Cbuf_Execute because we don't need this called immediately
- Cbuf_AddText(Cbuf_GetCurrentPlayer(), "disconnect", cmd_source_t::kCommandSrcCode);
- }
-
- return COM_ExplainDisconnection(a1, "%s", buf);
-}
-
-ON_DLL_LOAD_CLIENT("engine.dll", RejectConnectionFixes, (CModule module))
-{
- AUTOHOOK_DISPATCH()
-}
diff --git a/NorthstarDLL/config/profile.cpp b/NorthstarDLL/config/profile.cpp deleted file mode 100644 index d5361efa..00000000 --- a/NorthstarDLL/config/profile.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "config/profile.h" -#include "dedicated/dedicated.h" -#include <string> - -std::string GetNorthstarPrefix() -{ - return NORTHSTAR_FOLDER_PREFIX; -} - -void InitialiseNorthstarPrefix() -{ - char* clachar = strstr(GetCommandLineA(), "-profile="); - if (clachar) - { - std::string cla = std::string(clachar); - if (strncmp(cla.substr(9, 1).c_str(), "\"", 1)) - { - int space = cla.find(" "); - std::string dirname = cla.substr(9, space - 9); - NORTHSTAR_FOLDER_PREFIX = dirname; - } - else - { - std::string quote = "\""; - int quote1 = cla.find(quote); - int quote2 = (cla.substr(quote1 + 1)).find(quote); - std::string dirname = cla.substr(quote1 + 1, quote2); - NORTHSTAR_FOLDER_PREFIX = dirname; - } - } - else - { - NORTHSTAR_FOLDER_PREFIX = "R2Northstar"; - } - - // set the console title to show the current profile - // dont do this on dedi as title contains useful information on dedi and setting title breaks it as well - if (!IsDedicatedServer()) - SetConsoleTitleA((std::string("NorthstarLauncher | ") + NORTHSTAR_FOLDER_PREFIX).c_str()); -} diff --git a/NorthstarDLL/config/profile.h b/NorthstarDLL/config/profile.h deleted file mode 100644 index 9f3087fb..00000000 --- a/NorthstarDLL/config/profile.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include <string> - -static std::string NORTHSTAR_FOLDER_PREFIX; - -void InitialiseNorthstarPrefix(); -std::string GetNorthstarPrefix(); diff --git a/NorthstarDLL/core/convar/concommand.cpp b/NorthstarDLL/core/convar/concommand.cpp deleted file mode 100644 index 41f54c76..00000000 --- a/NorthstarDLL/core/convar/concommand.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "concommand.h" -#include "shared/misccommands.h" -#include "engine/r2engine.h" - -#include "plugins/pluginbackend.h" -#include "plugins/plugin_abi.h" - -#include <iostream> - -//----------------------------------------------------------------------------- -// Purpose: Returns true if this is a command -// Output : bool -//----------------------------------------------------------------------------- -bool ConCommand::IsCommand(void) const -{ - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true if this is a command -// Output : bool -//----------------------------------------------------------------------------- -bool ConCommandBase::IsCommand(void) const -{ - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Has this cvar been registered -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool ConCommandBase::IsRegistered(void) const -{ - return m_bRegistered; -} - -//----------------------------------------------------------------------------- -// Purpose: Test each ConCommand query before execution. -// Input : *pCommandBase - nFlags -// Output : False if execution is permitted, true if not. -//----------------------------------------------------------------------------- -bool ConCommandBase::IsFlagSet(int nFlags) const -{ - return m_nFlags & nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Checks if ConCommand has requested flags. -// Input : nFlags - -// Output : True if ConCommand has nFlags. -//----------------------------------------------------------------------------- -bool ConCommandBase::HasFlags(int nFlags) -{ - return m_nFlags & nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Add's flags to ConCommand. -// Input : nFlags - -//----------------------------------------------------------------------------- -void ConCommandBase::AddFlags(int nFlags) -{ - m_nFlags |= nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Removes flags from ConCommand. -// Input : nFlags - -//----------------------------------------------------------------------------- -void ConCommandBase::RemoveFlags(int nFlags) -{ - m_nFlags &= ~nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns current flags. -// Output : int -//----------------------------------------------------------------------------- -int ConCommandBase::GetFlags(void) const -{ - return m_nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const ConCommandBase -//----------------------------------------------------------------------------- -ConCommandBase* ConCommandBase::GetNext(void) const -{ - return m_pNext; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the ConCommandBase help text. -// Output : const char* -//----------------------------------------------------------------------------- -const char* ConCommandBase::GetHelpText(void) const -{ - return m_pszHelpString; -} - -//----------------------------------------------------------------------------- -// Purpose: Copies string using local new/delete operators -// Input : *szFrom - -// Output : char -//----------------------------------------------------------------------------- -char* ConCommandBase::CopyString(const char* szFrom) const -{ - size_t nLen; - char* szTo; - - nLen = strlen(szFrom); - if (nLen <= 0) - { - szTo = new char[1]; - szTo[0] = 0; - } - else - { - szTo = new char[nLen + 1]; - memmove(szTo, szFrom, nLen + 1); - } - return szTo; -} - -typedef void (*ConCommandConstructorType)( - ConCommand* newCommand, const char* name, FnCommandCallback_t callback, const char* helpString, int flags, void* parent); -ConCommandConstructorType ConCommandConstructor; - -void RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags) -{ - spdlog::info("Registering ConCommand {}", name); - - // no need to free this ever really, it should exist as long as game does - ConCommand* newCommand = new ConCommand; - ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); -} - -void RegisterConCommand( - const char* name, FnCommandCallback_t callback, const char* helpString, int flags, FnCommandCompletionCallback completionCallback) -{ - spdlog::info("Registering ConCommand {}", name); - - // no need to free this ever really, it should exist as long as game does - ConCommand* newCommand = new ConCommand; - ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); - newCommand->m_pCompletionCallback = completionCallback; -} - -ON_DLL_LOAD("engine.dll", ConCommand, (CModule module)) -{ - ConCommandConstructor = module.Offset(0x415F60).RCast<ConCommandConstructorType>(); - AddMiscConCommands(); - - g_pPluginCommunicationhandler->m_sEngineData.ConCommandConstructor = - reinterpret_cast<PluginConCommandConstructorType>(ConCommandConstructor); -} diff --git a/NorthstarDLL/core/convar/concommand.h b/NorthstarDLL/core/convar/concommand.h deleted file mode 100644 index 71a82fec..00000000 --- a/NorthstarDLL/core/convar/concommand.h +++ /dev/null @@ -1,139 +0,0 @@ -#pragma once - -// From Source SDK -class ConCommandBase; -class IConCommandBaseAccessor -{ -public: - // Flags is a combination of FCVAR flags in cvar.h. - // hOut is filled in with a handle to the variable. - virtual bool RegisterConCommandBase(ConCommandBase* pVar) = 0; -}; - -class CCommand -{ -public: - CCommand() = delete; - - int64_t ArgC() const; - const char** ArgV() const; - const char* ArgS() const; // All args that occur after the 0th arg, in string form - const char* GetCommandString() const; // The entire command in string form, including the 0th arg - const char* operator[](int nIndex) const; // Gets at arguments - const char* Arg(int nIndex) const; // Gets at arguments - - static int MaxCommandLength(); - -private: - enum - { - COMMAND_MAX_ARGC = 64, - COMMAND_MAX_LENGTH = 512, - }; - - int64_t m_nArgc; - int64_t m_nArgv0Size; - char m_pArgSBuffer[COMMAND_MAX_LENGTH]; - char m_pArgvBuffer[COMMAND_MAX_LENGTH]; - const char* m_ppArgv[COMMAND_MAX_ARGC]; -}; - -inline int CCommand::MaxCommandLength() -{ - return COMMAND_MAX_LENGTH - 1; -} -inline int64_t CCommand::ArgC() const -{ - return m_nArgc; -} -inline const char** CCommand::ArgV() const -{ - return m_nArgc ? (const char**)m_ppArgv : NULL; -} -inline const char* CCommand::ArgS() const -{ - return m_nArgv0Size ? &m_pArgSBuffer[m_nArgv0Size] : ""; -} -inline const char* CCommand::GetCommandString() const -{ - return m_nArgc ? m_pArgSBuffer : ""; -} -inline const char* CCommand::Arg(int nIndex) const -{ - // FIXME: Many command handlers appear to not be particularly careful - // about checking for valid argc range. For now, we're going to - // do the extra check and return an empty string if it's out of range - if (nIndex < 0 || nIndex >= m_nArgc) - return ""; - return m_ppArgv[nIndex]; -} -inline const char* CCommand::operator[](int nIndex) const -{ - return Arg(nIndex); -} - -//----------------------------------------------------------------------------- -// Called when a ConCommand needs to execute -//----------------------------------------------------------------------------- -typedef void (*FnCommandCallback_t)(const CCommand& command); - -#define COMMAND_COMPLETION_MAXITEMS 64 -#define COMMAND_COMPLETION_ITEM_LENGTH 128 - -//----------------------------------------------------------------------------- -// Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings -//----------------------------------------------------------------------------- -typedef int (*FnCommandCompletionCallback)(const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]); - -// From r5reloaded -class ConCommandBase -{ -public: - bool HasFlags(int nFlags); - void AddFlags(int nFlags); - void RemoveFlags(int nFlags); - - bool IsCommand(void) const; - bool IsRegistered(void) const; - bool IsFlagSet(int nFlags) const; - static bool IsFlagSet(ConCommandBase* pCommandBase, int nFlags); // For hooking to engine's implementation. - - int GetFlags(void) const; - ConCommandBase* GetNext(void) const; - const char* GetHelpText(void) const; - - char* CopyString(const char* szFrom) const; - - void* m_pConCommandBaseVTable; // 0x0000 - ConCommandBase* m_pNext; // 0x0008 - bool m_bRegistered; // 0x0010 - char pad_0011[7]; // 0x0011 <- 3 bytes padding + unk int32. - const char* m_pszName; // 0x0018 - const char* m_pszHelpString; // 0x0020 - int m_nFlags; // 0x0028 - ConCommandBase* s_pConCommandBases; // 0x002C - IConCommandBaseAccessor* s_pAccessor; // 0x0034 -}; // Size: 0x0040 - -// taken from ttf2sdk -class ConCommand : public ConCommandBase -{ - friend class CCVar; - -public: - ConCommand(void) {}; // !TODO: Rebuild engine constructor in SDK instead. - ConCommand(const char* szName, const char* szHelpString, int nFlags, void* pCallback, void* pCommandCompletionCallback); - void Init(void); - bool IsCommand(void) const; - - FnCommandCallback_t m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase. - FnCommandCompletionCallback m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax'). - int m_nCallbackFlags {}; // 0x0050 - char pad_0054[4]; // 0x0054 - int unk0; // 0x0058 - int unk1; // 0x005C -}; // Size: 0x0060 - -void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags); -void RegisterConCommand( - const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, FnCommandCompletionCallback completionCallback); diff --git a/NorthstarDLL/core/convar/convar.cpp b/NorthstarDLL/core/convar/convar.cpp deleted file mode 100644 index e77ae1fd..00000000 --- a/NorthstarDLL/core/convar/convar.cpp +++ /dev/null @@ -1,534 +0,0 @@ -#include "bits.h" -#include "cvar.h" -#include "convar.h" -#include "core/sourceinterface.h" - -#include "plugins/pluginbackend.h" -#include "plugins/plugin_abi.h" - -#include <float.h> - -typedef void (*ConVarRegisterType)( - ConVar* pConVar, - const char* pszName, - const char* pszDefaultValue, - int nFlags, - const char* pszHelpString, - bool bMin, - float fMin, - bool bMax, - float fMax, - void* pCallback); -ConVarRegisterType conVarRegister; - -typedef void (*ConVarMallocType)(void* pConVarMaloc, int a2, int a3); -ConVarMallocType conVarMalloc; - -void* g_pConVar_Vtable = nullptr; -void* g_pIConVar_Vtable = nullptr; - -//----------------------------------------------------------------------------- -// Purpose: ConVar interface initialization -//----------------------------------------------------------------------------- -ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) -{ - conVarMalloc = module.Offset(0x415C20).RCast<ConVarMallocType>(); - conVarRegister = module.Offset(0x417230).RCast<ConVarRegisterType>(); - - g_pConVar_Vtable = module.Offset(0x67FD28); - g_pIConVar_Vtable = module.Offset(0x67FDC8); - - g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007"); - g_pCVar = *g_pCVarInterface; - - g_pPluginCommunicationhandler->m_sEngineData.conVarMalloc = reinterpret_cast<PluginConVarMallocType>(conVarMalloc); - g_pPluginCommunicationhandler->m_sEngineData.conVarRegister = reinterpret_cast<PluginConVarRegisterType>(conVarRegister); - g_pPluginCommunicationhandler->m_sEngineData.ConVar_Vtable = reinterpret_cast<void*>(g_pConVar_Vtable); - g_pPluginCommunicationhandler->m_sEngineData.IConVar_Vtable = reinterpret_cast<void*>(g_pIConVar_Vtable); - g_pPluginCommunicationhandler->m_sEngineData.g_pCVar = reinterpret_cast<void*>(g_pCVar); -} - -//----------------------------------------------------------------------------- -// Purpose: constructor -//----------------------------------------------------------------------------- -ConVar::ConVar(const char* pszName, const char* pszDefaultValue, int nFlags, const char* pszHelpString) -{ - spdlog::info("Registering Convar {}", pszName); - - this->m_ConCommandBase.m_pConCommandBaseVTable = g_pConVar_Vtable; - this->m_ConCommandBase.s_pConCommandBases = (ConCommandBase*)g_pIConVar_Vtable; - - conVarMalloc(&this->m_pMalloc, 0, 0); // Allocate new memory for ConVar. - conVarRegister(this, pszName, pszDefaultValue, nFlags, pszHelpString, 0, 0, 0, 0, 0); -} - -//----------------------------------------------------------------------------- -// Purpose: constructor -//----------------------------------------------------------------------------- -ConVar::ConVar( - const char* pszName, - const char* pszDefaultValue, - int nFlags, - const char* pszHelpString, - bool bMin, - float fMin, - bool bMax, - float fMax, - FnChangeCallback_t pCallback) -{ - spdlog::info("Registering Convar {}", pszName); - - this->m_ConCommandBase.m_pConCommandBaseVTable = g_pConVar_Vtable; - this->m_ConCommandBase.s_pConCommandBases = (ConCommandBase*)g_pIConVar_Vtable; - - conVarMalloc(&this->m_pMalloc, 0, 0); // Allocate new memory for ConVar. - conVarRegister(this, pszName, pszDefaultValue, nFlags, pszHelpString, bMin, fMin, bMax, fMax, (void*)pCallback); -} - -//----------------------------------------------------------------------------- -// Purpose: destructor -//----------------------------------------------------------------------------- -ConVar::~ConVar(void) -{ - if (m_Value.m_pszString) - delete[] m_Value.m_pszString; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the base ConVar name. -// Output : const char* -//----------------------------------------------------------------------------- -const char* ConVar::GetBaseName(void) const -{ - return m_ConCommandBase.m_pszName; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the ConVar help text. -// Output : const char* -//----------------------------------------------------------------------------- -const char* ConVar::GetHelpText(void) const -{ - return m_ConCommandBase.m_pszHelpString; -} - -//----------------------------------------------------------------------------- -// Purpose: Add's flags to ConVar. -// Input : nFlags - -//----------------------------------------------------------------------------- -void ConVar::AddFlags(int nFlags) -{ - m_ConCommandBase.m_nFlags |= nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Removes flags from ConVar. -// Input : nFlags - -//----------------------------------------------------------------------------- -void ConVar::RemoveFlags(int nFlags) -{ - m_ConCommandBase.m_nFlags &= ~nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Return ConVar value as a boolean. -// Output : bool -//----------------------------------------------------------------------------- -bool ConVar::GetBool(void) const -{ - return !!GetInt(); -} - -//----------------------------------------------------------------------------- -// Purpose: Return ConVar value as a float. -// Output : float -//----------------------------------------------------------------------------- -float ConVar::GetFloat(void) const -{ - return m_Value.m_fValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Return ConVar value as an integer. -// Output : int -//----------------------------------------------------------------------------- -int ConVar::GetInt(void) const -{ - return m_Value.m_nValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Return ConVar value as a color. -// Output : Color -//----------------------------------------------------------------------------- -Color ConVar::GetColor(void) const -{ - unsigned char* pColorElement = ((unsigned char*)&m_Value.m_nValue); - return Color(pColorElement[0], pColorElement[1], pColorElement[2], pColorElement[3]); -} - -//----------------------------------------------------------------------------- -// Purpose: Return ConVar value as a string. -// Output : const char * -//----------------------------------------------------------------------------- -const char* ConVar::GetString(void) const -{ - if (m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING) - { - return "FCVAR_NEVER_AS_STRING"; - } - - char const* str = m_Value.m_pszString; - return str ? str : ""; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : flMinVal - -// Output : true if there is a min set. -//----------------------------------------------------------------------------- -bool ConVar::GetMin(float& flMinVal) const -{ - flMinVal = m_fMinVal; - return m_bHasMin; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : flMaxVal - -// Output : true if there is a max set. -//----------------------------------------------------------------------------- -bool ConVar::GetMax(float& flMaxVal) const -{ - flMaxVal = m_fMaxVal; - return m_bHasMax; -} - -//----------------------------------------------------------------------------- -// Purpose: returns the min value. -// Output : float -//----------------------------------------------------------------------------- -float ConVar::GetMinValue(void) const -{ - return m_fMinVal; -} - -//----------------------------------------------------------------------------- -// Purpose: returns the max value. -// Output : float -//----------------------------------------------------------------------------- -float ConVar::GetMaxValue(void) const -{ - return m_fMaxVal; - ; -} - -//----------------------------------------------------------------------------- -// Purpose: checks if ConVar has min value. -// Output : bool -//----------------------------------------------------------------------------- -bool ConVar::HasMin(void) const -{ - return m_bHasMin; -} - -//----------------------------------------------------------------------------- -// Purpose: checks if ConVar has max value. -// Output : bool -//----------------------------------------------------------------------------- -bool ConVar::HasMax(void) const -{ - return m_bHasMax; -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ConVar int value. -// Input : nValue - -//----------------------------------------------------------------------------- -void ConVar::SetValue(int nValue) -{ - if (nValue == m_Value.m_nValue) - { - return; - } - - float flValue = (float)nValue; - - // Check bounds. - if (ClampValue(flValue)) - { - nValue = (int)(flValue); - } - - // Redetermine value. - float flOldValue = m_Value.m_fValue; - m_Value.m_fValue = flValue; - m_Value.m_nValue = nValue; - - if (!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING)) - { - char szTempValue[32]; - snprintf(szTempValue, sizeof(szTempValue), "%d", m_Value.m_nValue); - ChangeStringValue(szTempValue, flOldValue); - } -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ConVar float value. -// Input : flValue - -//----------------------------------------------------------------------------- -void ConVar::SetValue(float flValue) -{ - if (flValue == m_Value.m_fValue) - { - return; - } - - // Check bounds. - ClampValue(flValue); - - // Redetermine value. - float flOldValue = m_Value.m_fValue; - m_Value.m_fValue = flValue; - m_Value.m_nValue = (int)m_Value.m_fValue; - - if (!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING)) - { - char szTempValue[32]; - snprintf(szTempValue, sizeof(szTempValue), "%f", m_Value.m_fValue); - ChangeStringValue(szTempValue, flOldValue); - } -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ConVar string value. -// Input : *szValue - -//----------------------------------------------------------------------------- -void ConVar::SetValue(const char* pszValue) -{ - if (strcmp(this->m_Value.m_pszString, pszValue) == 0) - return; - - char szTempValue[32] {}; - const char* pszNewValue {}; - - float flOldValue = m_Value.m_fValue; - pszNewValue = (char*)pszValue; - if (!pszNewValue) - { - pszNewValue = ""; - } - - if (!SetColorFromString(pszValue)) - { - // Not a color, do the standard thing - float flNewValue = (float)atof(pszValue); - if (!std::isfinite(flNewValue)) - { - spdlog::warn("Warning: ConVar '{}' = '{}' is infinite, clamping value.\n", GetBaseName(), pszValue); - flNewValue = FLT_MAX; - } - - if (ClampValue(flNewValue)) - { - snprintf(szTempValue, sizeof(szTempValue), "%f", flNewValue); - pszNewValue = szTempValue; - } - - // Redetermine value - m_Value.m_fValue = flNewValue; - m_Value.m_nValue = (int)(m_Value.m_fValue); - } - - if (!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING)) - { - ChangeStringValue(pszNewValue, flOldValue); - } -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ConVar color value. -// Input : clValue - -//----------------------------------------------------------------------------- -void ConVar::SetValue(Color clValue) -{ - std::string svResult = ""; - - for (int i = 0; i < 4; i++) - { - if (!(clValue.GetValue(i) == 0 && svResult.size() == 0)) - { - svResult += std::to_string(clValue.GetValue(i)); - svResult.append(" "); - } - } - - this->m_Value.m_pszString = svResult.c_str(); -} - -//----------------------------------------------------------------------------- -// Purpose: changes the ConVar string value. -// Input : *pszTempVal - flOldValue -//----------------------------------------------------------------------------- -void ConVar::ChangeStringValue(const char* pszTempVal, float flOldValue) -{ - assert(!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING)); - - char* pszOldValue = (char*)_malloca(m_Value.m_iStringLength); - if (pszOldValue != NULL) - { - memcpy(pszOldValue, m_Value.m_pszString, m_Value.m_iStringLength); - } - - if (pszTempVal) - { - int len = strlen(pszTempVal) + 1; - - if (len > m_Value.m_iStringLength) - { - if (m_Value.m_pszString) - delete[] m_Value.m_pszString; - - m_Value.m_pszString = new char[len]; - m_Value.m_iStringLength = len; - } - - memcpy((char*)m_Value.m_pszString, pszTempVal, len); - } - else - { - m_Value.m_pszString = NULL; - } - - pszOldValue = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ConVar color value from string. -// Input : *pszValue - -//----------------------------------------------------------------------------- -bool ConVar::SetColorFromString(const char* pszValue) -{ - bool bColor = false; - - // Try pulling RGBA color values out of the string. - int nRGBA[4] {}; - int nParamsRead = sscanf_s(pszValue, "%i %i %i %i", &(nRGBA[0]), &(nRGBA[1]), &(nRGBA[2]), &(nRGBA[3])); - - if (nParamsRead >= 3) - { - // This is probably a color! - if (nParamsRead == 3) - { - // Assume they wanted full alpha. - nRGBA[3] = 255; - } - - if (nRGBA[0] >= 0 && nRGBA[0] <= 255 && nRGBA[1] >= 0 && nRGBA[1] <= 255 && nRGBA[2] >= 0 && nRGBA[2] <= 255 && nRGBA[3] >= 0 && - nRGBA[3] <= 255) - { - // printf("*** WOW! Found a color!! ***\n"); - - // This is definitely a color! - bColor = true; - - // Stuff all the values into each byte of our int. - unsigned char* pColorElement = ((unsigned char*)&m_Value.m_nValue); - pColorElement[0] = nRGBA[0]; - pColorElement[1] = nRGBA[1]; - pColorElement[2] = nRGBA[2]; - pColorElement[3] = nRGBA[3]; - - // Copy that value into our float. - m_Value.m_fValue = (float)(m_Value.m_nValue); - } - } - - return bColor; -} - -//----------------------------------------------------------------------------- -// Purpose: Checks if ConVar is registered. -// Output : bool -//----------------------------------------------------------------------------- -bool ConVar::IsRegistered(void) const -{ - return m_ConCommandBase.m_bRegistered; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true if this is a command -// Output : bool -//----------------------------------------------------------------------------- -bool ConVar::IsCommand(void) const -{ - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Test each ConVar query before setting the value. -// Input : nFlags -// Output : False if change is permitted, true if not. -//----------------------------------------------------------------------------- -bool ConVar::IsFlagSet(int nFlags) const -{ - return m_ConCommandBase.m_nFlags & nFlags; -} - -//----------------------------------------------------------------------------- -// Purpose: Check whether to clamp and then perform clamp. -// Input : flValue - -// Output : Returns true if value changed. -//----------------------------------------------------------------------------- -bool ConVar::ClampValue(float& flValue) -{ - if (m_bHasMin && (flValue < m_fMinVal)) - { - flValue = m_fMinVal; - return true; - } - - if (m_bHasMax && (flValue > m_fMaxVal)) - { - flValue = m_fMaxVal; - return true; - } - - return false; -} - -int ParseConVarFlagsString(std::string modName, std::string sFlags) -{ - int iFlags = 0; - std::stringstream stFlags(sFlags); - std::string sFlag; - - while (std::getline(stFlags, sFlag, '|')) - { - // trim the flag - sFlag.erase(sFlag.find_last_not_of(" \t\n\f\v\r") + 1); - sFlag.erase(0, sFlag.find_first_not_of(" \t\n\f\v\r")); - - // skip if empty - if (sFlag.empty()) - continue; - - // find the matching flag value - bool ok = false; - for (auto const& flagPair : g_PrintCommandFlags) - { - if (sFlag == flagPair.second) - { - iFlags |= flagPair.first; - ok = true; - break; - } - } - if (!ok) - { - spdlog::warn("Mod ConCommand {} has unknown flag {}", modName, sFlag); - } - } - - return iFlags; -} diff --git a/NorthstarDLL/core/convar/convar.h b/NorthstarDLL/core/convar/convar.h deleted file mode 100644 index f0366b46..00000000 --- a/NorthstarDLL/core/convar/convar.h +++ /dev/null @@ -1,194 +0,0 @@ -#pragma once -#include "core/sourceinterface.h" -#include "core/math/color.h" -#include "cvar.h" -#include "concommand.h" - -// taken directly from iconvar.h - -// The default, no flags at all -#define FCVAR_NONE 0 - -// Command to ConVars and ConCommands -// ConVar Systems -#define FCVAR_UNREGISTERED (1 << 0) // If this is set, don't add to linked list, etc. -#define FCVAR_DEVELOPMENTONLY (1 << 1) // Hidden in released products. Flag is removed automatically if ALLOW_DEVELOPMENT_CVARS is defined. -#define FCVAR_GAMEDLL (1 << 2) // defined by the game DLL -#define FCVAR_CLIENTDLL (1 << 3) // defined by the client DLL -#define FCVAR_HIDDEN (1 << 4) // Hidden. Doesn't appear in find or auto complete. Like DEVELOPMENTONLY, but can't be compiled out. - -// ConVar only -#define FCVAR_PROTECTED \ - (1 << 5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as - // value. -#define FCVAR_SPONLY (1 << 6) // This cvar cannot be changed by clients connected to a multiplayer server. -#define FCVAR_ARCHIVE (1 << 7) // set to cause it to be saved to vars.rc -#define FCVAR_NOTIFY (1 << 8) // notifies players when changed -#define FCVAR_USERINFO (1 << 9) // changes the client's info string - -#define FCVAR_PRINTABLEONLY (1 << 10) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). -#define FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS \ - (1 << 10) // When on concommands this allows remote clients to execute this cmd on the server. - // We are changing the default behavior of concommands to disallow execution by remote clients without - // this flag due to the number existing concommands that can lag or crash the server when clients abuse them. - -#define FCVAR_UNLOGGED (1 << 11) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log -#define FCVAR_NEVER_AS_STRING (1 << 12) // never try to print that cvar - -// It's a ConVar that's shared between the client and the server. -// At signon, the values of all such ConVars are sent from the server to the client (skipped for local client, of course ) -// If a change is requested it must come from the console (i.e., no remote client changes) -// If a value is changed while a server is active, it's replicated to all connected clients -#define FCVAR_REPLICATED (1 << 13) // server setting enforced on clients, TODO rename to FCAR_SERVER at some time -#define FCVAR_CHEAT (1 << 14) // Only useable in singleplayer / debug / multiplayer & sv_cheats -#define FCVAR_SS (1 << 15) // causes varnameN where N == 2 through max splitscreen slots for mod to be autogenerated -#define FCVAR_DEMO (1 << 16) // record this cvar when starting a demo file -#define FCVAR_DONTRECORD (1 << 17) // don't record these command in demofiles -#define FCVAR_SS_ADDED (1 << 18) // This is one of the "added" FCVAR_SS variables for the splitscreen players -#define FCVAR_RELEASE (1 << 19) // Cvars tagged with this are the only cvars avaliable to customers -#define FCVAR_RELOAD_MATERIALS (1 << 20) // If this cvar changes, it forces a material reload -#define FCVAR_RELOAD_TEXTURES (1 << 21) // If this cvar changes, if forces a texture reload - -#define FCVAR_NOT_CONNECTED (1 << 22) // cvar cannot be changed by a client that is connected to a server -#define FCVAR_MATERIAL_SYSTEM_THREAD (1 << 23) // Indicates this cvar is read from the material system thread -#define FCVAR_ARCHIVE_PLAYERPROFILE (1 << 24) // respawn-defined flag, same as FCVAR_ARCHIVE but writes to profile.cfg - -#define FCVAR_SERVER_CAN_EXECUTE \ - (1 << 28) // the server is allowed to execute this command on clients via - // ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd. -#define FCVAR_SERVER_CANNOT_QUERY \ - (1 << 29) // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue). - -// !!!NOTE!!! : this is likely incorrect, there are multiple concommands that the vanilla game registers with this flag that 100% should not -// be remotely executable i.e. multiple commands that only exist on client (screenshot, joystick_initialize) we now use -// FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS in all places this flag was previously used -#define FCVAR_CLIENTCMD_CAN_EXECUTE \ - (1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command. - // Note: IVEngineClient::ClientCmd_Unrestricted can run any client command. - -#define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) // used as a debugging tool necessary to check material system thread convars - -// TODO: could be cool to repurpose these for northstar use somehow? -// #define FCVAR_AVAILABLE (1<<26) -// #define FCVAR_AVAILABLE (1<<27) -// #define FCVAR_AVAILABLE (1<<31) - -// flag => string stuff -const std::multimap<int, const char*> g_PrintCommandFlags = { - {FCVAR_UNREGISTERED, "UNREGISTERED"}, - {FCVAR_DEVELOPMENTONLY, "DEVELOPMENTONLY"}, - {FCVAR_GAMEDLL, "GAMEDLL"}, - {FCVAR_CLIENTDLL, "CLIENTDLL"}, - {FCVAR_HIDDEN, "HIDDEN"}, - {FCVAR_PROTECTED, "PROTECTED"}, - {FCVAR_SPONLY, "SPONLY"}, - {FCVAR_ARCHIVE, "ARCHIVE"}, - {FCVAR_NOTIFY, "NOTIFY"}, - {FCVAR_USERINFO, "USERINFO"}, - - // TODO: PRINTABLEONLY and GAMEDLL_FOR_REMOTE_CLIENTS are both 1<<10, one is for vars and one is for commands - // this fucking sucks i think - {FCVAR_PRINTABLEONLY, "PRINTABLEONLY"}, - {FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, "GAMEDLL_FOR_REMOTE_CLIENTS"}, - - {FCVAR_UNLOGGED, "UNLOGGED"}, - {FCVAR_NEVER_AS_STRING, "NEVER_AS_STRING"}, - {FCVAR_REPLICATED, "REPLICATED"}, - {FCVAR_CHEAT, "CHEAT"}, - {FCVAR_SS, "SS"}, - {FCVAR_DEMO, "DEMO"}, - {FCVAR_DONTRECORD, "DONTRECORD"}, - {FCVAR_SS_ADDED, "SS_ADDED"}, - {FCVAR_RELEASE, "RELEASE"}, - {FCVAR_RELOAD_MATERIALS, "RELOAD_MATERIALS"}, - {FCVAR_RELOAD_TEXTURES, "RELOAD_TEXTURES"}, - {FCVAR_NOT_CONNECTED, "NOT_CONNECTED"}, - {FCVAR_MATERIAL_SYSTEM_THREAD, "MATERIAL_SYSTEM_THREAD"}, - {FCVAR_ARCHIVE_PLAYERPROFILE, "ARCHIVE_PLAYERPROFILE"}, - {FCVAR_SERVER_CAN_EXECUTE, "SERVER_CAN_EXECUTE"}, - {FCVAR_SERVER_CANNOT_QUERY, "SERVER_CANNOT_QUERY"}, - {FCVAR_CLIENTCMD_CAN_EXECUTE, "UNKNOWN"}, - {FCVAR_ACCESSIBLE_FROM_THREADS, "ACCESSIBLE_FROM_THREADS"}}; - -//----------------------------------------------------------------------------- -// Forward declarations -//----------------------------------------------------------------------------- -class ConCommandBase; -class ConCommand; -class ConVar; - -typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue); - -//----------------------------------------------------------------------------- -// Purpose: A console variable -//----------------------------------------------------------------------------- -class ConVar -{ -public: - ConVar(void) {}; - ConVar(const char* pszName, const char* pszDefaultValue, int nFlags, const char* pszHelpString); - ConVar( - const char* pszName, - const char* pszDefaultValue, - int nFlags, - const char* pszHelpString, - bool bMin, - float fMin, - bool bMax, - float fMax, - FnChangeCallback_t pCallback); - ~ConVar(void); - - const char* GetBaseName(void) const; - const char* GetHelpText(void) const; - - void AddFlags(int nFlags); - void RemoveFlags(int nFlags); - - bool GetBool(void) const; - float GetFloat(void) const; - int GetInt(void) const; - Color GetColor(void) const; - const char* GetString(void) const; - - bool GetMin(float& flMinValue) const; - bool GetMax(float& flMaxValue) const; - float GetMinValue(void) const; - float GetMaxValue(void) const; - - bool HasMin(void) const; - bool HasMax(void) const; - - void SetValue(int nValue); - void SetValue(float flValue); - void SetValue(const char* pszValue); - void SetValue(Color clValue); - - void ChangeStringValue(const char* pszTempValue, float flOldValue); - bool SetColorFromString(const char* pszValue); - bool ClampValue(float& value); - - bool IsRegistered(void) const; - bool IsCommand(void) const; - bool IsFlagSet(int nFlags) const; - - struct CVValue_t - { - const char* m_pszString; - int64_t m_iStringLength; - float m_fValue; - int m_nValue; - }; - - ConCommandBase m_ConCommandBase {}; // 0x0000 - const char* m_pszDefaultValue {}; // 0x0040 - CVValue_t m_Value {}; // 0x0048 - bool m_bHasMin {}; // 0x005C - float m_fMinVal {}; // 0x0060 - bool m_bHasMax {}; // 0x0064 - float m_fMaxVal {}; // 0x0068 - void* m_pMalloc {}; // 0x0070 - char m_pPad80[10] {}; // 0x0080 -}; // Size: 0x0080 - -int ParseConVarFlagsString(std::string modName, std::string flags); diff --git a/NorthstarDLL/core/convar/cvar.cpp b/NorthstarDLL/core/convar/cvar.cpp deleted file mode 100644 index aa5f0365..00000000 --- a/NorthstarDLL/core/convar/cvar.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "cvar.h" -#include "convar.h" -#include "concommand.h" - -//----------------------------------------------------------------------------- -// Purpose: returns all ConVars -//----------------------------------------------------------------------------- -std::unordered_map<std::string, ConCommandBase*> CCvar::DumpToMap() -{ - std::stringstream ss; - CCVarIteratorInternal* itint = FactoryInternalIterator(); // Allocate new InternalIterator. - - std::unordered_map<std::string, ConCommandBase*> allConVars; - - for (itint->SetFirst(); itint->IsValid(); itint->Next()) // Loop through all instances. - { - ConCommandBase* pCommand = itint->Get(); - const char* pszCommandName = pCommand->m_pszName; - allConVars[pszCommandName] = pCommand; - } - - return allConVars; -} - -SourceInterface<CCvar>* g_pCVarInterface; -CCvar* g_pCVar; diff --git a/NorthstarDLL/core/convar/cvar.h b/NorthstarDLL/core/convar/cvar.h deleted file mode 100644 index beaa84f4..00000000 --- a/NorthstarDLL/core/convar/cvar.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include "convar.h" - -//----------------------------------------------------------------------------- -// Forward declarations -//----------------------------------------------------------------------------- -class ConCommandBase; -class ConCommand; -class ConVar; - -//----------------------------------------------------------------------------- -// Internals for ICVarIterator -//----------------------------------------------------------------------------- -class CCVarIteratorInternal // Fully reversed table, just look at the virtual function table and rename the function. -{ -public: - virtual void SetFirst(void) = 0; // 0 - virtual void Next(void) = 0; // 1 - virtual bool IsValid(void) = 0; // 2 - virtual ConCommandBase* Get(void) = 0; // 3 -}; - -//----------------------------------------------------------------------------- -// Default implementation -//----------------------------------------------------------------------------- -class CCvar -{ -public: - M_VMETHOD(ConCommandBase*, FindCommandBase, 14, (const char* pszCommandName), (this, pszCommandName)); - M_VMETHOD(ConVar*, FindVar, 16, (const char* pszVarName), (this, pszVarName)); - M_VMETHOD(ConCommand*, FindCommand, 18, (const char* pszCommandName), (this, pszCommandName)); - M_VMETHOD(CCVarIteratorInternal*, FactoryInternalIterator, 41, (), (this)); - - std::unordered_map<std::string, ConCommandBase*> DumpToMap(); -}; - -extern SourceInterface<CCvar>* g_pCVarInterface; -extern CCvar* g_pCVar; diff --git a/NorthstarDLL/core/filesystem/filesystem.cpp b/NorthstarDLL/core/filesystem/filesystem.cpp deleted file mode 100644 index b39939e4..00000000 --- a/NorthstarDLL/core/filesystem/filesystem.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include "filesystem.h" -#include "core/sourceinterface.h" -#include "mods/modmanager.h" - -#include <iostream> -#include <sstream> - -AUTOHOOK_INIT() - -bool bReadingOriginalFile = false; -std::string sCurrentModPath; - -ConVar* Cvar_ns_fs_log_reads; - -SourceInterface<IFileSystem>* g_pFilesystem; - -std::string ReadVPKFile(const char* path) -{ - // read scripts.rson file, todo: check if this can be overwritten - FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0); - - std::stringstream fileStream; - int bytesRead = 0; - char data[4096]; - do - { - bytesRead = (*g_pFilesystem)->m_vtable2->Read(&(*g_pFilesystem)->m_vtable2, data, (int)std::size(data), fileHandle); - fileStream.write(data, bytesRead); - } while (bytesRead == std::size(data)); - - (*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle); - - return fileStream.str(); -} - -std::string ReadVPKOriginalFile(const char* path) -{ - // todo: should probably set search path to be g_pModName here also - - bReadingOriginalFile = true; - std::string ret = ReadVPKFile(path); - bReadingOriginalFile = false; - - return ret; -} - -// clang-format off -HOOK(AddSearchPathHook, AddSearchPath, -void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)) -// clang-format on -{ - AddSearchPath(fileSystem, pPath, pathID, addType); - - // make sure current mod paths are at head - if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD) - { - AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD); - AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD); - } -} - -void SetNewModSearchPaths(Mod* mod) -{ - // put our new path to the head if we need to read from a different mod path - // in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets - if (mod != nullptr) - { - if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath)) - { - AddSearchPath( - &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); - sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string(); - } - } - else // push compiled to head - AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD); -} - -bool TryReplaceFile(const char* pPath, bool shouldCompile) -{ - if (bReadingOriginalFile) - return false; - - if (shouldCompile) - g_pModManager->CompileAssetsForFile(pPath); - - // idk how efficient the lexically normal check is - // can't just set all /s in path to \, since some paths aren't in writeable memory - auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath))); - if (file != g_pModManager->m_ModFiles.end()) - { - SetNewModSearchPaths(file->second.m_pOwningMod); - return true; - } - - return false; -} - -// force modded files to be read from mods, not cache -// clang-format off -HOOK(ReadFromCacheHook, ReadFromCache, -bool, __fastcall, (IFileSystem * filesystem, char* pPath, void* result)) -// clang-format off -{ - if (TryReplaceFile(pPath, true)) - return false; - - return ReadFromCache(filesystem, pPath, result); -} - -// force modded files to be read from mods, not vpk -// clang-format off -AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0, -FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename)) -// clang-format on -{ - // don't compile here because this is only ever called from OpenEx, which already compiles - if (TryReplaceFile(filename, false)) - { - *b = -1; - return b; - } - - return ReadFileFromVPK(vpkInfo, b, filename); -} - -// clang-format off -AUTOHOOK(CBaseFileSystem__OpenEx, filesystem_stdio.dll + 0x15F50, -FileHandle_t, __fastcall, (IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char **ppszResolvedFilename)) -// clang-format on -{ - TryReplaceFile(pPath, true); - return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename); -} - -HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath)) -{ - NS::log::fs->info("MountVPK {}", pVpkPath); - VPKData* ret = MountVPK(fileSystem, pVpkPath); - - for (Mod mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - for (ModVPKEntry& vpkEntry : mod.Vpks) - { - // if we're autoloading, just load no matter what - if (!vpkEntry.m_bAutoLoad) - { - // resolve vpk name and try to load one with the same name - // todo: we should be unloading these on map unload manually - std::string mapName(fs::path(pVpkPath).filename().string()); - std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string()); - if (mapName.compare(modMapName)) - continue; - } - - VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str()); - if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here - ret = loaded; - } - } - - return ret; -} - -ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - g_pFilesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017"); - - AddSearchPathHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->AddSearchPath); - ReadFromCacheHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->ReadFromCache); - MountVPKHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->MountVPK); -} diff --git a/NorthstarDLL/core/filesystem/filesystem.h b/NorthstarDLL/core/filesystem/filesystem.h deleted file mode 100644 index fcd1bb2f..00000000 --- a/NorthstarDLL/core/filesystem/filesystem.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once -#include "core/sourceinterface.h" - -// taken from ttf2sdk -typedef void* FileHandle_t; - -#pragma pack(push, 1) - -// clang-format off -OFFSET_STRUCT(VPKFileEntry) -{ - STRUCT_SIZE(0x44); - FIELDS(0x0, - char* directory; - char* filename; - char* extension; - ) -}; -// clang-format on -#pragma pack(pop) - -struct VPKData; - -enum SearchPathAdd_t -{ - PATH_ADD_TO_HEAD, // First path searched - PATH_ADD_TO_TAIL, // Last path searched -}; - -class CSearchPath -{ -public: - unsigned char unknown[0x18]; - const char* debugPath; -}; - -class IFileSystem -{ -public: - struct VTable - { - void* unknown[10]; - void (*AddSearchPath)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType); - void* unknown2[84]; - bool (*ReadFromCache)(IFileSystem* fileSystem, const char* path, void* result); - void* unknown3[15]; - VPKData* (*MountVPK)(IFileSystem* fileSystem, const char* vpkPath); - }; - - struct VTable2 - { - int (*Read)(IFileSystem::VTable2** fileSystem, void* pOutput, int size, FileHandle_t file); - void* unknown[1]; - FileHandle_t (*Open)( - IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown); - void (*Close)(IFileSystem* fileSystem, FileHandle_t file); - long long (*Seek)(IFileSystem::VTable2** fileSystem, FileHandle_t file, long long offset, long long whence); - void* unknown2[5]; - bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID); - }; - - VTable* m_vtable; - VTable2* m_vtable2; -}; - -extern SourceInterface<IFileSystem>* g_pFilesystem; - -std::string ReadVPKFile(const char* path); -std::string ReadVPKOriginalFile(const char* path); diff --git a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp b/NorthstarDLL/core/filesystem/rpakfilesystem.cpp deleted file mode 100644 index da72646b..00000000 --- a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp +++ /dev/null @@ -1,347 +0,0 @@ -#include "rpakfilesystem.h" -#include "mods/modmanager.h" -#include "dedicated/dedicated.h" -#include "core/tier0.h" - -AUTOHOOK_INIT() - -// there are more i'm just too lazy to add -struct PakLoadFuncs -{ - void* unk0[3]; - int (*LoadPakAsync)(const char* pPath, void* unknownSingleton, int flags, void* callback0, void* callback1); - void* unk1[2]; - void* (*UnloadPak)(int iPakHandle, void* callback); - void* unk2[6]; - void* (*LoadFile)(const char* path); // unsure - void* unk3[10]; - void* (*ReadFileAsync)(const char* pPath, void* a2); -}; - -PakLoadFuncs* g_pakLoadApi; - -PakLoadManager* g_pPakLoadManager; -void** pUnknownPakLoadSingleton; - -int PakLoadManager::LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource) -{ - int nHandle = g_pakLoadApi->LoadPakAsync(pPath, *pUnknownPakLoadSingleton, 2, nullptr, nullptr); - - // set the load source of the pak we just loaded - if (nHandle != -1) - GetPakInfo(nHandle)->m_nLoadSource = nLoadSource; - - return nHandle; -} - -void PakLoadManager::UnloadPak(const int nPakHandle) -{ - g_pakLoadApi->UnloadPak(nPakHandle, nullptr); -} - -void PakLoadManager::UnloadMapPaks() -{ - for (auto& pair : m_vLoadedPaks) - if (pair.second.m_nLoadSource == ePakLoadSource::MAP) - UnloadPak(pair.first); -} - -LoadedPak* PakLoadManager::TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash) -{ - LoadedPak pak; - pak.m_nLoadSource = nLoadSource; - pak.m_nPakHandle = nPakHandle; - pak.m_nPakNameHash = nPakNameHash; - - m_vLoadedPaks.insert(std::make_pair(nPakHandle, pak)); - return &m_vLoadedPaks.at(nPakHandle); -} - -void PakLoadManager::RemoveLoadedPak(int nPakHandle) -{ - m_vLoadedPaks.erase(nPakHandle); -} - -LoadedPak* PakLoadManager::GetPakInfo(const int nPakHandle) -{ - return &m_vLoadedPaks.at(nPakHandle); -} - -int PakLoadManager::GetPakHandle(const size_t nPakNameHash) -{ - for (auto& pair : m_vLoadedPaks) - if (pair.second.m_nPakNameHash == nPakNameHash) - return pair.first; - - return -1; -} - -int PakLoadManager::GetPakHandle(const char* pPath) -{ - return GetPakHandle(STR_HASH(pPath)); -} - -void* PakLoadManager::LoadFile(const char* path) -{ - return g_pakLoadApi->LoadFile(path); -} - -void HandlePakAliases(char** map) -{ - // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift - for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > -1; i--) - { - Mod* mod = &g_pModManager->m_LoadedMods[i]; - if (!mod->m_bEnabled) - continue; - - if (mod->RpakAliases.find(*map) != mod->RpakAliases.end()) - { - *map = &mod->RpakAliases[*map][0]; - return; - } - } -} - -void LoadPreloadPaks() -{ - // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); - - for (ModRpakEntry& pak : mod.Rpaks) - if (pak.m_bAutoLoad) - g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT); - } -} - -void LoadPostloadPaks(const char* pPath) -{ - // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); - - for (ModRpakEntry& pak : mod.Rpaks) - if (pak.m_sLoadAfterPak == pPath) - g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT); - } -} - -void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName) -{ - // whether the vanilla game has this rpak - bool bHasOriginalPak = fs::exists(fs::path("r2/paks/Win64/") / *pakName); - - // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); - - for (ModRpakEntry& pak : mod.Rpaks) - { - if (!pak.m_bAutoLoad && !pak.m_sPakName.compare(*pakName)) - { - // if the game doesn't have the original pak, let it handle loading this one as if it was the one it was loading originally - if (!bHasOriginalPak) - { - std::string path = (modPakPath / pak.m_sPakName).string(); - *pakName = new char[path.size() + 1]; - strcpy(*pakName, &path[0]); - (*pakName)[path.size()] = '\0'; - - bHasOriginalPak = true; - *bNeedToFreePakName = - true; // we can't free this memory until we're done with the pak, so let whatever's calling this deal with it - } - else - g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::MAP); - } - } - } -} - -// clang-format off -HOOK(LoadPakAsyncHook, LoadPakAsync, -int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallback0, void* pCallback1)) -// clang-format on -{ - HandlePakAliases(&pPath); - - // dont load the pak if it's currently loaded already - size_t nPathHash = STR_HASH(pPath); - if (g_pPakLoadManager->GetPakHandle(nPathHash) != -1) - return -1; - - bool bNeedToFreePakName = false; - - static bool bShouldLoadPaks = true; - if (bShouldLoadPaks) - { - // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten by - // LoadCustomMapPaks - std::string originalPath(pPath); - - // disable preloading while we're doing this - bShouldLoadPaks = false; - - LoadPreloadPaks(); - LoadCustomMapPaks(&pPath, &bNeedToFreePakName); - - bShouldLoadPaks = true; - - // do this after custom paks load and in bShouldLoadPaks so we only ever call this on the root pakload call - // todo: could probably add some way to flag custom paks to not be loaded on dedicated servers in rpak.json - - // dedicated only needs common, common_mp, common_sp, and sp_<map> rpaks - // sp_<map> rpaks contain tutorial ghost data - // sucks to have to load the entire rpak for that but sp was never meant to be done on dedi - if (IsDedicatedServer() && - (CommandLine()->CheckParm("-nopakdedi") || strncmp(&originalPath[0], "common", 6) && strncmp(&originalPath[0], "sp_", 3))) - { - if (bNeedToFreePakName) - delete[] pPath; - - NS::log::rpak->info("Not loading pak {} for dedicated server", originalPath); - return -1; - } - } - - int iPakHandle = LoadPakAsync(pPath, unknownSingleton, flags, pCallback0, pCallback1); - NS::log::rpak->info("LoadPakAsync {} {}", pPath, iPakHandle); - - // trak the pak - g_pPakLoadManager->TrackLoadedPak(ePakLoadSource::UNTRACKED, iPakHandle, nPathHash); - LoadPostloadPaks(pPath); - - if (bNeedToFreePakName) - delete[] pPath; - - return iPakHandle; -} - -// clang-format off -HOOK(UnloadPakHook, UnloadPak, -void*, __fastcall, (int nPakHandle, void* pCallback)) -// clang-format on -{ - // stop tracking the pak - g_pPakLoadManager->RemoveLoadedPak(nPakHandle); - - static bool bShouldUnloadPaks = true; - if (bShouldUnloadPaks) - { - bShouldUnloadPaks = false; - g_pPakLoadManager->UnloadMapPaks(); - bShouldUnloadPaks = true; - } - - NS::log::rpak->info("UnloadPak {}", nPakHandle); - return UnloadPak(nPakHandle, pCallback); -} - -// we hook this exclusively for resolving stbsp paths, but seemingly it's also used for other stuff like vpk, rpak, mprj and starpak loads -// tbh this actually might be for memory mapped files or something, would make sense i think -// clang-format off -HOOK(ReadFileAsyncHook, ReadFileAsync, -void*, __fastcall, (const char* pPath, void* pCallback)) -// clang-format on -{ - fs::path path(pPath); - std::string newPath = ""; - fs::path filename = path.filename(); - - if (path.extension() == ".stbsp") - { - if (IsDedicatedServer()) - return nullptr; - - NS::log::rpak->info("LoadStreamBsp: {}", filename.string()); - - // resolve modded stbsp path so we can load mod stbsps - auto modFile = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path("maps" / filename))); - if (modFile != g_pModManager->m_ModFiles.end()) - { - newPath = (modFile->second.m_pOwningMod->m_ModDirectory / "mod" / modFile->second.m_Path).string(); - pPath = newPath.c_str(); - } - } - else if (path.extension() == ".starpak") - { - if (IsDedicatedServer()) - return nullptr; - - // code for this is mostly stolen from above - - // unfortunately I can't find a way to get the rpak that is causing this function call, so I have to - // store them on mod init and then compare the current path with the stored paths - - // game adds r2\ to every path, so assume that a starpak path that begins with r2\paks\ is a vanilla one - // modded starpaks will be in the mod's paks folder (but can be in folders within the paks folder) - - // this might look a bit confusing, but its just an iterator over the various directories in a path. - // path.begin() being the first directory, r2 in this case, which is guaranteed anyway, - // so increment the iterator with ++ to get the first actual directory, * just gets the actual value - // then we compare to "paks" to determine if it's a vanilla rpak or not - if (*++path.begin() != "paks") - { - // remove the r2\ from the start used for path lookups - std::string starpakPath = path.string().substr(3); - // hash the starpakPath to compare with stored entries - size_t hashed = STR_HASH(starpakPath); - - // loop through all loaded mods - for (Mod& mod : g_pModManager->m_LoadedMods) - { - // ignore non-loaded mods - if (!mod.m_bEnabled) - continue; - - // loop through the stored starpak paths - for (size_t hash : mod.StarpakPaths) - { - if (hash == hashed) - { - // construct new path - newPath = (mod.m_ModDirectory / "paks" / starpakPath).string(); - // set path to the new path - pPath = newPath.c_str(); - goto LOG_STARPAK; - } - } - } - } - - LOG_STARPAK: - NS::log::rpak->info("LoadStreamPak: {}", filename.string()); - } - - return ReadFileAsync(pPath, pCallback); -} - -ON_DLL_LOAD("engine.dll", RpakFilesystem, (CModule module)) -{ - AUTOHOOK_DISPATCH(); - - g_pPakLoadManager = new PakLoadManager; - - g_pakLoadApi = module.Offset(0x5BED78).Deref().RCast<PakLoadFuncs*>(); - pUnknownPakLoadSingleton = module.Offset(0x7C5E20).RCast<void**>(); - - LoadPakAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->LoadPakAsync); - UnloadPakHook.Dispatch((LPVOID*)g_pakLoadApi->UnloadPak); - ReadFileAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->ReadFileAsync); -} diff --git a/NorthstarDLL/core/filesystem/rpakfilesystem.h b/NorthstarDLL/core/filesystem/rpakfilesystem.h deleted file mode 100644 index bcd57a73..00000000 --- a/NorthstarDLL/core/filesystem/rpakfilesystem.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -enum class ePakLoadSource -{ - UNTRACKED = -1, // not a pak we loaded, we shouldn't touch this one - - CONSTANT, // should be loaded at all times - MAP // loaded from a map, should be unloaded when the map is unloaded -}; - -struct LoadedPak -{ - ePakLoadSource m_nLoadSource; - int m_nPakHandle; - size_t m_nPakNameHash; -}; - -class PakLoadManager -{ -private: - std::map<int, LoadedPak> m_vLoadedPaks {}; - std::unordered_map<size_t, int> m_HashToPakHandle {}; - -public: - int LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource); - void UnloadPak(const int nPakHandle); - void UnloadMapPaks(); - void* LoadFile(const char* path); // this is a guess - - LoadedPak* TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash); - void RemoveLoadedPak(int nPakHandle); - - LoadedPak* GetPakInfo(const int nPakHandle); - - int GetPakHandle(const size_t nPakNameHash); - int GetPakHandle(const char* pPath); -}; - -extern PakLoadManager* g_pPakLoadManager; diff --git a/NorthstarDLL/core/hooks.cpp b/NorthstarDLL/core/hooks.cpp deleted file mode 100644 index 26b3fe57..00000000 --- a/NorthstarDLL/core/hooks.cpp +++ /dev/null @@ -1,474 +0,0 @@ -#include "dedicated/dedicated.h" -#include "plugins/pluginbackend.h" - -#include <iostream> -#include <wchar.h> -#include <iostream> -#include <vector> -#include <fstream> -#include <sstream> -#include <filesystem> -#include <Psapi.h> - -#define XINPUT1_3_DLL "XInput1_3.dll" - -AUTOHOOK_INIT() - -// called from the ON_DLL_LOAD macros -__dllLoadCallback::__dllLoadCallback( - eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn) -{ - // parse reliesOn array from string - std::vector<std::string> reliesOnArray; - - if (reliesOn.length() && reliesOn[0] != '(') - { - reliesOnArray.push_back(reliesOn); - } - else - { - // follows the format (tag, tag, tag) - std::string sCurrentTag; - for (int i = 1; i < reliesOn.length(); i++) - { - if (!isspace(reliesOn[i])) - { - if (reliesOn[i] == ',' || reliesOn[i] == ')') - { - reliesOnArray.push_back(sCurrentTag); - sCurrentTag = ""; - } - else - sCurrentTag += reliesOn[i]; - } - } - } - - switch (side) - { - case eDllLoadCallbackSide::UNSIDED: - { - AddDllLoadCallback(dllName, callback, uniqueStr, reliesOnArray); - break; - } - - case eDllLoadCallbackSide::CLIENT: - { - AddDllLoadCallbackForClient(dllName, callback, uniqueStr, reliesOnArray); - break; - } - - case eDllLoadCallbackSide::DEDICATED_SERVER: - { - AddDllLoadCallbackForDedicatedServer(dllName, callback, uniqueStr, reliesOnArray); - break; - } - } -} - -void __fileAutohook::Dispatch() -{ - for (__autovar* var : vars) - var->Dispatch(); - - for (__autohook* hook : hooks) - hook->Dispatch(); -} - -void __fileAutohook::DispatchForModule(const char* pModuleName) -{ - const int iModuleNameLen = strlen(pModuleName); - - for (__autohook* hook : hooks) - if ((hook->iAddressResolutionMode == __autohook::OFFSET_STRING && !strncmp(pModuleName, hook->pAddrString, iModuleNameLen)) || - (hook->iAddressResolutionMode == __autohook::PROCADDRESS && !strcmp(pModuleName, hook->pModuleName))) - hook->Dispatch(); -} - -ManualHook::ManualHook(const char* funcName, LPVOID func) : pHookFunc(func), ppOrigFunc(nullptr) -{ - const int iFuncNameStrlen = strlen(funcName); - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); -} - -ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) : pHookFunc(func), ppOrigFunc(orig) -{ - const int iFuncNameStrlen = strlen(funcName); - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); -} - -bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) -{ - if (orig) - ppOrigFunc = orig; - - if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK) - { - if (MH_EnableHook(addr) == MH_OK) - { - spdlog::info("Enabling hook {}", pFuncName); - return true; - } - else - spdlog::error("MH_EnableHook failed for function {}", pFuncName); - } - else - spdlog::error("MH_CreateHook failed for function {}", pFuncName); - - return false; -} - -uintptr_t ParseDLLOffsetString(const char* pAddrString) -{ - // in the format server.dll + 0xDEADBEEF - int iDllNameEnd = 0; - for (; !isspace(pAddrString[iDllNameEnd]) && pAddrString[iDllNameEnd] != '+'; iDllNameEnd++) - ; - - char* pModuleName = new char[iDllNameEnd + 1]; - memcpy(pModuleName, pAddrString, iDllNameEnd); - pModuleName[iDllNameEnd] = '\0'; - - // get the module address - const HMODULE pModuleAddr = GetModuleHandleA(pModuleName); - - if (!pModuleAddr) - return 0; - - // get the offset string - uintptr_t iOffset = 0; - - int iOffsetBegin = iDllNameEnd; - int iOffsetEnd = strlen(pAddrString); - - // seek until we hit the start of the number offset - for (; !(pAddrString[iOffsetBegin] >= '0' && pAddrString[iOffsetBegin] <= '9') && pAddrString[iOffsetBegin]; iOffsetBegin++) - ; - - bool bIsHex = pAddrString[iOffsetBegin] == '0' && (pAddrString[iOffsetBegin + 1] == 'X' || pAddrString[iOffsetBegin + 1] == 'x'); - if (bIsHex) - iOffset = std::stoi(pAddrString + iOffsetBegin + 2, 0, 16); - else - iOffset = std::stoi(pAddrString + iOffsetBegin); - - return ((uintptr_t)pModuleAddr + iOffset); -} - -// dll load callback stuff -// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load -struct DllLoadCallback -{ - std::string dll; - DllLoadCallbackFuncType callback; - std::string tag; - std::vector<std::string> reliesOn; - bool called; -}; - -// HACK: declaring and initialising this vector at file scope crashes on debug builds due to static initialisation order -// using a static var like this ensures that the vector is initialised lazily when it's used -std::vector<DllLoadCallback>& GetDllLoadCallbacks() -{ - static std::vector<DllLoadCallback> vec = std::vector<DllLoadCallback>(); - return vec; -} - -void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> reliesOn) -{ - DllLoadCallback& callbackStruct = GetDllLoadCallbacks().emplace_back(); - - callbackStruct.dll = dll; - callbackStruct.callback = callback; - callbackStruct.tag = tag; - callbackStruct.reliesOn = reliesOn; - callbackStruct.called = false; -} - -void AddDllLoadCallbackForDedicatedServer( - std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> reliesOn) -{ - if (!IsDedicatedServer()) - return; - - AddDllLoadCallback(dll, callback, tag, reliesOn); -} - -void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> reliesOn) -{ - if (IsDedicatedServer()) - return; - - AddDllLoadCallback(dll, callback, tag, reliesOn); -} - -void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName) -{ - char* pStrippedFuncName = (char*)pFuncName; - // strip & char from funcname - if (*pStrippedFuncName == '&') - pStrippedFuncName++; - - if (MH_CreateHook(pTarget, pDetour, (LPVOID*)ppOriginal) == MH_OK) - { - if (MH_EnableHook(pTarget) == MH_OK) - spdlog::info("Enabling hook {}", pStrippedFuncName); - else - spdlog::error("MH_EnableHook failed for function {}", pStrippedFuncName); - } - else - spdlog::error("MH_CreateHook failed for function {}", pStrippedFuncName); -} - -AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, (LPVOID)GetCommandLineA, LPSTR, WINAPI, ()) -{ - static char* cmdlineModified; - static char* cmdlineOrg; - - if (cmdlineOrg == nullptr || cmdlineModified == nullptr) - { - cmdlineOrg = _GetCommandLineA(); - bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument - bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs"); - - std::string args; - std::ifstream cmdlineArgFile; - - // it looks like CommandLine() prioritizes parameters apprearing first, so we want the real commandline to take priority - // not to mention that cmdlineOrg starts with the EXE path - args.append(cmdlineOrg); - args.append(" "); - - // append those from the file - - if (!ignoreStartupArgs) - { - - cmdlineArgFile = std::ifstream(!isDedi ? "ns_startup_args.txt" : "ns_startup_args_dedi.txt"); - - if (cmdlineArgFile) - { - std::stringstream argBuffer; - argBuffer << cmdlineArgFile.rdbuf(); - cmdlineArgFile.close(); - - // if some other command line option includes "-northstar" in the future then you have to refactor this check to check with - // both either space after or ending with - if (!isDedi && argBuffer.str().find("-northstar") != std::string::npos) - MessageBoxA( - NULL, - "The \"-northstar\" command line option is NOT supposed to go into ns_startup_args.txt file!\n\nThis option is " - "supposed to go into Origin/Steam game launch options, and then you are supposed to launch the original " - "Titanfall2.exe " - "rather than NorthstarLauncher.exe to make use of it.", - "Northstar Warning", - MB_ICONWARNING); - - args.append(argBuffer.str()); - } - } - - auto len = args.length(); - cmdlineModified = new char[len + 1]; - if (!cmdlineModified) - { - spdlog::error("malloc failed for command line"); - return cmdlineOrg; - } - memcpy(cmdlineModified, args.c_str(), len + 1); - } - - return cmdlineModified; -} - -std::vector<std::string> calledTags; -void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress) -{ - CModule cModule(moduleAddress); - - while (true) - { - bool bDoneCalling = true; - - for (auto& callbackStruct : GetDllLoadCallbacks()) - { - if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename()) - { - bool bShouldContinue = false; - - if (!callbackStruct.reliesOn.empty()) - { - for (std::string tag : callbackStruct.reliesOn) - { - if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end()) - { - bDoneCalling = false; - bShouldContinue = true; - break; - } - } - } - - if (bShouldContinue) - continue; - - callbackStruct.callback(moduleAddress); - calledTags.push_back(callbackStruct.tag); - callbackStruct.called = true; - } - } - - if (bDoneCalling) - break; - } -} - -void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress) -{ - CModule cModule(moduleAddress); - - while (true) - { - bool bDoneCalling = true; - - for (auto& callbackStruct : GetDllLoadCallbacks()) - { - if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename()) - { - bool bShouldContinue = false; - - if (!callbackStruct.reliesOn.empty()) - { - for (std::string tag : callbackStruct.reliesOn) - { - if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end()) - { - bDoneCalling = false; - bShouldContinue = true; - break; - } - } - } - - if (bShouldContinue) - continue; - - callbackStruct.callback(moduleAddress); - calledTags.push_back(callbackStruct.tag); - callbackStruct.called = true; - } - } - - if (bDoneCalling) - break; - } -} - -void CallAllPendingDLLLoadCallbacks() -{ - HMODULE hMods[1024]; - HANDLE hProcess = GetCurrentProcess(); - DWORD cbNeeded; - unsigned int i; - - // Get a list of all the modules in this process. - if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) - { - for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) - { - wchar_t szModName[MAX_PATH]; - - // Get the full path to the module's file. - if (GetModuleFileNameExW(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR))) - { - CallLoadLibraryWCallbacks(szModName, hMods[i]); - } - } - } -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, (LPVOID)LoadLibraryExA, -HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) -// clang-format on -{ - HMODULE moduleAddress; - - LPCSTR lpLibFileNameEnd = lpLibFileName + strlen(lpLibFileName); - LPCSTR lpLibName = lpLibFileNameEnd - strlen(XINPUT1_3_DLL); - - // replace xinput dll with one that has ASLR - if (lpLibFileName <= lpLibName && !strncmp(lpLibName, XINPUT1_3_DLL, strlen(XINPUT1_3_DLL) + 1)) - { - moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags); - - if (!moduleAddress) - { - MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR); - exit(EXIT_FAILURE); - - return nullptr; - } - } - else - moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags); - - if (moduleAddress) - { - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress); - } - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, (LPVOID)LoadLibraryA, -HMODULE, WINAPI, (LPCSTR lpLibFileName)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryA(lpLibFileName); - - if (moduleAddress) - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, (LPVOID)LoadLibraryExW, -HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags); - - if (moduleAddress) - CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, (LPVOID)LoadLibraryW, -HMODULE, WINAPI, (LPCWSTR lpLibFileName)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryW(lpLibFileName); - - if (moduleAddress) - { - CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress); - } - - return moduleAddress; -} - -void InstallInitialHooks() -{ - if (MH_Initialize() != MH_OK) - spdlog::error("MH_Initialize (minhook initialization) failed"); - - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/core/hooks.h b/NorthstarDLL/core/hooks.h deleted file mode 100644 index 15edbf0b..00000000 --- a/NorthstarDLL/core/hooks.h +++ /dev/null @@ -1,331 +0,0 @@ -#pragma once -#include "memory.h" - -#include <string> -#include <iostream> - -void InstallInitialHooks(); - -typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress); -void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {}); -void AddDllLoadCallbackForDedicatedServer( - std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {}); -void AddDllLoadCallbackForClient( - std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {}); - -void CallAllPendingDLLLoadCallbacks(); - -// new dll load callback stuff -enum class eDllLoadCallbackSide -{ - UNSIDED, - CLIENT, - DEDICATED_SERVER -}; - -class __dllLoadCallback -{ -public: - __dllLoadCallback() = delete; - __dllLoadCallback( - eDllLoadCallbackSide side, - const std::string dllName, - DllLoadCallbackFuncType callback, - std::string uniqueStr, - std::string reliesOn); -}; - -#define __CONCAT3(x, y, z) x##y##z -#define CONCAT3(x, y, z) __CONCAT3(x, y, z) -#define __CONCAT2(x, y) x##y -#define CONCAT2(x, y) __CONCAT2(x, y) -#define __STR(s) #s - -// adds a callback to be called when a given dll is loaded, for creating hooks and such -#define __ON_DLL_LOAD(dllName, side, uniquestr, reliesOn, args) \ - void CONCAT2(__dllLoadCallback, uniquestr) args; \ - namespace \ - { \ - __dllLoadCallback CONCAT2(__dllLoadCallbackInstance, __LINE__)( \ - side, dllName, CONCAT2(__dllLoadCallback, uniquestr), __STR(uniquestr), reliesOn); \ - } \ - void CONCAT2(__dllLoadCallback, uniquestr) args - -#define ON_DLL_LOAD(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, "", args) -#define ON_DLL_LOAD_RELIESON(dllName, uniquestr, reliesOn, args) \ - __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, __STR(reliesOn), args) -#define ON_DLL_LOAD_CLIENT(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, "", args) -#define ON_DLL_LOAD_CLIENT_RELIESON(dllName, uniquestr, reliesOn, args) \ - __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, __STR(reliesOn), args) -#define ON_DLL_LOAD_DEDI(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, "", args) -#define ON_DLL_LOAD_DEDI_RELIESON(dllName, uniquestr, reliesOn, args) \ - __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, __STR(reliesOn), args) - -// new macro hook stuff -class __autohook; -class __autovar; - -class __fileAutohook -{ -public: - std::vector<__autohook*> hooks; - std::vector<__autovar*> vars; - - void Dispatch(); - void DispatchForModule(const char* pModuleName); -}; - -uintptr_t ParseDLLOffsetString(const char* pAddrString); - -// initialise autohooks for this file -#define AUTOHOOK_INIT() \ - namespace \ - { \ - __fileAutohook __FILEAUTOHOOK; \ - } - -// dispatch all autohooks in this file -#define AUTOHOOK_DISPATCH() __FILEAUTOHOOK.Dispatch(); - -#define AUTOHOOK_DISPATCH_MODULE(moduleName) __FILEAUTOHOOK.DispatchForModule(__STR(moduleName)); - -class __autohook -{ -public: - enum AddressResolutionMode - { - OFFSET_STRING, // we're using a string that of the format dllname.dll + offset - ABSOLUTE_ADDR, // we're using an absolute address, we don't need to process it at all - PROCADDRESS // resolve using GetModuleHandle and GetProcAddress - }; - - char* pFuncName; - - LPVOID pHookFunc; - LPVOID* ppOrigFunc; - - // address resolution props - AddressResolutionMode iAddressResolutionMode; - char* pAddrString = nullptr; // for OFFSET_STRING - LPVOID iAbsoluteAddress = nullptr; // for ABSOLUTE_ADDR - char* pModuleName; // for PROCADDRESS - char* pProcName; // for PROCADDRESS - -public: - __autohook() = delete; - - __autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress) - { - iAddressResolutionMode = ABSOLUTE_ADDR; - - const int iFuncNameStrlen = strlen(funcName) + 1; - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); - - autohook->hooks.push_back(this); - } - - __autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig) - { - iAddressResolutionMode = OFFSET_STRING; - - const int iFuncNameStrlen = strlen(funcName) + 1; - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); - - const int iAddrStrlen = strlen(addrString) + 1; - pAddrString = new char[iAddrStrlen]; - memcpy(pAddrString, addrString, iAddrStrlen); - - autohook->hooks.push_back(this); - } - - __autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig) - { - iAddressResolutionMode = PROCADDRESS; - - const int iFuncNameStrlen = strlen(funcName) + 1; - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); - - const int iModuleNameStrlen = strlen(moduleName) + 1; - pModuleName = new char[iModuleNameStrlen]; - memcpy(pModuleName, moduleName, iModuleNameStrlen); - - const int iProcNameStrlen = strlen(procName) + 1; - pProcName = new char[iProcNameStrlen]; - memcpy(pProcName, procName, iProcNameStrlen); - - autohook->hooks.push_back(this); - } - - ~__autohook() - { - delete[] pFuncName; - - if (pAddrString) - delete[] pAddrString; - - if (pModuleName) - delete[] pModuleName; - - if (pProcName) - delete[] pProcName; - } - - void Dispatch() - { - LPVOID targetAddr = nullptr; - - // determine the address of the function we're hooking - switch (iAddressResolutionMode) - { - case ABSOLUTE_ADDR: - { - targetAddr = iAbsoluteAddress; - break; - } - - case OFFSET_STRING: - { - targetAddr = (LPVOID)ParseDLLOffsetString(pAddrString); - break; - } - - case PROCADDRESS: - { - targetAddr = (LPVOID)GetProcAddress(GetModuleHandleA(pModuleName), pProcName); - break; - } - } - - if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK) - { - if (MH_EnableHook(targetAddr) == MH_OK) - spdlog::info("Enabling hook {}", pFuncName); - else - spdlog::error("MH_EnableHook failed for function {}", pFuncName); - } - else - spdlog::error("MH_CreateHook failed for function {}", pFuncName); - } -}; - -// hook a function at a given offset from a dll to be dispatched with AUTOHOOK_DISPATCH() -#define AUTOHOOK(name, addrString, type, callingConvention, args) \ - type callingConvention CONCAT2(__autohookfunc, name) args; \ - namespace \ - { \ - type(*name) args; \ - __autohook CONCAT2(__autohook, __LINE__)( \ - &__FILEAUTOHOOK, __STR(name), __STR(addrString), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ - } \ - type callingConvention CONCAT2(__autohookfunc, name) args - -// hook a function at a given absolute constant address to be dispatched with AUTOHOOK_DISPATCH() -#define AUTOHOOK_ABSOLUTEADDR(name, addr, type, callingConvention, args) \ - type callingConvention CONCAT2(__autohookfunc, name) args; \ - namespace \ - { \ - type(*name) args; \ - __autohook \ - CONCAT2(__autohook, __LINE__)(&__FILEAUTOHOOK, __STR(name), addr, (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ - } \ - type callingConvention CONCAT2(__autohookfunc, name) args - -// hook a function at a given module and exported function to be dispatched with AUTOHOOK_DISPATCH() -#define AUTOHOOK_PROCADDRESS(name, moduleName, procName, type, callingConvention, args) \ - type callingConvention CONCAT2(__autohookfunc, name) args; \ - namespace \ - { \ - type(*name) args; \ - __autohook CONCAT2(__autohook, __LINE__)( \ - &__FILEAUTOHOOK, __STR(name), __STR(moduleName), __STR(procName), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ - } \ - type callingConvention CONCAT2(__autohookfunc, name) \ - args - -class ManualHook -{ -public: - char* pFuncName; - - LPVOID pHookFunc; - LPVOID* ppOrigFunc; - -public: - ManualHook() = delete; - ManualHook(const char* funcName, LPVOID func); - ManualHook(const char* funcName, LPVOID* orig, LPVOID func); - bool Dispatch(LPVOID addr, LPVOID* orig = nullptr); -}; - -// hook a function to be dispatched manually later -#define HOOK(varName, originalFunc, type, callingConvention, args) \ - namespace \ - { \ - type(*originalFunc) args; \ - } \ - type callingConvention CONCAT2(__manualhookfunc, varName) args; \ - ManualHook varName = ManualHook(__STR(varName), (LPVOID*)&originalFunc, (LPVOID)CONCAT2(__manualhookfunc, varName)); \ - type callingConvention CONCAT2(__manualhookfunc, varName) args - -#define HOOK_NOORIG(varName, type, callingConvention, args) \ - type callingConvention CONCAT2(__manualhookfunc, varName) args; \ - ManualHook varName = ManualHook(__STR(varName), (LPVOID)CONCAT2(__manualhookfunc, varName)); \ - type callingConvention CONCAT2(__manualhookfunc, varName) \ - args - -void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName = ""); -#define MAKEHOOK(pTarget, pDetour, ppOriginal) MakeHook((LPVOID)pTarget, (LPVOID)pDetour, (void*)ppOriginal, __STR(pDetour)) - -class __autovar -{ -public: - char* m_pAddrString; - void** m_pTarget; - -public: - __autovar(__fileAutohook* pAutohook, const char* pAddrString, void** pTarget) - { - m_pTarget = pTarget; - - const int iAddrStrlen = strlen(pAddrString) + 1; - m_pAddrString = new char[iAddrStrlen]; - memcpy(m_pAddrString, pAddrString, iAddrStrlen); - - pAutohook->vars.push_back(this); - } - - void Dispatch() - { - *m_pTarget = (void*)ParseDLLOffsetString(m_pAddrString); - } -}; - -// VAR_AT(engine.dll+0x404, ConVar*, Cvar_host_timescale) -#define VAR_AT(addrString, type, name) \ - type name; \ - namespace \ - { \ - __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \ - } - -// FUNCTION_AT(engine.dll + 0xDEADBEEF, void, __fastcall, SomeFunc, (void* a1)) -#define FUNCTION_AT(addrString, type, callingConvention, name, args) \ - type(*name) args; \ - namespace \ - { \ - __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \ - } - -// int* g_pSomeInt; -// DEFINED_VAR_AT(engine.dll + 0x5005, g_pSomeInt) -#define DEFINED_VAR_AT(addrString, name) \ - namespace \ - { \ - __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \ - } diff --git a/NorthstarDLL/core/macros.h b/NorthstarDLL/core/macros.h deleted file mode 100644 index ae944cca..00000000 --- a/NorthstarDLL/core/macros.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -template <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args) -{ - return (*reinterpret_cast<ReturnType(__fastcall***)(void*, Args...)>(thisPtr))[index](thisPtr, args...); -} - -template <typename T, size_t index, typename... Args> constexpr T CallVFunc_Alt(void* classBase, Args... args) noexcept -{ - return ((*(T(__thiscall***)(void*, Args...))(classBase))[index])(classBase, args...); -} - -#define STR_HASH(s) (std::hash<std::string>()(s)) - -// Example usage: M_VMETHOD(int, GetEntityIndex, 8, (CBaseEntity* ent), (this, ent)) -#define M_VMETHOD(returnType, name, index, args, argsRaw) \ - FORCEINLINE returnType name args noexcept \ - { \ - return CallVFunc_Alt<returnType, index> argsRaw; \ - } diff --git a/NorthstarDLL/core/math/bitbuf.h b/NorthstarDLL/core/math/bitbuf.h deleted file mode 100644 index 5ca75455..00000000 --- a/NorthstarDLL/core/math/bitbuf.h +++ /dev/null @@ -1,1148 +0,0 @@ -#pragma once - -#define INLINE inline - -#define BITS_PER_INT 32 - -INLINE int GetBitForBitnum(int bitNum) -{ - static int bitsForBitnum[] = { - (1 << 0), (1 << 1), (1 << 2), (1 << 3), (1 << 4), (1 << 5), (1 << 6), (1 << 7), (1 << 8), (1 << 9), (1 << 10), - (1 << 11), (1 << 12), (1 << 13), (1 << 14), (1 << 15), (1 << 16), (1 << 17), (1 << 18), (1 << 19), (1 << 20), (1 << 21), - (1 << 22), (1 << 23), (1 << 24), (1 << 25), (1 << 26), (1 << 27), (1 << 28), (1 << 29), (1 << 30), (1 << 31), - }; - - return bitsForBitnum[(bitNum) & (BITS_PER_INT - 1)]; -} - -#undef BITS_PER_INT - -using u8 = uint8_t; -using u16 = uint16_t; -using u32 = uint32_t; -using u64 = uint64_t; -using uptr = uintptr_t; - -using i8 = int8_t; -using i16 = int16_t; -using i32 = int32_t; -using i64 = int64_t; -using iptr = intptr_t; - -// Endianess, don't use on PPC64 nor ARM64BE -#define LittleDWord(val) (val) - -static INLINE void StoreLittleDWord(u32* base, size_t dwordIndex, u32 dword) -{ - base[dwordIndex] = LittleDWord(dword); -} - -static INLINE u32 LoadLittleDWord(u32* base, size_t dwordIndex) -{ - return LittleDWord(base[dwordIndex]); -} - -#include <algorithm> - -static inline const u32 s_nMaskTable[33] = { - 0, - (1 << 1) - 1, - (1 << 2) - 1, - (1 << 3) - 1, - (1 << 4) - 1, - (1 << 5) - 1, - (1 << 6) - 1, - (1 << 7) - 1, - (1 << 8) - 1, - (1 << 9) - 1, - (1 << 10) - 1, - (1 << 11) - 1, - (1 << 12) - 1, - (1 << 13) - 1, - (1 << 14) - 1, - (1 << 15) - 1, - (1 << 16) - 1, - (1 << 17) - 1, - (1 << 18) - 1, - (1 << 19) - 1, - (1 << 20) - 1, - (1 << 21) - 1, - (1 << 22) - 1, - (1 << 23) - 1, - (1 << 24) - 1, - (1 << 25) - 1, - (1 << 26) - 1, - (1 << 27) - 1, - (1 << 28) - 1, - (1 << 29) - 1, - (1 << 30) - 1, - 0x7fffffff, - 0xffffffff, -}; - -enum EBitCoordType -{ - kCW_None, - kCW_LowPrecision, - kCW_Integral -}; - -class BitBufferBase -{ -protected: - INLINE void SetName(const char* name) - { - m_BufferName = name; - } - -public: - INLINE bool IsOverflowed() - { - return m_Overflow; - } - INLINE void SetOverflowed() - { - m_Overflow = true; - } - - INLINE const char* GetName() - { - return m_BufferName; - } - -private: - const char* m_BufferName = ""; - -protected: - u8 m_Overflow = false; -}; - -class BFRead : public BitBufferBase -{ -public: - BFRead() = default; - - INLINE BFRead(uptr data, size_t byteLength, size_t startPos = 0, const char* bufferName = 0) - { - StartReading(data, byteLength, startPos); - - if (bufferName) - SetName(bufferName); - } - -public: - INLINE void StartReading(uptr data, size_t byteLength, size_t startPos = 0) - { - m_Data = reinterpret_cast<u32 const*>(data); - m_DataIn = m_Data; - - m_DataBytes = byteLength; - m_DataBits = byteLength << 3; - - m_DataEnd = reinterpret_cast<u32 const*>(reinterpret_cast<u8 const*>(m_Data) + m_DataBytes); - - Seek(startPos); - } - - INLINE void GrabNextDWord(bool overflow = false) - { - if (m_Data == m_DataEnd) - { - m_CachedBitsLeft = 1; - m_CachedBufWord = 0; - - m_DataIn++; - - if (overflow) - SetOverflowed(); - } - else - { - if (m_DataIn > m_DataEnd) - { - SetOverflowed(); - m_CachedBufWord = 0; - } - else - { - m_CachedBufWord = LittleDWord(*(m_DataIn++)); - } - } - } - - INLINE void FetchNext() - { - m_CachedBitsLeft = 32; - GrabNextDWord(false); - } - - INLINE i32 ReadOneBit() - { - i32 ret = m_CachedBufWord & 1; - - if (--m_CachedBitsLeft == 0) - FetchNext(); - else - m_CachedBufWord >>= 1; - - return ret; - } - - INLINE u32 ReadUBitLong(i32 numBits) - { - if (m_CachedBitsLeft >= numBits) - { - u32 ret = m_CachedBufWord & s_nMaskTable[numBits]; - - m_CachedBitsLeft -= numBits; - - if (m_CachedBitsLeft) - m_CachedBufWord >>= numBits; - else - FetchNext(); - - return ret; - } - else - { - // need to merge words - u32 ret = m_CachedBufWord; - numBits -= m_CachedBitsLeft; - - GrabNextDWord(true); - - if (IsOverflowed()) - return 0; - - ret |= ((m_CachedBufWord & s_nMaskTable[numBits]) << m_CachedBitsLeft); - - m_CachedBitsLeft = 32 - numBits; - m_CachedBufWord >>= numBits; - - return ret; - } - } - - INLINE i32 ReadSBitLong(int numBits) - { - i32 ret = ReadUBitLong(numBits); - return (ret << (32 - numBits)) >> (32 - numBits); - } - - INLINE u32 ReadUBitVar() - { - u32 ret = ReadUBitLong(6); - - switch (ret & (16 | 32)) - { - case 16: - ret = (ret & 15) | (ReadUBitLong(4) << 4); - // Assert(ret >= 16); - break; - case 32: - ret = (ret & 15) | (ReadUBitLong(8) << 4); - // Assert(ret >= 256); - break; - case 48: - ret = (ret & 15) | (ReadUBitLong(32 - 4) << 4); - // Assert(ret >= 4096); - break; - } - - return ret; - } - - INLINE u32 PeekUBitLong(i32 numBits) - { - i32 nSaveBA = m_CachedBitsLeft; - i32 nSaveW = m_CachedBufWord; - u32 const* pSaveP = m_DataIn; - u32 nRet = ReadUBitLong(numBits); - - m_CachedBitsLeft = nSaveBA; - m_CachedBufWord = nSaveW; - m_DataIn = pSaveP; - - return nRet; - } - - INLINE float ReadBitFloat() - { - u32 value = ReadUBitLong(32); - return *reinterpret_cast<float*>(&value); - } - - /*INLINE float ReadBitCoord() { - i32 intval = 0, fractval = 0, signbit = 0; - float value = 0.0; - - // Read the required integer and fraction flags - intval = ReadOneBit(); - fractval = ReadOneBit(); - - // If we got either parse them, otherwise it's a zero. - if (intval || fractval) { - // Read the sign bit - signbit = ReadOneBit(); - - // If there's an integer, read it in - if (intval) { - // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE] - intval = ReadUBitLong(COORD_INTEGER_BITS) + 1; - } - - // If there's a fraction, read it in - if (fractval) { - fractval = ReadUBitLong(COORD_FRACTIONAL_BITS); - } - - // Calculate the correct floating point value - value = intval + ((float)fractval * COORD_RESOLUTION); - - // Fixup the sign if negative. - if (signbit) - value = -value; - } - - return value; - } - - INLINE float ReadBitCoordMP() { - i32 intval = 0, fractval = 0, signbit = 0; - float value = 0.0; - - bool inBounds = ReadOneBit() ? true : false; - - // Read the required integer and fraction flags - intval = ReadOneBit(); - - // If we got either parse them, otherwise it's a zero. - if (intval) { - // Read the sign bit - signbit = ReadOneBit(); - - // If there's an integer, read it in - // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE] - if (inBounds) - value = ReadUBitLong(COORD_INTEGER_BITS_MP) + 1; - else - value = ReadUBitLong(COORD_INTEGER_BITS) + 1; - } - - // Fixup the sign if negative. - if (signbit) - value = -value; - - return value; - } - - INLINE float ReadBitCellCoord(int bits, EBitCoordType coordType) { - bool bIntegral = (coordType == kCW_Integral); - bool bLowPrecision = (coordType == kCW_LowPrecision); - - int intval = 0, fractval = 0; - float value = 0.0; - - if (bIntegral) - value = ReadUBitLong(bits); - else { - intval = ReadUBitLong(bits); - - // If there's a fraction, read it in - fractval = ReadUBitLong(bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS); - - // Calculate the correct floating point value - value = intval + ((float)fractval * (bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION)); - } - - return value; - } - - INLINE float ReadBitNormal() { - // Read the sign bit - i32 signbit = ReadOneBit(); - - // Read the fractional part - u32 fractval = ReadUBitLong(NORMAL_FRACTIONAL_BITS); - - // Calculate the correct floating point value - float value = (float)fractval * NORMAL_RESOLUTION; - - // Fixup the sign if negative. - if (signbit) - value = -value; - - return value; - } - - INLINE void ReadBitVec3Coord(Vector& fa) { - i32 xflag, yflag, zflag; - - // This vector must be initialized! Otherwise, If any of the flags aren't set, - // the corresponding component will not be read and will be stack garbage. - fa.Init(0, 0, 0); - - xflag = ReadOneBit(); - yflag = ReadOneBit(); - zflag = ReadOneBit(); - - if (xflag) - fa[0] = ReadBitCoord(); - if (yflag) - fa[1] = ReadBitCoord(); - if (zflag) - fa[2] = ReadBitCoord(); - } - - INLINE void ReadBitVec3Normal(Vector& fa) { - i32 xflag = ReadOneBit(); - i32 yflag = ReadOneBit(); - - if (xflag) - fa[0] = ReadBitNormal(); - else - fa[0] = 0.0f; - - if (yflag) - fa[1] = ReadBitNormal(); - else - fa[1] = 0.0f; - - // The first two imply the third (but not its sign) - i32 znegative = ReadOneBit(); - - float fafafbfb = fa[0] * fa[0] + fa[1] * fa[1]; - if (fafafbfb < 1.0f) - fa[2] = sqrt(1.0f - fafafbfb); - else - fa[2] = 0.0f; - - if (znegative) - fa[2] = -fa[2]; - } - - INLINE void ReadBitAngles(QAngle& fa) { - Vector tmp; - ReadBitVec3Coord(tmp); - fa.Init(tmp.x, tmp.y, tmp.z); - }*/ - - INLINE float ReadBitAngle(int numBits) - { - float shift = (float)(GetBitForBitnum(numBits)); - - i32 i = ReadUBitLong(numBits); - float fReturn = (float)i * (360.0 / shift); - - return fReturn; - } - - INLINE i32 ReadChar() - { - return ReadSBitLong(sizeof(char) << 3); - } - INLINE u32 ReadByte() - { - return ReadUBitLong(sizeof(unsigned char) << 3); - } - - INLINE i32 ReadShort() - { - return ReadSBitLong(sizeof(short) << 3); - } - INLINE u32 ReadWord() - { - return ReadUBitLong(sizeof(unsigned short) << 3); - } - - INLINE i32 ReadLong() - { - return (i32)(ReadUBitLong(sizeof(i32) << 3)); - } - INLINE float ReadFloat() - { - u32 temp = ReadUBitLong(sizeof(float) << 3); - return *reinterpret_cast<float*>(&temp); - } - - INLINE u32 ReadVarInt32() - { - constexpr int kMaxVarint32Bytes = 5; - - u32 result = 0; - int count = 0; - u32 b; - - do - { - if (count == kMaxVarint32Bytes) - return result; - - b = ReadUBitLong(8); - result |= (b & 0x7F) << (7 * count); - ++count; - } while (b & 0x80); - - return result; - } - - INLINE u64 ReadVarInt64() - { - constexpr int kMaxVarintBytes = 10; - - u64 result = 0; - int count = 0; - u64 b; - - do - { - if (count == kMaxVarintBytes) - return result; - - b = ReadUBitLong(8); - result |= static_cast<u64>(b & 0x7F) << (7 * count); - ++count; - } while (b & 0x80); - - return result; - } - - INLINE void ReadBits(uptr outData, u32 bitLength) - { - u8* out = reinterpret_cast<u8*>(outData); - int bitsLeft = bitLength; - - // align output to dword boundary - while (((uptr)out & 3) != 0 && bitsLeft >= 8) - { - *out = (unsigned char)ReadUBitLong(8); - ++out; - bitsLeft -= 8; - } - - // read dwords - while (bitsLeft >= 32) - { - *((u32*)out) = ReadUBitLong(32); - out += sizeof(u32); - bitsLeft -= 32; - } - - // read remaining bytes - while (bitsLeft >= 8) - { - *out = ReadUBitLong(8); - ++out; - bitsLeft -= 8; - } - - // read remaining bits - if (bitsLeft) - *out = ReadUBitLong(bitsLeft); - } - - INLINE bool ReadBytes(uptr outData, u32 byteLength) - { - ReadBits(outData, byteLength << 3); - return !IsOverflowed(); - } - - INLINE bool ReadString(char* str, i32 maxLength, bool stopAtLineTermination = false, i32* outNumChars = 0) - { - bool tooSmall = false; - int iChar = 0; - - while (1) - { - char val = ReadChar(); - - if (val == 0) - break; - else if (stopAtLineTermination && val == '\n') - break; - - if (iChar < (maxLength - 1)) - { - str[iChar] = val; - ++iChar; - } - else - { - tooSmall = true; - } - } - - // Make sure it's null-terminated. - // Assert(iChar < maxLength); - str[iChar] = 0; - - if (outNumChars) - *outNumChars = iChar; - - return !IsOverflowed() && !tooSmall; - } - - INLINE char* ReadAndAllocateString(bool* hasOverflowed = 0) - { - char str[2048]; - - int chars = 0; - bool overflowed = !ReadString(str, sizeof(str), false, &chars); - - if (hasOverflowed) - *hasOverflowed = overflowed; - - // Now copy into the output and return it; - char* ret = new char[chars + 1]; - for (u32 i = 0; i <= chars; i++) - ret[i] = str[i]; - - return ret; - } - - INLINE i64 ReadLongLong() - { - i64 retval; - u32* longs = (u32*)&retval; - - // Read the two DWORDs according to network endian - const short endianIndex = 0x0100; - u8* idx = (u8*)&endianIndex; - - longs[*idx++] = ReadUBitLong(sizeof(i32) << 3); - longs[*idx] = ReadUBitLong(sizeof(i32) << 3); - - return retval; - } - - INLINE bool Seek(size_t startPos) - { - bool bSucc = true; - - if (startPos < 0 || startPos > m_DataBits) - { - SetOverflowed(); - bSucc = false; - startPos = m_DataBits; - } - - // non-multiple-of-4 bytes at head of buffer. We put the "round off" - // at the head to make reading and detecting the end efficient. - int nHead = m_DataBytes & 3; - - int posBytes = startPos / 8; - if ((m_DataBytes < 4) || (nHead && (posBytes < nHead))) - { - // partial first dword - u8 const* partial = (u8 const*)m_Data; - - if (m_Data) - { - m_CachedBufWord = *(partial++); - if (nHead > 1) - m_CachedBufWord |= (*partial++) << 8; - if (nHead > 2) - m_CachedBufWord |= (*partial++) << 16; - } - - m_DataIn = (u32 const*)partial; - - m_CachedBufWord >>= (startPos & 31); - m_CachedBitsLeft = (nHead << 3) - (startPos & 31); - } - else - { - int adjustedPos = startPos - (nHead << 3); - - m_DataIn = reinterpret_cast<u32 const*>(reinterpret_cast<u8 const*>(m_Data) + ((adjustedPos / 32) << 2) + nHead); - - if (m_Data) - { - m_CachedBitsLeft = 32; - GrabNextDWord(); - } - else - { - m_CachedBufWord = 0; - m_CachedBitsLeft = 1; - } - - m_CachedBufWord >>= (adjustedPos & 31); - m_CachedBitsLeft = std::min(m_CachedBitsLeft, u32(32 - (adjustedPos & 31))); // in case grabnextdword overflowed - } - - return bSucc; - } - - INLINE size_t GetNumBitsRead() - { - if (!m_Data) - return 0; - - size_t nCurOfs = size_t(((iptr(m_DataIn) - iptr(m_Data)) / 4) - 1); - nCurOfs *= 32; - nCurOfs += (32 - m_CachedBitsLeft); - - size_t nAdjust = 8 * (m_DataBytes & 3); - return std::min(nCurOfs + nAdjust, m_DataBits); - } - - INLINE bool SeekRelative(size_t offset) - { - return Seek(GetNumBitsRead() + offset); - } - - INLINE size_t TotalBytesAvailable() - { - return m_DataBytes; - } - - INLINE size_t GetNumBitsLeft() - { - return m_DataBits - GetNumBitsRead(); - } - INLINE size_t GetNumBytesLeft() - { - return GetNumBitsLeft() >> 3; - } - -private: - size_t m_DataBits; // 0x0010 - size_t m_DataBytes; // 0x0018 - - u32 m_CachedBufWord; // 0x0020 - u32 m_CachedBitsLeft; // 0x0024 - - const u32* m_DataIn; // 0x0028 - const u32* m_DataEnd; // 0x0030 - const u32* m_Data; // 0x0038 -}; - -class BFWrite : public BitBufferBase -{ -public: - BFWrite() = default; - - INLINE BFWrite(uptr data, size_t byteLength, const char* bufferName = 0) - { - StartWriting(data, byteLength); - - if (bufferName) - SetName(bufferName); - } - -public: - INLINE void StartWriting(uptr data, size_t byteLength) - { - m_Data = reinterpret_cast<u32*>(data); - m_DataOut = m_Data; - - m_DataBytes = byteLength; - m_DataBits = byteLength << 3; - - m_DataEnd = reinterpret_cast<u32*>(reinterpret_cast<u8*>(m_Data) + m_DataBytes); - } - - INLINE int GetNumBitsLeft() - { - return m_OutBitsLeft + (32 * (m_DataEnd - m_DataOut - 1)); - } - - INLINE void Reset() - { - m_Overflow = false; - m_OutBufWord = 0; - m_OutBitsLeft = 32; - m_DataOut = m_Data; - } - - INLINE void TempFlush() - { - if (m_OutBitsLeft != 32) - { - if (m_DataOut == m_DataEnd) - SetOverflowed(); - else - StoreLittleDWord(m_DataOut, 0, LoadLittleDWord(m_DataOut, 0) & ~s_nMaskTable[32 - m_OutBitsLeft] | m_OutBufWord); - } - - m_Flushed = true; - } - - INLINE u8* GetBasePointer() - { - TempFlush(); - return reinterpret_cast<u8*>(m_Data); - } - - INLINE u8* GetData() - { - return GetBasePointer(); - } - - INLINE void Finish() - { - if (m_OutBitsLeft != 32) - { - if (m_DataOut == m_DataEnd) - SetOverflowed(); - - StoreLittleDWord(m_DataOut, 0, m_OutBufWord); - } - } - - INLINE void FlushNoCheck() - { - StoreLittleDWord(m_DataOut++, 0, m_OutBufWord); - - m_OutBitsLeft = 32; - m_OutBufWord = 0; - } - - INLINE void Flush() - { - if (m_DataOut == m_DataEnd) - SetOverflowed(); - else - StoreLittleDWord(m_DataOut++, 0, m_OutBufWord); - - m_OutBitsLeft = 32; - m_OutBufWord = 0; - } - - INLINE void WriteOneBitNoCheck(i32 value) - { - m_OutBufWord |= (value & 1) << (32 - m_OutBitsLeft); - - if (--m_OutBitsLeft == 0) - FlushNoCheck(); - } - - INLINE void WriteOneBit(i32 value) - { - m_OutBufWord |= (value & 1) << (32 - m_OutBitsLeft); - - if (--m_OutBitsLeft == 0) - Flush(); - } - - INLINE void WriteUBitLong(u32 data, i32 numBits, bool checkRange = true) - { - if (numBits <= m_OutBitsLeft) - { - if (checkRange) - m_OutBufWord |= (data) << (32 - m_OutBitsLeft); - else - m_OutBufWord |= (data & s_nMaskTable[numBits]) << (32 - m_OutBitsLeft); - - m_OutBitsLeft -= numBits; - - if (m_OutBitsLeft == 0) - Flush(); - } - else - { - // split dwords case - i32 overflowBits = (numBits - m_OutBitsLeft); - m_OutBufWord |= (data & s_nMaskTable[m_OutBitsLeft]) << (32 - m_OutBitsLeft); - Flush(); - m_OutBufWord = (data >> (numBits - overflowBits)); - m_OutBitsLeft = 32 - overflowBits; - } - } - - INLINE void WriteSBitLong(i32 data, i32 numBits) - { - WriteUBitLong((u32)data, numBits, false); - } - - INLINE void WriteUBitVar(u32 n) - { - if (n < 16) - WriteUBitLong(n, 6); - else if (n < 256) - WriteUBitLong((n & 15) | 16 | ((n & (128 | 64 | 32 | 16)) << 2), 10); - else if (n < 4096) - WriteUBitLong((n & 15) | 32 | ((n & (2048 | 1024 | 512 | 256 | 128 | 64 | 32 | 16)) << 2), 14); - else - { - WriteUBitLong((n & 15) | 48, 6); - WriteUBitLong((n >> 4), 32 - 4); - } - } - - INLINE void WriteBitFloat(float value) - { - auto temp = &value; - WriteUBitLong(*reinterpret_cast<u32*>(temp), 32); - } - - INLINE void WriteFloat(float value) - { - auto temp = &value; - WriteUBitLong(*reinterpret_cast<u32*>(temp), 32); - } - - INLINE bool WriteBits(const uptr data, i32 numBits) - { - u8* out = (u8*)data; - i32 numBitsLeft = numBits; - - // Bounds checking.. - if ((GetNumBitsWritten() + numBits) > m_DataBits) - { - SetOverflowed(); - return false; - } - - // !! speed!! need fast paths - // write remaining bytes - while (numBitsLeft >= 8) - { - WriteUBitLong(*out, 8, false); - ++out; - numBitsLeft -= 8; - } - - // write remaining bits - if (numBitsLeft) - WriteUBitLong(*out, numBitsLeft, false); - - return !IsOverflowed(); - } - - INLINE bool WriteBytes(const uptr data, i32 numBytes) - { - return WriteBits(data, numBytes << 3); - } - - INLINE i32 GetNumBitsWritten() - { - return (32 - m_OutBitsLeft) + (32 * (m_DataOut - m_Data)); - } - - INLINE i32 GetNumBytesWritten() - { - return (GetNumBitsWritten() + 7) >> 3; - } - - INLINE void WriteChar(i32 val) - { - WriteSBitLong(val, sizeof(char) << 3); - } - - INLINE void WriteByte(i32 val) - { - WriteUBitLong(val, sizeof(unsigned char) << 3, false); - } - - INLINE void WriteShort(i32 val) - { - WriteSBitLong(val, sizeof(short) << 3); - } - - INLINE void WriteWord(i32 val) - { - WriteUBitLong(val, sizeof(unsigned short) << 3); - } - - INLINE bool WriteString(const char* str) - { - if (str) - while (*str) - WriteChar(*(str++)); - - WriteChar(0); - - return !IsOverflowed(); - } - - INLINE void WriteLongLong(i64 val) - { - u32* pLongs = (u32*)&val; - - // Insert the two DWORDS according to network endian - const short endianIndex = 0x0100; - u8* idx = (u8*)&endianIndex; - - WriteUBitLong(pLongs[*idx++], sizeof(i32) << 3); - WriteUBitLong(pLongs[*idx], sizeof(i32) << 3); - } - - /*INLINE void WriteBitCoord(const float f) { - i32 signbit = (f <= -COORD_RESOLUTION); - i32 intval = (i32)abs(f); - i32 fractval = abs((i32)(f * COORD_DENOMINATOR)) & (COORD_DENOMINATOR - 1); - - // Send the bit flags that indicate whether we have an integer part and/or a fraction part. - WriteOneBit(intval); - WriteOneBit(fractval); - - if (intval || fractval) { - // Send the sign bit - WriteOneBit(signbit); - - // Send the integer if we have one. - if (intval) { - // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] - intval--; - WriteUBitLong((u32)intval, COORD_INTEGER_BITS); - } - - // Send the fraction if we have one - if (fractval) { - WriteUBitLong((u32)fractval, COORD_FRACTIONAL_BITS); - } - } - } - - INLINE void WriteBitCoordMP(const float f) { - i32 signbit = (f <= -COORD_RESOLUTION); - i32 intval = (i32)abs(f); - i32 fractval = (abs((i32)(f * COORD_DENOMINATOR)) & (COORD_DENOMINATOR - 1)); - - bool bInBounds = intval < (1 << COORD_INTEGER_BITS_MP); - - WriteOneBit(bInBounds); - - // Send the sign bit - WriteOneBit(intval); - - if (intval) { - WriteOneBit(signbit); - - // Send the integer if we have one. - // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] - intval--; - - if (bInBounds) - WriteUBitLong((u32)intval, COORD_INTEGER_BITS_MP); - else - WriteUBitLong((u32)intval, COORD_INTEGER_BITS); - } - } - - INLINE void WriteBitCellCoord(const float f, int bits, EBitCoordType coordType) { - bool bIntegral = (coordType == kCW_Integral); - bool bLowPrecision = (coordType == kCW_LowPrecision); - - i32 intval = (i32)abs(f); - i32 fractval = bLowPrecision ? (abs((i32)(f * COORD_DENOMINATOR_LOWPRECISION)) & (COORD_DENOMINATOR_LOWPRECISION - 1)) : - (abs((i32)(f * COORD_DENOMINATOR)) & (COORD_DENOMINATOR - 1)); - - if (bIntegral) - WriteUBitLong((u32)intval, bits); - else { - WriteUBitLong((u32)intval, bits); - WriteUBitLong((u32)fractval, bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS); - } - }*/ - - INLINE void SeekToBit(int bit) - { - TempFlush(); - - m_DataOut = m_Data + (bit / 32); - m_OutBufWord = LoadLittleDWord(m_DataOut, 0); - m_OutBitsLeft = 32 - (bit & 31); - } - - /*INLINE void WriteBitVec3Coord(const Vector& fa) { - i32 xflag, yflag, zflag; - - xflag = (fa[0] >= COORD_RESOLUTION) || (fa[0] <= -COORD_RESOLUTION); - yflag = (fa[1] >= COORD_RESOLUTION) || (fa[1] <= -COORD_RESOLUTION); - zflag = (fa[2] >= COORD_RESOLUTION) || (fa[2] <= -COORD_RESOLUTION); - - WriteOneBit(xflag); - WriteOneBit(yflag); - WriteOneBit(zflag); - - if (xflag) - WriteBitCoord(fa[0]); - if (yflag) - WriteBitCoord(fa[1]); - if (zflag) - WriteBitCoord(fa[2]); - } - - INLINE void WriteBitNormal(float f) { - i32 signbit = (f <= -NORMAL_RESOLUTION); - - // NOTE: Since +/-1 are valid values for a normal, I'm going to encode that as all ones - u32 fractval = abs((i32)(f * NORMAL_DENOMINATOR)); - - // clamp.. - if (fractval > NORMAL_DENOMINATOR) - fractval = NORMAL_DENOMINATOR; - - // Send the sign bit - WriteOneBit(signbit); - - // Send the fractional component - WriteUBitLong(fractval, NORMAL_FRACTIONAL_BITS); - } - - INLINE void WriteBitVec3Normal(const Vector& fa) { - i32 xflag, yflag; - - xflag = (fa[0] >= NORMAL_RESOLUTION) || (fa[0] <= -NORMAL_RESOLUTION); - yflag = (fa[1] >= NORMAL_RESOLUTION) || (fa[1] <= -NORMAL_RESOLUTION); - - WriteOneBit(xflag); - WriteOneBit(yflag); - - if (xflag) - WriteBitNormal(fa[0]); - if (yflag) - WriteBitNormal(fa[1]); - - // Write z sign bit - i32 signbit = (fa[2] <= -NORMAL_RESOLUTION); - WriteOneBit(signbit); - }*/ - - INLINE void WriteBitAngle(float angle, int numBits) - { - u32 shift = GetBitForBitnum(numBits); - u32 mask = shift - 1; - - i32 d = (i32)((angle / 360.0) * shift); - d &= mask; - - WriteUBitLong((u32)d, numBits); - } - - INLINE bool WriteBitsFromBuffer(BFRead* in, int numBits) - { - while (numBits > 32) - { - WriteUBitLong(in->ReadUBitLong(32), 32); - numBits -= 32; - } - - WriteUBitLong(in->ReadUBitLong(numBits), numBits); - return !IsOverflowed() && !in->IsOverflowed(); - } - - /*INLINE void WriteBitAngles(const QAngle& fa) { - // FIXME: - Vector tmp(fa.x, fa.y, fa.z); - WriteBitVec3Coord(tmp); - }*/ - -private: - size_t m_DataBits = 0; - size_t m_DataBytes = 0; - - u32 m_OutBufWord = 0; - u32 m_OutBitsLeft = 32; - - u32* m_DataOut = nullptr; - u32* m_DataEnd = nullptr; - u32* m_Data = nullptr; - - bool m_Flushed = false; // :flushed: -}; - -#undef INLINE diff --git a/NorthstarDLL/core/math/bits.cpp b/NorthstarDLL/core/math/bits.cpp deleted file mode 100644 index c879a45c..00000000 --- a/NorthstarDLL/core/math/bits.cpp +++ /dev/null @@ -1,45 +0,0 @@ -//=============================================================================// -// -// Purpose: look for NANs, infinities, and underflows. -// -//=============================================================================// - -#include "bits.h" - -//----------------------------------------------------------------------------- -// This follows the ANSI/IEEE 754-1985 standard -//----------------------------------------------------------------------------- -unsigned long& FloatBits(float& f) -{ - return *reinterpret_cast<unsigned long*>(&f); -} - -unsigned long const& FloatBits(float const& f) -{ - return *reinterpret_cast<unsigned long const*>(&f); -} - -float BitsToFloat(unsigned long i) -{ - return *reinterpret_cast<float*>(&i); -} - -bool IsFinite(float f) -{ - return ((FloatBits(f) & 0x7F800000) != 0x7F800000); -} - -unsigned long FloatAbsBits(float f) -{ - return FloatBits(f) & 0x7FFFFFFF; -} - -float FloatMakePositive(float f) -{ - return fabsf(f); -} - -float FloatNegate(float f) -{ - return -f; // BitsToFloat( FloatBits(f) ^ 0x80000000 ); -} diff --git a/NorthstarDLL/core/math/bits.h b/NorthstarDLL/core/math/bits.h deleted file mode 100644 index 0532a9bd..00000000 --- a/NorthstarDLL/core/math/bits.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -unsigned long& FloatBits(float& f); -unsigned long const& FloatBits(float const& f); -float BitsToFloat(unsigned long i); -bool IsFinite(float f); -unsigned long FloatAbsBits(float f); - -#define FLOAT32_NAN_BITS (std::uint32_t)0x7FC00000 // NaN! -#define FLOAT32_NAN BitsToFloat(FLOAT32_NAN_BITS) diff --git a/NorthstarDLL/core/math/color.cpp b/NorthstarDLL/core/math/color.cpp deleted file mode 100644 index 7b98043a..00000000 --- a/NorthstarDLL/core/math/color.cpp +++ /dev/null @@ -1,27 +0,0 @@ - -// clang-format off -namespace NS::Colors -{ - Color SCRIPT_UI (100, 255, 255); - Color SCRIPT_CL (100, 255, 100); - Color SCRIPT_SV (255, 100, 255); - Color NATIVE_UI (50 , 150, 150); - Color NATIVE_CL (50 , 150, 50 ); - Color NATIVE_SV (150, 50 , 150); - Color NATIVE_ENGINE (252, 133, 153); - Color FILESYSTEM (0 , 150, 150); - Color RPAK (255, 190, 0 ); - Color NORTHSTAR (66 , 72 , 128); - Color ECHO (150, 150, 159); - Color PLUGINSYS (244, 60 , 14); - Color PLUGIN (244, 106, 14); - - Color TRACE (0 , 255, 255); - Color DEBUG (0 , 255, 255); - Color INFO (16 , 160, 16 ); - Color WARN (255, 255, 0 ); - Color ERR (255, 50 , 50 ); - Color CRIT (255, 0 , 0 ); - Color OFF (0 , 0 , 0 ); -}; -// clang-format on diff --git a/NorthstarDLL/core/math/color.h b/NorthstarDLL/core/math/color.h deleted file mode 100644 index 013c4e4c..00000000 --- a/NorthstarDLL/core/math/color.h +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once - -struct color24 -{ - uint8_t r, g, b; -}; - -typedef struct color32_s -{ - bool operator!=(const struct color32_s& other) const - { - return r != other.r || g != other.g || b != other.b || a != other.a; - } - inline unsigned* asInt(void) - { - return reinterpret_cast<unsigned*>(this); - } - inline const unsigned* asInt(void) const - { - return reinterpret_cast<const unsigned*>(this); - } - inline void Copy(const color32_s& rhs) - { - *asInt() = *rhs.asInt(); - } - - uint8_t r, g, b, a; -} color32; - -struct SourceColor -{ - unsigned char R; - unsigned char G; - unsigned char B; - unsigned char A; - - SourceColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) - { - R = r; - G = g; - B = b; - A = a; - } - - SourceColor() - { - R = 0; - G = 0; - B = 0; - A = 0; - } -}; - -//----------------------------------------------------------------------------- -// Purpose: Basic handler for an rgb set of colors -// This class is fully inline -//----------------------------------------------------------------------------- -class Color -{ -public: - Color(int r, int g, int b, int a = 255) - { - _color[0] = (unsigned char)r; - _color[1] = (unsigned char)g; - _color[2] = (unsigned char)b; - _color[3] = (unsigned char)a; - } - void SetColor(int _r, int _g, int _b, int _a = 0) - { - _color[0] = (unsigned char)_r; - _color[1] = (unsigned char)_g; - _color[2] = (unsigned char)_b; - _color[3] = (unsigned char)_a; - } - void GetColor(int& _r, int& _g, int& _b, int& _a) const - { - _r = _color[0]; - _g = _color[1]; - _b = _color[2]; - _a = _color[3]; - } - int GetValue(int index) const - { - return _color[index]; - } - void SetRawColor(int color32) - { - *((int*)this) = color32; - } - int GetRawColor(void) const - { - return *((int*)this); - } - - inline int r() const - { - return _color[0]; - } - inline int g() const - { - return _color[1]; - } - inline int b() const - { - return _color[2]; - } - inline int a() const - { - return _color[3]; - } - - unsigned char& operator[](int index) - { - return _color[index]; - } - - const unsigned char& operator[](int index) const - { - return _color[index]; - } - - bool operator==(const Color& rhs) const - { - return (*((int*)this) == *((int*)&rhs)); - } - - bool operator!=(const Color& rhs) const - { - return !(operator==(rhs)); - } - - Color& operator=(const Color& rhs) - { - SetRawColor(rhs.GetRawColor()); - return *this; - } - - Color& operator=(const color32& rhs) - { - _color[0] = rhs.r; - _color[1] = rhs.g; - _color[2] = rhs.b; - _color[3] = rhs.a; - return *this; - } - - color32 ToColor32(void) const - { - color32 newColor {}; - newColor.r = _color[0]; - newColor.g = _color[1]; - newColor.b = _color[2]; - newColor.a = _color[3]; - return newColor; - } - - std::string ToANSIColor() - { - std::string out = "\033[38;2;"; - out += std::to_string(_color[0]) + ";"; - out += std::to_string(_color[1]) + ";"; - out += std::to_string(_color[2]) + ";"; - out += "49m"; - return out; - } - - SourceColor ToSourceColor() - { - return SourceColor(_color[0], _color[1], _color[2], _color[3]); - } - -private: - unsigned char _color[4]; -}; - -namespace NS::Colors -{ - extern Color SCRIPT_UI; - extern Color SCRIPT_CL; - extern Color SCRIPT_SV; - extern Color NATIVE_UI; - extern Color NATIVE_CL; - extern Color NATIVE_SV; - extern Color NATIVE_ENGINE; - extern Color FILESYSTEM; - extern Color RPAK; - extern Color NORTHSTAR; - extern Color ECHO; - extern Color PLUGINSYS; - extern Color PLUGIN; - - extern Color TRACE; - extern Color DEBUG; - extern Color INFO; - extern Color WARN; - extern Color ERR; - extern Color CRIT; - extern Color OFF; -}; // namespace NS::Colors diff --git a/NorthstarDLL/core/math/vector.h b/NorthstarDLL/core/math/vector.h deleted file mode 100644 index 8684908f..00000000 --- a/NorthstarDLL/core/math/vector.h +++ /dev/null @@ -1,47 +0,0 @@ -#include <cmath> - -#pragma once - -union Vector3 -{ - struct - { - float x; - float y; - float z; - }; - - float raw[3]; - - void MakeValid() - { - for (auto& fl : raw) - if (std::isnan(fl)) - fl = 0; - } - - // todo: more operators maybe - bool operator==(const Vector3& other) - { - return x == other.x && y == other.y && z == other.z; - } -}; - -union QAngle -{ - struct - { - float x; - float y; - float z; - float w; - }; - - float raw[4]; - - // todo: more operators maybe - bool operator==(const QAngle& other) - { - return x == other.x && y == other.y && z == other.z && w == other.w; - } -}; diff --git a/NorthstarDLL/core/memalloc.cpp b/NorthstarDLL/core/memalloc.cpp deleted file mode 100644 index 0a75bc2b..00000000 --- a/NorthstarDLL/core/memalloc.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "core/memalloc.h" -#include "core/tier0.h" - -// TODO: rename to malloc and free after removing statically compiled .libs - -extern "C" void* _malloc_base(size_t n) -{ - // allocate into static buffer if g_pMemAllocSingleton isn't initialised - if (!g_pMemAllocSingleton) - TryCreateGlobalMemAlloc(); - - return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n); -} - -/*extern "C" void* malloc(size_t n) -{ - return _malloc_base(n); -}*/ - -extern "C" void _free_base(void* p) -{ - if (!g_pMemAllocSingleton) - TryCreateGlobalMemAlloc(); - - g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p); -} - -extern "C" void* _realloc_base(void* oldPtr, size_t size) -{ - if (!g_pMemAllocSingleton) - TryCreateGlobalMemAlloc(); - - return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size); -} - -extern "C" void* _calloc_base(size_t n, size_t size) -{ - size_t bytes = n * size; - void* memory = _malloc_base(bytes); - if (memory) - { - memset(memory, 0, bytes); - } - return memory; -} - -extern "C" char* _strdup_base(const char* src) -{ - char* str; - char* p; - int len = 0; - - while (src[len]) - len++; - str = reinterpret_cast<char*>(_malloc_base(len + 1)); - p = str; - while (*src) - *p++ = *src++; - *p = '\0'; - return str; -} - -void* operator new(size_t n) -{ - return _malloc_base(n); -} - -void operator delete(void* p) noexcept -{ - _free_base(p); -} // /FORCE:MULTIPLE diff --git a/NorthstarDLL/core/memalloc.h b/NorthstarDLL/core/memalloc.h deleted file mode 100644 index 2f383335..00000000 --- a/NorthstarDLL/core/memalloc.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "rapidjson/document.h" -// #include "include/rapidjson/allocators.h" - -extern "C" void* _malloc_base(size_t size); -extern "C" void* _calloc_base(size_t const count, size_t const size); -extern "C" void* _realloc_base(void* block, size_t size); -extern "C" void* _recalloc_base(void* const block, size_t const count, size_t const size); -extern "C" void _free_base(void* const block); -extern "C" char* _strdup_base(const char* src); - -void* operator new(size_t n); -void operator delete(void* p) noexcept; - -// void* malloc(size_t n); - -class SourceAllocator -{ -public: - static const bool kNeedFree = true; - void* Malloc(size_t size) - { - if (size) // behavior of malloc(0) is implementation defined. - return _malloc_base(size); - else - return NULL; // standardize to returning NULL. - } - void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) - { - (void)originalSize; - if (newSize == 0) - { - _free_base(originalPtr); - return NULL; - } - return _realloc_base(originalPtr, newSize); - } - static void Free(void* ptr) - { - _free_base(ptr); - } -}; - -typedef rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::MemoryPoolAllocator<SourceAllocator>, SourceAllocator> rapidjson_document; -// typedef rapidjson::GenericDocument<rapidjson::UTF8<>, SourceAllocator, SourceAllocator> rapidjson_document; -// typedef rapidjson::Document rapidjson_document; -// using MyDocument = rapidjson::GenericDocument<rapidjson::UTF8<>, MemoryAllocator>; -// using rapidjson_document = rapidjson::GenericDocument<rapidjson::UTF8<>, SourceAllocator, SourceAllocator>; diff --git a/NorthstarDLL/core/memory.cpp b/NorthstarDLL/core/memory.cpp deleted file mode 100644 index 3770586f..00000000 --- a/NorthstarDLL/core/memory.cpp +++ /dev/null @@ -1,347 +0,0 @@ -#include "memory.h" - -CMemoryAddress::CMemoryAddress() : m_nAddress(0) {} -CMemoryAddress::CMemoryAddress(const uintptr_t nAddress) : m_nAddress(nAddress) {} -CMemoryAddress::CMemoryAddress(const void* pAddress) : m_nAddress(reinterpret_cast<uintptr_t>(pAddress)) {} - -// operators -CMemoryAddress::operator uintptr_t() const -{ - return m_nAddress; -} - -CMemoryAddress::operator void*() const -{ - return reinterpret_cast<void*>(m_nAddress); -} - -CMemoryAddress::operator bool() const -{ - return m_nAddress != 0; -} - -bool CMemoryAddress::operator==(const CMemoryAddress& other) const -{ - return m_nAddress == other.m_nAddress; -} - -bool CMemoryAddress::operator!=(const CMemoryAddress& other) const -{ - return m_nAddress != other.m_nAddress; -} - -bool CMemoryAddress::operator==(const uintptr_t& addr) const -{ - return m_nAddress == addr; -} - -bool CMemoryAddress::operator!=(const uintptr_t& addr) const -{ - return m_nAddress != addr; -} - -CMemoryAddress CMemoryAddress::operator+(const CMemoryAddress& other) const -{ - return Offset(other.m_nAddress); -} - -CMemoryAddress CMemoryAddress::operator-(const CMemoryAddress& other) const -{ - return CMemoryAddress(m_nAddress - other.m_nAddress); -} - -CMemoryAddress CMemoryAddress::operator+(const uintptr_t& addr) const -{ - return Offset(addr); -} - -CMemoryAddress CMemoryAddress::operator-(const uintptr_t& addr) const -{ - return CMemoryAddress(m_nAddress - addr); -} - -CMemoryAddress CMemoryAddress::operator*() const -{ - return Deref(); -} - -// traversal -CMemoryAddress CMemoryAddress::Offset(const uintptr_t nOffset) const -{ - return CMemoryAddress(m_nAddress + nOffset); -} - -CMemoryAddress CMemoryAddress::Deref(const int nNumDerefs) const -{ - uintptr_t ret = m_nAddress; - for (int i = 0; i < nNumDerefs; i++) - ret = *reinterpret_cast<uintptr_t*>(ret); - - return CMemoryAddress(ret); -} - -// patching -void CMemoryAddress::Patch(const uint8_t* pBytes, const size_t nSize) -{ - if (nSize) - WriteProcessMemory(GetCurrentProcess(), reinterpret_cast<LPVOID>(m_nAddress), pBytes, nSize, NULL); -} - -void CMemoryAddress::Patch(const std::initializer_list<uint8_t> bytes) -{ - uint8_t* pBytes = new uint8_t[bytes.size()]; - - int i = 0; - for (const uint8_t& byte : bytes) - pBytes[i++] = byte; - - Patch(pBytes, bytes.size()); - delete[] pBytes; -} - -inline std::vector<uint8_t> HexBytesToString(const char* pHexString) -{ - std::vector<uint8_t> ret; - - int size = strlen(pHexString); - for (int i = 0; i < size; i++) - { - // If this is a space character, ignore it - if (isspace(pHexString[i])) - continue; - - if (i < size - 1) - { - BYTE result = 0; - for (int j = 0; j < 2; j++) - { - int val = 0; - char c = *(pHexString + i + j); - if (c >= 'a') - { - val = c - 'a' + 0xA; - } - else if (c >= 'A') - { - val = c - 'A' + 0xA; - } - else if (isdigit(c)) - { - val = c - '0'; - } - else - { - assert_msg(false, "Failed to parse invalid hex string."); - val = -1; - } - - result += (j == 0) ? val * 16 : val; - } - ret.push_back(result); - } - - i++; - } - - return ret; -} - -void CMemoryAddress::Patch(const char* pBytes) -{ - std::vector<uint8_t> vBytes = HexBytesToString(pBytes); - Patch(vBytes.data(), vBytes.size()); -} - -void CMemoryAddress::NOP(const size_t nSize) -{ - uint8_t* pBytes = new uint8_t[nSize]; - - memset(pBytes, 0x90, nSize); - Patch(pBytes, nSize); - - delete[] pBytes; -} - -bool CMemoryAddress::IsMemoryReadable(const size_t nSize) -{ - static SYSTEM_INFO sysInfo; - if (!sysInfo.dwPageSize) - GetSystemInfo(&sysInfo); - - MEMORY_BASIC_INFORMATION memInfo; - if (!VirtualQuery(reinterpret_cast<LPCVOID>(m_nAddress), &memInfo, sizeof(memInfo))) - return false; - - return memInfo.RegionSize >= nSize && memInfo.State & MEM_COMMIT && !(memInfo.Protect & PAGE_NOACCESS); -} - -CModule::CModule(const HMODULE pModule) -{ - MODULEINFO mInfo {0}; - - if (pModule && pModule != INVALID_HANDLE_VALUE) - GetModuleInformation(GetCurrentProcess(), pModule, &mInfo, sizeof(MODULEINFO)); - - m_nModuleSize = static_cast<size_t>(mInfo.SizeOfImage); - m_pModuleBase = reinterpret_cast<uintptr_t>(mInfo.lpBaseOfDll); - m_nAddress = m_pModuleBase; - - if (!m_nModuleSize || !m_pModuleBase) - return; - - m_pDOSHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(m_pModuleBase); - m_pNTHeaders = reinterpret_cast<IMAGE_NT_HEADERS64*>(m_pModuleBase + m_pDOSHeader->e_lfanew); - - const IMAGE_SECTION_HEADER* hSection = IMAGE_FIRST_SECTION(m_pNTHeaders); // Get first image section. - - for (WORD i = 0; i < m_pNTHeaders->FileHeader.NumberOfSections; i++) // Loop through the sections. - { - const IMAGE_SECTION_HEADER& hCurrentSection = hSection[i]; // Get current section. - - ModuleSections_t moduleSection = ModuleSections_t( - std::string(reinterpret_cast<const char*>(hCurrentSection.Name)), - static_cast<uintptr_t>(m_pModuleBase + hCurrentSection.VirtualAddress), - hCurrentSection.SizeOfRawData); - - if (!strcmp((const char*)hCurrentSection.Name, ".text")) - m_ExecutableCode = moduleSection; - else if (!strcmp((const char*)hCurrentSection.Name, ".pdata")) - m_ExceptionTable = moduleSection; - else if (!strcmp((const char*)hCurrentSection.Name, ".data")) - m_RunTimeData = moduleSection; - else if (!strcmp((const char*)hCurrentSection.Name, ".rdata")) - m_ReadOnlyData = moduleSection; - - m_vModuleSections.push_back(moduleSection); // Push back a struct with the section data. - } -} - -CModule::CModule(const char* pModuleName) : CModule(GetModuleHandleA(pModuleName)) {} - -CMemoryAddress CModule::GetExport(const char* pExportName) -{ - return CMemoryAddress(reinterpret_cast<uintptr_t>(GetProcAddress(reinterpret_cast<HMODULE>(m_nAddress), pExportName))); -} - -CMemoryAddress CModule::FindPattern(const uint8_t* pPattern, const char* pMask) -{ - if (!m_ExecutableCode.IsSectionValid()) - return CMemoryAddress(); - - uint64_t nBase = static_cast<uint64_t>(m_ExecutableCode.m_pSectionBase); - uint64_t nSize = static_cast<uint64_t>(m_ExecutableCode.m_nSectionSize); - - const uint8_t* pData = reinterpret_cast<uint8_t*>(nBase); - const uint8_t* pEnd = pData + static_cast<uint32_t>(nSize) - strlen(pMask); - - int nMasks[64]; // 64*16 = enough masks for 1024 bytes. - int iNumMasks = static_cast<int>(ceil(static_cast<float>(strlen(pMask)) / 16.f)); - - memset(nMasks, '\0', iNumMasks * sizeof(int)); - for (intptr_t i = 0; i < iNumMasks; ++i) - { - for (intptr_t j = strnlen(pMask + i * 16, 16) - 1; j >= 0; --j) - { - if (pMask[i * 16 + j] == 'x') - { - _bittestandset(reinterpret_cast<LONG*>(&nMasks[i]), j); - } - } - } - __m128i xmm1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pPattern)); - __m128i xmm2, xmm3, msks; - for (; pData != pEnd; _mm_prefetch(reinterpret_cast<const char*>(++pData + 64), _MM_HINT_NTA)) - { - if (pPattern[0] == pData[0]) - { - xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData)); - msks = _mm_cmpeq_epi8(xmm1, xmm2); - if ((_mm_movemask_epi8(msks) & nMasks[0]) == nMasks[0]) - { - for (uintptr_t i = 1; i < static_cast<uintptr_t>(iNumMasks); ++i) - { - xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>((pData + i * 16))); - xmm3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>((pPattern + i * 16))); - msks = _mm_cmpeq_epi8(xmm2, xmm3); - if ((_mm_movemask_epi8(msks) & nMasks[i]) == nMasks[i]) - { - if ((i + 1) == iNumMasks) - { - return CMemoryAddress(const_cast<uint8_t*>(pData)); - } - } - else - goto CONTINUE; - } - - return CMemoryAddress((&*(const_cast<uint8_t*>(pData)))); - } - } - - CONTINUE:; - } - - return CMemoryAddress(); -} - -inline std::pair<std::vector<uint8_t>, std::string> MaskedBytesFromPattern(const char* pPatternString) -{ - std::vector<uint8_t> vRet; - std::string sMask; - - int size = strlen(pPatternString); - for (int i = 0; i < size; i++) - { - // If this is a space character, ignore it - if (isspace(pPatternString[i])) - continue; - - if (pPatternString[i] == '?') - { - // Add a wildcard - vRet.push_back(0); - sMask.append("?"); - } - else if (i < size - 1) - { - BYTE result = 0; - for (int j = 0; j < 2; j++) - { - int val = 0; - char c = *(pPatternString + i + j); - if (c >= 'a') - { - val = c - 'a' + 0xA; - } - else if (c >= 'A') - { - val = c - 'A' + 0xA; - } - else if (isdigit(c)) - { - val = c - '0'; - } - else - { - assert_msg(false, "Failed to parse invalid pattern string."); - val = -1; - } - - result += (j == 0) ? val * 16 : val; - } - - vRet.push_back(result); - sMask.append("x"); - } - - i++; - } - - return std::make_pair(vRet, sMask); -} - -CMemoryAddress CModule::FindPattern(const char* pPattern) -{ - const auto pattern = MaskedBytesFromPattern(pPattern); - return FindPattern(pattern.first.data(), pattern.second.c_str()); -} diff --git a/NorthstarDLL/core/memory.h b/NorthstarDLL/core/memory.h deleted file mode 100644 index a978963e..00000000 --- a/NorthstarDLL/core/memory.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -class CMemoryAddress -{ -public: - uintptr_t m_nAddress; - -public: - CMemoryAddress(); - CMemoryAddress(const uintptr_t nAddress); - CMemoryAddress(const void* pAddress); - - // operators - operator uintptr_t() const; - operator void*() const; - operator bool() const; - - bool operator==(const CMemoryAddress& other) const; - bool operator!=(const CMemoryAddress& other) const; - bool operator==(const uintptr_t& addr) const; - bool operator!=(const uintptr_t& addr) const; - - CMemoryAddress operator+(const CMemoryAddress& other) const; - CMemoryAddress operator-(const CMemoryAddress& other) const; - CMemoryAddress operator+(const uintptr_t& other) const; - CMemoryAddress operator-(const uintptr_t& other) const; - CMemoryAddress operator*() const; - - template <typename T> T RCast() - { - return reinterpret_cast<T>(m_nAddress); - } - - // traversal - CMemoryAddress Offset(const uintptr_t nOffset) const; - CMemoryAddress Deref(const int nNumDerefs = 1) const; - - // patching - void Patch(const uint8_t* pBytes, const size_t nSize); - void Patch(const std::initializer_list<uint8_t> bytes); - void Patch(const char* pBytes); - void NOP(const size_t nSize); - - bool IsMemoryReadable(const size_t nSize); -}; - -// based on https://github.com/Mauler125/r5sdk/blob/master/r5dev/public/include/module.h -class CModule : public CMemoryAddress -{ -public: - struct ModuleSections_t - { - ModuleSections_t(void) = default; - ModuleSections_t(const std::string& svSectionName, uintptr_t pSectionBase, size_t nSectionSize) - : m_svSectionName(svSectionName), m_pSectionBase(pSectionBase), m_nSectionSize(nSectionSize) - { - } - - bool IsSectionValid(void) const - { - return m_nSectionSize != 0; - } - - std::string m_svSectionName; // Name of section. - uintptr_t m_pSectionBase {}; // Start address of section. - size_t m_nSectionSize {}; // Size of section. - }; - - ModuleSections_t m_ExecutableCode; - ModuleSections_t m_ExceptionTable; - ModuleSections_t m_RunTimeData; - ModuleSections_t m_ReadOnlyData; - -private: - std::string m_svModuleName; - uintptr_t m_pModuleBase {}; - DWORD m_nModuleSize {}; - IMAGE_NT_HEADERS64* m_pNTHeaders = nullptr; - IMAGE_DOS_HEADER* m_pDOSHeader = nullptr; - std::vector<ModuleSections_t> m_vModuleSections; - -public: - CModule() = delete; // no default, we need a module name - CModule(const HMODULE pModule); - CModule(const char* pModuleName); - - CMemoryAddress GetExport(const char* pExportName); - CMemoryAddress FindPattern(const uint8_t* pPattern, const char* pMask); - CMemoryAddress FindPattern(const char* pPattern); -}; diff --git a/NorthstarDLL/core/sourceinterface.cpp b/NorthstarDLL/core/sourceinterface.cpp deleted file mode 100644 index 5a72beb0..00000000 --- a/NorthstarDLL/core/sourceinterface.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "sourceinterface.h" -#include "logging/sourceconsole.h" - -AUTOHOOK_INIT() - -// really wanted to do a modular callback system here but honestly couldn't be bothered so hardcoding stuff for now: todo later - -// clang-format off -AUTOHOOK_PROCADDRESS(ClientCreateInterface, client.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on -{ - void* ret = ClientCreateInterface(pName, pReturnCode); - spdlog::info("CreateInterface CLIENT {}", pName); - - if (!strcmp(pName, "GameClientExports001")) - InitialiseConsoleOnInterfaceCreation(); - - return ret; -} - -// clang-format off -AUTOHOOK_PROCADDRESS(ServerCreateInterface, server.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on -{ - void* ret = ServerCreateInterface(pName, pReturnCode); - spdlog::info("CreateInterface SERVER {}", pName); - - return ret; -} - -// clang-format off -AUTOHOOK_PROCADDRESS(EngineCreateInterface, engine.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on -{ - void* ret = EngineCreateInterface(pName, pReturnCode); - spdlog::info("CreateInterface ENGINE {}", pName); - - return ret; -} - -// clang-format off -ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(client.dll)} -ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(server.dll)} -ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(engine.dll)} -// clang-format on diff --git a/NorthstarDLL/core/sourceinterface.h b/NorthstarDLL/core/sourceinterface.h deleted file mode 100644 index 7b5e81f3..00000000 --- a/NorthstarDLL/core/sourceinterface.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include <string> - -// literally just copied from ttf2sdk definition -typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode); - -template <typename T> class SourceInterface -{ -private: - T* m_interface; - -public: - SourceInterface(const std::string& moduleName, const std::string& interfaceName) - { - HMODULE handle = GetModuleHandleA(moduleName.c_str()); - CreateInterfaceFn createInterface = (CreateInterfaceFn)GetProcAddress(handle, "CreateInterface"); - m_interface = (T*)createInterface(interfaceName.c_str(), NULL); - if (m_interface == nullptr) - spdlog::error("Failed to call CreateInterface for %s in %s", interfaceName, moduleName); - } - - T* operator->() const - { - return m_interface; - } - - operator T*() const - { - return m_interface; - } -}; diff --git a/NorthstarDLL/core/structs.h b/NorthstarDLL/core/structs.h deleted file mode 100644 index 037233a6..00000000 --- a/NorthstarDLL/core/structs.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once -//clang-format off -// About this file: -// This file contains several macros used to define reversed structs -// The reason we use these macros is to make it easier to update existing structs -// when new fields are reversed -// This means we dont have to manually add padding, and recalculate when updating - -// Technical note: -// While functionally, these structs act like a regular struct, they are actually -// defined as unions with anonymous structs in them. -// This means that each field is essentially an offset into a union. -// We acknowledge that this goes against C++'s active-member guideline for unions -// However, this is not such a big deal here as these structs will not be constructed - -// Usage: -// To use these macros, define a struct like so: -/* -OFFSET_STRUCT(Name) -{ - STRUCT_SIZE(0x100) // Total in-memory struct size - FIELD(0x0, int field) // offset, signature -} -*/ - -#define OFFSET_STRUCT(name) union name -#define STRUCT_SIZE(size) char __size[size]; -#define STRUCT_FIELD_OFFSET(offset, signature) \ - struct \ - { \ - char CONCAT2(pad, __LINE__)[offset]; \ - signature; \ - }; - -// Special case for a 0-offset field -#define STRUCT_FIELD_NOOFFSET(offset, signature) signature; - -// Just puts two tokens next to each other, but -// allows us to force the preprocessor to do another pass -#define FX(f, x) f x - -// Macro used to detect if the given offset is 0 or not -#define TEST_0 , -// MSVC does no preprocessing of integer literals. -// On other compilers `0x0` gets processed into `0` -#define TEST_0x0 , - -// Concats the first and third argument and drops everything else -// Used with preprocessor expansion in later passes to move the third argument to the fourth and change the value -#define ZERO_P_I(a, b, c, ...) a##c - -// We use FX to prepare to use ZERO_P_I. -// The right block contains 3 arguments: -// NIF_ -// CONCAT2(TEST_, offset) -// 1 -// -// If offset is not 0 (or 0x0) the preprocessor replaces -// it with nothing and the third argument stays 1 -// -// If the offset is 0, TEST_0 expands to , and 1 becomes the fourth argument -// -// With those arguments we call ZERO_P_I and the first and third arugment get concat. -// We either end up with: -// NIF_ (if offset is 0) or -// NIF_1 (if offset is not 0) -#define IF_ZERO(m) FX(ZERO_P_I, (NIF_, CONCAT2(TEST_, m), 1)) - -// These macros are used to branch after we processed if the offset is zero or not -#define NIF_(t, ...) t -#define NIF_1(t, ...) __VA_ARGS__ - -// FIELD(S), generates an anonymous struct when a non 0 offset is given, otherwise just a signature -#define FIELD(offset, signature) IF_ZERO(offset)(STRUCT_FIELD_NOOFFSET, STRUCT_FIELD_OFFSET)(offset, signature) -#define FIELDS FIELD - -//clang-format on diff --git a/NorthstarDLL/core/tier0.cpp b/NorthstarDLL/core/tier0.cpp deleted file mode 100644 index 1f59722c..00000000 --- a/NorthstarDLL/core/tier0.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "tier0.h" - -IMemAlloc* g_pMemAllocSingleton = nullptr; - -CommandLineType CommandLine; -Plat_FloatTimeType Plat_FloatTime; -ThreadInServerFrameThreadType ThreadInServerFrameThread; - -typedef IMemAlloc* (*CreateGlobalMemAllocType)(); -CreateGlobalMemAllocType CreateGlobalMemAlloc; - -// needs to be a seperate function, since memalloc.cpp calls it -void TryCreateGlobalMemAlloc() -{ - // init memalloc stuff - CreateGlobalMemAlloc = - reinterpret_cast<CreateGlobalMemAllocType>(GetProcAddress(GetModuleHandleA("tier0.dll"), "CreateGlobalMemAlloc")); - g_pMemAllocSingleton = CreateGlobalMemAlloc(); // if it already exists, this returns the preexisting IMemAlloc instance -} - -ON_DLL_LOAD("tier0.dll", Tier0GameFuncs, (CModule module)) -{ - // shouldn't be necessary, but do this just in case - TryCreateGlobalMemAlloc(); - - // setup tier0 funcs - CommandLine = module.GetExport("CommandLine").RCast<CommandLineType>(); - Plat_FloatTime = module.GetExport("Plat_FloatTime").RCast<Plat_FloatTimeType>(); - ThreadInServerFrameThread = module.GetExport("ThreadInServerFrameThread").RCast<ThreadInServerFrameThreadType>(); -} diff --git a/NorthstarDLL/core/tier0.h b/NorthstarDLL/core/tier0.h deleted file mode 100644 index cc9af39e..00000000 --- a/NorthstarDLL/core/tier0.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -class IMemAlloc -{ -public: - struct VTable - { - void* unknown[1]; // alloc debug - void* (*Alloc)(IMemAlloc* memAlloc, size_t nSize); - void* unknown2[1]; // realloc debug - void* (*Realloc)(IMemAlloc* memAlloc, void* pMem, size_t nSize); - void* unknown3[1]; // free #1 - void (*Free)(IMemAlloc* memAlloc, void* pMem); - void* unknown4[2]; // nullsubs, maybe CrtSetDbgFlag - size_t (*GetSize)(IMemAlloc* memAlloc, void* pMem); - void* unknown5[9]; // they all do literally nothing - void (*DumpStats)(IMemAlloc* memAlloc); - void (*DumpStatsFileBase)(IMemAlloc* memAlloc, const char* pchFileBase); - void* unknown6[4]; - int (*heapchk)(IMemAlloc* memAlloc); - }; - - VTable* m_vtable; -}; - -class CCommandLine -{ -public: - // based on the defs in the 2013 source sdk, but for some reason has an extra function (may be another CreateCmdLine overload?) - // these seem to line up with what they should be though - virtual void CreateCmdLine(const char* commandline) = 0; - virtual void CreateCmdLine(int argc, char** argv) = 0; - virtual void unknown() = 0; - virtual const char* GetCmdLine(void) const = 0; - - virtual const char* CheckParm(const char* psz, const char** ppszValue = 0) const = 0; - virtual void RemoveParm() const = 0; - virtual void AppendParm(const char* pszParm, const char* pszValues) = 0; - - virtual const char* ParmValue(const char* psz, const char* pDefaultVal = 0) const = 0; - virtual int ParmValue(const char* psz, int nDefaultVal) const = 0; - virtual float ParmValue(const char* psz, float flDefaultVal) const = 0; - - virtual int ParmCount() const = 0; - virtual int FindParm(const char* psz) const = 0; - virtual const char* GetParm(int nIndex) const = 0; - virtual void SetParm(int nIndex, char const* pParm) = 0; - - // virtual const char** GetParms() const {} -}; - -extern IMemAlloc* g_pMemAllocSingleton; - -typedef CCommandLine* (*CommandLineType)(); -extern CommandLineType CommandLine; - -typedef double (*Plat_FloatTimeType)(); -extern Plat_FloatTimeType Plat_FloatTime; - -typedef bool (*ThreadInServerFrameThreadType)(); -extern ThreadInServerFrameThreadType ThreadInServerFrameThread; - -void TryCreateGlobalMemAlloc(); diff --git a/NorthstarDLL/core/vanilla.h b/NorthstarDLL/core/vanilla.h deleted file mode 100644 index b0797803..00000000 --- a/NorthstarDLL/core/vanilla.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/// Determines if we are in vanilla-compatibility mode. -/// In this mode we shouldn't auth with Atlas, which prevents users from joining a -/// non-trusted server. This means that we can unrestrict client/server commands -/// as well as various other small changes for compatibility -class VanillaCompatibility -{ -public: - void SetVanillaCompatibility(bool isVanilla) - { - static bool bInitialised = false; - if (bInitialised) - return; - - bInitialised = true; - m_bIsVanillaCompatible = isVanilla; - } - - bool GetVanillaCompatibility() - { - return m_bIsVanillaCompatible; - } - -private: - bool m_bIsVanillaCompatible = false; -}; - -inline VanillaCompatibility* g_pVanillaCompatibility; diff --git a/NorthstarDLL/dedicated/dedicated.cpp b/NorthstarDLL/dedicated/dedicated.cpp deleted file mode 100644 index df6e5787..00000000 --- a/NorthstarDLL/dedicated/dedicated.cpp +++ /dev/null @@ -1,296 +0,0 @@ -#include "dedicated.h" -#include "dedicatedlogtoclient.h" -#include "core/tier0.h" -#include "shared/playlist.h" -#include "engine/r2engine.h" -#include "engine/hoststate.h" -#include "server/auth/serverauthentication.h" -#include "masterserver/masterserver.h" -#include "util/printcommands.h" - -AUTOHOOK_INIT() - -bool IsDedicatedServer() -{ - static bool result = strstr(GetCommandLineA(), "-dedicated"); - return result; -} - -// CDedidcatedExports defs -struct CDedicatedExports; // forward declare - -typedef void (*DedicatedSys_PrintfType)(CDedicatedExports* dedicated, const char* msg); -typedef void (*DedicatedRunServerType)(CDedicatedExports* dedicated); - -// would've liked to just do this as a class but have not been able to get it to work -struct CDedicatedExports -{ - void* vtable; // because it's easier, we just set this to &this, since CDedicatedExports has no props we care about other than funcs - - char unused[56]; - - DedicatedSys_PrintfType Sys_Printf; - DedicatedRunServerType RunServer; -}; - -void Sys_Printf(CDedicatedExports* dedicated, const char* msg) -{ - spdlog::info("[DEDICATED SERVER] {}", msg); -} - -void RunServer(CDedicatedExports* dedicated) -{ - spdlog::info("CDedicatedExports::RunServer(): starting"); - spdlog::info(CommandLine()->GetCmdLine()); - - // initialise engine - g_pEngine->Frame(); - - // add +map if no map loading command is present - // don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present - if (!CommandLine()->CheckParm("+map") && !CommandLine()->CheckParm("+launchplaylist")) - CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString()); - - // re-run commandline - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - - // main loop - double frameTitle = 0; - while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING) - { - double frameStart = Plat_FloatTime(); - g_pEngine->Frame(); - - std::this_thread::sleep_for( - std::chrono::duration<double, std::ratio<1>>(g_pGlobals->m_flTickInterval - fmin(Plat_FloatTime() - frameStart, 0.25))); - } -} - -// use server presence to update window title -class DedicatedConsoleServerPresence : public ServerPresenceReporter -{ - void ReportPresence(const ServerPresence* pServerPresence) override - { - SetConsoleTitleA(fmt::format( - "{} - {} {}/{} players ({})", - pServerPresence->m_sServerName, - pServerPresence->m_MapName, - pServerPresence->m_iPlayerCount, - pServerPresence->m_iMaxPlayers, - pServerPresence->m_PlaylistName) - .c_str()); - } -}; - -HANDLE consoleInputThreadHandle = NULL; -DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter) -{ - while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN) - Sleep(1000); - - // Bind stdin to receive console input. - FILE* fp = nullptr; - freopen_s(&fp, "CONIN$", "r", stdin); - - spdlog::info("Ready to receive console commands."); - - { - // Process console input - std::string input; - while (g_pEngine && g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING && std::getline(std::cin, input)) - { - input += "\n"; - Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode); - TryPrintCvarHelpForCommand(input.c_str()); // this needs to be done on main thread, unstable in this one - } - } - - return 0; -} - -// clang-format off -AUTOHOOK(IsGameActiveWindow, engine.dll + 0x1CDC80, -bool,, ()) -// clang-format on -{ - return true; -} - -ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModule module)) -{ - spdlog::info("InitialiseDedicated"); - - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - // Host_Init - // prevent a particle init that relies on client dll - module.Offset(0x156799).NOP(5); - - // Host_Init - // don't call Key_Init to avoid loading some extra rsons from rpak (will be necessary to boot if we ever wanna disable rpaks entirely on - // dedi) - module.Offset(0x1565B0).NOP(5); - - { - // CModAppSystemGroup::Create - // force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment - CMemoryAddress base = module.Offset(0x1C4EBD); - - // cmp => mov - base.Offset(1).Patch("C6 87"); - - // 00 => 01 - base.Offset(7).Patch("01"); - } - - // Some init that i'm not sure of that crashes - // nop the call to it - module.Offset(0x156A63).NOP(5); - - // runframeserver - // nop some access violations - module.Offset(0x159819).NOP(17); - - module.Offset(0x156B4C).NOP(7); - - // previously patched these, took me a couple weeks to figure out they were the issue - // removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address - // so uhh, don't do that - // NSMem::NOP(ea + 0x156B4C + 7, 8); - module.Offset(0x156B4C).Offset(15).NOP(9); - - // HostState_State_NewGame - // nop an access violation - module.Offset(0xB934C).NOP(9); - - // CEngineAPI::Connect - // remove call to Shader_Connect - module.Offset(0x1C4D7D).NOP(5); - - // Host_Init - // remove call to ui loading stuff - module.Offset(0x156595).NOP(5); - - // some function that gets called from RunFrameServer - // nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway - module.Offset(0x15A0BB).NOP(5); - - // RunFrameServer - // nop a function that access violations - module.Offset(0x159BF3).NOP(5); - - // func that checks if origin is inited - // always return 1 - module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret - - // HostState_State_ChangeLevel - // nop clientinterface call - module.Offset(0x1552ED).NOP(16); - - // HostState_State_ChangeLevel - // nop clientinterface call - module.Offset(0x155363).NOP(16); - - // IVideoMode::CreateGameWindow - // nop call to ShowWindow - module.Offset(0x1CD146).NOP(5); - - CDedicatedExports* dedicatedExports = new CDedicatedExports; - dedicatedExports->vtable = dedicatedExports; - dedicatedExports->Sys_Printf = Sys_Printf; - dedicatedExports->RunServer = RunServer; - - *module.Offset(0x13F0B668).RCast<CDedicatedExports**>() = dedicatedExports; - - // extra potential patches: - // nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow - // also look into launcher.dll+d381, seems to cause renderthread to get made - // this crashes HARD if no window which makes sense tbh - // also look into materialsystem + 5B344 since it seems to be the base of all the renderthread stuff - - // big note: datatable gets registered in window creation - // make sure it still gets registered - - // add cmdline args that are good for dedi - CommandLine()->AppendParm("-nomenuvid", 0); - CommandLine()->AppendParm("-nosound", 0); - CommandLine()->AppendParm("-windowed", 0); - CommandLine()->AppendParm("-nomessagebox", 0); - CommandLine()->AppendParm("+host_preload_shaders", "0"); - CommandLine()->AppendParm("+net_usesocketsforloopback", "1"); - CommandLine()->AppendParm("+community_frame_run", "0"); - - // use presence reporter for console title - DedicatedConsoleServerPresence* presenceReporter = new DedicatedConsoleServerPresence; - g_pServerPresence->AddPresenceReporter(presenceReporter); - - // setup dedicated printing to client - RegisterCustomSink(std::make_shared<DedicatedServerLogToClientSink>()); - - // Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something. - if (!CommandLine()->CheckParm("-bringbackquickedit")) - { - HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE); - DWORD mode = 0; - - if (GetConsoleMode(stdIn, &mode)) - { - if (mode & ENABLE_QUICK_EDIT_MODE) - { - mode &= ~ENABLE_QUICK_EDIT_MODE; - mode &= ~ENABLE_MOUSE_INPUT; - - mode |= ENABLE_PROCESSED_INPUT; - - SetConsoleMode(stdIn, mode); - } - } - } - else - spdlog::info("Quick Edit enabled by user request"); - - // create console input thread - if (!CommandLine()->CheckParm("-noconsoleinput")) - consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL); - else - spdlog::info("Console input disabled by user request"); -} - -ON_DLL_LOAD_DEDI("tier0.dll", DedicatedServerOrigin, (CModule module)) -{ - // disable origin on dedicated - // for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without - // an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server - module.GetExport("Tier0_InitOrigin").Patch("C3"); -} - -// clang-format off -AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0, -void, __fastcall, (void* sqvm)) -// clang-format on -{ - PrintSquirrelError(sqvm); - - // close dedicated server if a fatal error is hit - // atm, this will crash if not aborted, so this just closes more gracefully - static ConVar* Cvar_fatal_script_errors = g_pCVar->FindVar("fatal_script_errors"); - if (Cvar_fatal_script_errors->GetBool()) - { - NS::log::FlushLoggers(); - abort(); - } -} - -ON_DLL_LOAD_DEDI("server.dll", DedicatedServerGameDLL, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(server.dll) - - if (CommandLine()->CheckParm("-nopakdedi")) - { - module.Offset(0x6BA350).Patch("C3"); // dont load skins.rson from rpak if we don't have rpaks, as loading it will cause a crash - module.Offset(0x6BA300).Patch( - "B8 C8 00 00 00 C3"); // return 200 as the number of skins from server.dll + 6BA300, this is the normal value read from - // skins.rson and should be updated when we need it more modular - } -} diff --git a/NorthstarDLL/dedicated/dedicated.h b/NorthstarDLL/dedicated/dedicated.h deleted file mode 100644 index 82806763..00000000 --- a/NorthstarDLL/dedicated/dedicated.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -bool IsDedicatedServer(); diff --git a/NorthstarDLL/dedicated/dedicatedlogtoclient.cpp b/NorthstarDLL/dedicated/dedicatedlogtoclient.cpp deleted file mode 100644 index bf2cf77a..00000000 --- a/NorthstarDLL/dedicated/dedicatedlogtoclient.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "dedicatedlogtoclient.h" -#include "engine/r2engine.h" - -void (*CGameClient__ClientPrintf)(CBaseClient* pClient, const char* fmt, ...); - -void DedicatedServerLogToClientSink::custom_sink_it_(const custom_log_msg& msg) -{ - if (*g_pServerState == server_state_t::ss_dead) - return; - - enum class eSendPrintsToClient - { - NONE = -1, - FIRST, - ALL - }; - - static const ConVar* Cvar_dedi_sendPrintsToClient = g_pCVar->FindVar("dedi_sendPrintsToClient"); - eSendPrintsToClient eSendPrints = static_cast<eSendPrintsToClient>(Cvar_dedi_sendPrintsToClient->GetInt()); - if (eSendPrints == eSendPrintsToClient::NONE) - return; - - std::string sLogMessage = fmt::format("[DEDICATED SERVER] [{}] {}", level_names[msg.level], msg.payload); - for (int i = 0; i < g_pGlobals->m_nMaxClients; i++) - { - CBaseClient* pClient = &g_pClientArray[i]; - - if (pClient->m_Signon >= eSignonState::CONNECTED) - { - CGameClient__ClientPrintf(pClient, sLogMessage.c_str()); - - if (eSendPrints == eSendPrintsToClient::FIRST) - break; - } - } -} - -void DedicatedServerLogToClientSink::sink_it_(const spdlog::details::log_msg& msg) -{ - throw std::runtime_error("sink_it_ called on DedicatedServerLogToClientSink with pure log_msg. This is an error!"); -} - -void DedicatedServerLogToClientSink::flush_() {} - -ON_DLL_LOAD_DEDI("engine.dll", DedicatedServerLogToClient, (CModule module)) -{ - CGameClient__ClientPrintf = module.Offset(0x1016A0).RCast<void (*)(CBaseClient*, const char*, ...)>(); -} diff --git a/NorthstarDLL/dedicated/dedicatedlogtoclient.h b/NorthstarDLL/dedicated/dedicatedlogtoclient.h deleted file mode 100644 index 82f4c56b..00000000 --- a/NorthstarDLL/dedicated/dedicatedlogtoclient.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "logging/logging.h" -#include "core/convar/convar.h" - -class DedicatedServerLogToClientSink : public CustomSink -{ -protected: - void custom_sink_it_(const custom_log_msg& msg); - void sink_it_(const spdlog::details::log_msg& msg) override; - void flush_() override; -}; diff --git a/NorthstarDLL/dedicated/dedicatedmaterialsystem.cpp b/NorthstarDLL/dedicated/dedicatedmaterialsystem.cpp deleted file mode 100644 index 01078086..00000000 --- a/NorthstarDLL/dedicated/dedicatedmaterialsystem.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "dedicated.h" -#include "core/tier0.h" - -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E, -HRESULT, __stdcall, ( - void* pAdapter, - int DriverType, - HMODULE Software, - UINT Flags, - int* pFeatureLevels, - UINT FeatureLevels, - UINT SDKVersion, - void** ppDevice, - int* pFeatureLevel, - void** ppImmediateContext)) -// clang-format on -{ - // note: this is super duper temp pretty much just messing around with it - // does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't - // really call gpu much even with renderthread still being a thing will be using this hook for actual d3d stubbing and stuff later - - // note: this has been succeeded by the d3d11 and gfsdk stubs, and is only being kept around for posterity and as a fallback option - if (CommandLine()->CheckParm("-softwared3d11")) - DriverType = 5; // D3D_DRIVER_TYPE_WARP - - return D3D11CreateDevice( - pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext); -} - -ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - // CMaterialSystem::FindMaterial - // make the game always use the error material - module.Offset(0x5F0F1).Patch("E9 34 03 00"); -} diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp deleted file mode 100644 index 3d9bdc97..00000000 --- a/NorthstarDLL/dllmain.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "dllmain.h" -#include "logging/logging.h" -#include "logging/crashhandler.h" -#include "core/memalloc.h" -#include "core/vanilla.h" -#include "config/profile.h" -#include "plugins/plugin_abi.h" -#include "plugins/plugins.h" -#include "plugins/pluginbackend.h" -#include "util/version.h" -#include "squirrel/squirrel.h" -#include "server/serverpresence.h" - -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/writer.h" -#include "rapidjson/error/en.h" - -#include <string.h> -#include <filesystem> - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - - return TRUE; -} - -bool InitialiseNorthstar() -{ - static bool bInitialised = false; - if (bInitialised) - return false; - - bInitialised = true; - - InitialiseNorthstarPrefix(); - - // initialise the console if needed (-northstar needs this) - InitialiseConsole(); - // initialise logging before most other things so that they can use spdlog and it have the proper formatting - InitialiseLogging(); - InitialiseVersion(); - CreateLogFiles(); - - g_pCrashHandler = new CCrashHandler(); - bool bAllFatal = strstr(GetCommandLineA(), "-crash_handle_all") != NULL; - g_pCrashHandler->SetAllFatal(bAllFatal); - - // determine if we are in vanilla-compatibility mode - g_pVanillaCompatibility = new VanillaCompatibility(); - g_pVanillaCompatibility->SetVanillaCompatibility(strstr(GetCommandLineA(), "-vanilla") != NULL); - - // Write launcher version to log - StartupLog(); - - InstallInitialHooks(); - - g_pServerPresence = new ServerPresenceManager(); - - g_pPluginManager = new PluginManager(); - g_pPluginCommunicationhandler = new PluginCommunicationHandler(); - g_pPluginManager->LoadPlugins(); - - InitialiseSquirrelManagers(); - - // Fix some users' failure to connect to respawn datacenters - SetEnvironmentVariableA("OPENSSL_ia32cap", "~0x200000200000000"); - - curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base); - - // run callbacks for any libraries that are already loaded by now - CallAllPendingDLLLoadCallbacks(); - - return true; -} diff --git a/NorthstarDLL/dllmain.h b/NorthstarDLL/dllmain.h deleted file mode 100644 index 0debf379..00000000 --- a/NorthstarDLL/dllmain.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -extern "C" __declspec(dllexport) bool InitialiseNorthstar(); diff --git a/NorthstarDLL/engine/host.cpp b/NorthstarDLL/engine/host.cpp deleted file mode 100644 index dacb8fc1..00000000 --- a/NorthstarDLL/engine/host.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "core/convar/convar.h" -#include "mods/modmanager.h" -#include "util/printcommands.h" -#include "util/printmaps.h" -#include "shared/misccommands.h" -#include "r2engine.h" -#include "core/tier0.h" - -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(Host_Init, engine.dll + 0x155EA0, -void, __fastcall, (bool bDedicated)) -// clang-format on -{ - spdlog::info("Host_Init()"); - Host_Init(bDedicated); - FixupCvarFlags(); - // need to initialise these after host_init since they do stuff to preexisting concommands/convars without being client/server specific - InitialiseCommandPrint(); - InitialiseMapsPrint(); - // client/server autoexecs on necessary platforms - // dedi needs autoexec_ns_server on boot, while non-dedi will run it on on listen server start - if (bDedicated) - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); - else - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_client", cmd_source_t::kCommandSrcCode); -} - -ON_DLL_LOAD("engine.dll", Host_Init, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/engine/hoststate.cpp b/NorthstarDLL/engine/hoststate.cpp deleted file mode 100644 index ec728afb..00000000 --- a/NorthstarDLL/engine/hoststate.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "engine/hoststate.h" -#include "masterserver/masterserver.h" -#include "server/auth/serverauthentication.h" -#include "server/serverpresence.h" -#include "shared/playlist.h" -#include "core/tier0.h" -#include "engine/r2engine.h" -#include "shared/exploit_fixes/ns_limits.h" -#include "squirrel/squirrel.h" -#include "plugins/plugins.h" -#include "plugins/pluginbackend.h" - -AUTOHOOK_INIT() - -CHostState* g_pHostState; - -std::string sLastMode; - -VAR_AT(engine.dll + 0x13FA6070, ConVar*, Cvar_hostport); -FUNCTION_AT(engine.dll + 0x1232C0, void, __fastcall, _Cmd_Exec_f, (const CCommand& arg, bool bOnlyIfExists, bool bUseWhitelists)); - -void ServerStartingOrChangingMap() -{ - ConVar* Cvar_mp_gamemode = g_pCVar->FindVar("mp_gamemode"); - - // directly call _Cmd_Exec_f to avoid weirdness with ; being in mp_gamemode potentially - // if we ran exec {mp_gamemode} and mp_gamemode contained semicolons, this could be used to execute more commands - char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor - memset(commandBuf, 0, sizeof(commandBuf)); - CCommand tempCommand = *(CCommand*)&commandBuf; - if (sLastMode.length() && - CCommand__Tokenize(tempCommand, fmt::format("exec server/cleanup_gamemode_{}", sLastMode).c_str(), cmd_source_t::kCommandSrcCode)) - _Cmd_Exec_f(tempCommand, false, false); - - memset(commandBuf, 0, sizeof(commandBuf)); - if (CCommand__Tokenize( - tempCommand, - fmt::format("exec server/setup_gamemode_{}", sLastMode = Cvar_mp_gamemode->GetString()).c_str(), - cmd_source_t::kCommandSrcCode)) - { - _Cmd_Exec_f(tempCommand, false, false); - } - - Cbuf_Execute(); // exec everything right now - - // net_data_block_enabled is required for sp, force it if we're on an sp map - // sucks for security but just how it be - if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) - { - g_pCVar->FindVar("net_data_block_enabled")->SetValue(true); - g_pServerAuthentication->m_bStartingLocalSPGame = true; - } - else - g_pServerAuthentication->m_bStartingLocalSPGame = false; -} - -// clang-format off -AUTOHOOK(CHostState__State_NewGame, engine.dll + 0x16E7D0, -void, __fastcall, (CHostState* self)) -// clang-format on -{ - spdlog::info("HostState: NewGame"); - - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - - // need to do this to ensure we don't go to private match - if (g_pServerAuthentication->m_bNeedLocalAuthForNewgame) - R2::SetCurrentPlaylist("tdm"); - - ServerStartingOrChangingMap(); - - double dStartTime = Plat_FloatTime(); - CHostState__State_NewGame(self); - spdlog::info("loading took {}s", Plat_FloatTime() - dStartTime); - - // setup server presence - g_pServerPresence->CreatePresence(); - g_pServerPresence->SetMap(g_pHostState->m_levelName, true); - g_pServerPresence->SetPlaylist(R2::GetCurrentPlaylistName()); - g_pServerPresence->SetPort(Cvar_hostport->GetInt()); - - g_pServerAuthentication->m_bNeedLocalAuthForNewgame = false; -} - -// clang-format off -AUTOHOOK(CHostState__State_LoadGame, engine.dll + 0x16E730, -void, __fastcall, (CHostState* self)) -// clang-format on -{ - // singleplayer server starting - // useless in 99% of cases but without it things could potentially break very much - - spdlog::info("HostState: LoadGame"); - - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - - // this is normally done in ServerStartingOrChangingMap(), but seemingly the map name isn't set at this point - g_pCVar->FindVar("net_data_block_enabled")->SetValue(true); - g_pServerAuthentication->m_bStartingLocalSPGame = true; - - double dStartTime = Plat_FloatTime(); - CHostState__State_LoadGame(self); - spdlog::info("loading took {}s", Plat_FloatTime() - dStartTime); - - // no server presence, can't do it because no map name in hoststate - // and also not super important for sp saves really - - g_pServerAuthentication->m_bNeedLocalAuthForNewgame = false; -} - -// clang-format off -AUTOHOOK(CHostState__State_ChangeLevelMP, engine.dll + 0x16E520, -void, __fastcall, (CHostState* self)) -// clang-format on -{ - spdlog::info("HostState: ChangeLevelMP"); - - ServerStartingOrChangingMap(); - - double dStartTime = Plat_FloatTime(); - CHostState__State_ChangeLevelMP(self); - spdlog::info("loading took {}s", Plat_FloatTime() - dStartTime); - - g_pServerPresence->SetMap(g_pHostState->m_levelName); -} - -// clang-format off -AUTOHOOK(CHostState__State_GameShutdown, engine.dll + 0x16E640, -void, __fastcall, (CHostState* self)) -// clang-format on -{ - spdlog::info("HostState: GameShutdown"); - - g_pServerPresence->DestroyPresence(); - - CHostState__State_GameShutdown(self); - - // run gamemode cleanup cfg now instead of when we start next map - if (sLastMode.length()) - { - char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor - memset(commandBuf, 0, sizeof(commandBuf)); - CCommand tempCommand = *(CCommand*)&commandBuf; - if (CCommand__Tokenize( - tempCommand, fmt::format("exec server/cleanup_gamemode_{}", sLastMode).c_str(), cmd_source_t::kCommandSrcCode)) - { - _Cmd_Exec_f(tempCommand, false, false); - Cbuf_Execute(); - } - - sLastMode.clear(); - } -} - -// clang-format off -AUTOHOOK(CHostState__FrameUpdate, engine.dll + 0x16DB00, -void, __fastcall, (CHostState* self, double flCurrentTime, float flFrameTime)) -// clang-format on -{ - CHostState__FrameUpdate(self, flCurrentTime, flFrameTime); - - if (*g_pServerState == server_state_t::ss_active) - { - // update server presence - g_pServerPresence->RunFrame(flCurrentTime); - - // update limits for frame - g_pServerLimits->RunFrame(flCurrentTime, flFrameTime); - } - - // Run Squirrel message buffer - if (g_pSquirrel<ScriptContext::UI>->m_pSQVM != nullptr && g_pSquirrel<ScriptContext::UI>->m_pSQVM->sqvm != nullptr) - g_pSquirrel<ScriptContext::UI>->ProcessMessageBuffer(); - - if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM != nullptr && g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm != nullptr) - g_pSquirrel<ScriptContext::CLIENT>->ProcessMessageBuffer(); - - if (g_pSquirrel<ScriptContext::SERVER>->m_pSQVM != nullptr && g_pSquirrel<ScriptContext::SERVER>->m_pSQVM->sqvm != nullptr) - g_pSquirrel<ScriptContext::SERVER>->ProcessMessageBuffer(); - - g_pPluginManager->RunFrame(); -} - -ON_DLL_LOAD_RELIESON("engine.dll", HostState, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - g_pHostState = module.Offset(0x7CF180).RCast<CHostState*>(); -} diff --git a/NorthstarDLL/engine/hoststate.h b/NorthstarDLL/engine/hoststate.h deleted file mode 100644 index 290903ab..00000000 --- a/NorthstarDLL/engine/hoststate.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -enum class HostState_t -{ - HS_NEW_GAME = 0, - HS_LOAD_GAME, - HS_CHANGE_LEVEL_SP, - HS_CHANGE_LEVEL_MP, - HS_RUN, - HS_GAME_SHUTDOWN, - HS_SHUTDOWN, - HS_RESTART, -}; - -struct CHostState -{ -public: - HostState_t m_iCurrentState; - HostState_t m_iNextState; - - float m_vecLocation[3]; - float m_angLocation[3]; - - char m_levelName[32]; - char m_mapGroupName[32]; - char m_landmarkName[32]; - char m_saveName[32]; - float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets - - bool m_activeGame; - bool m_bRememberLocation; - bool m_bBackgroundLevel; - bool m_bWaitingForConnection; - bool m_bLetToolsOverrideLoadGameEnts; // During a load game, this tells Foundry to override ents that are selected in Hammer. - bool m_bSplitScreenConnect; - bool m_bGameHasShutDownAndFlushedMemory; // This is false once we load a map into memory, and set to true once the map is unloaded - // and all memory flushed - bool m_bWorkshopMapDownloadPending; -}; - -extern CHostState* g_pHostState; diff --git a/NorthstarDLL/engine/r2engine.cpp b/NorthstarDLL/engine/r2engine.cpp deleted file mode 100644 index 88500376..00000000 --- a/NorthstarDLL/engine/r2engine.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "r2engine.h" - -Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; -Cbuf_AddTextType Cbuf_AddText; -Cbuf_ExecuteType Cbuf_Execute; - -bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, cmd_source_t commandSource); - -CEngine* g_pEngine; - -void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); -CBaseClient* g_pClientArray; - -server_state_t* g_pServerState; - -char* g_pModName = - nullptr; // we cant set this up here atm since we dont have an offset to it in engine, instead we store it in IsRespawnMod - -CGlobalVars* g_pGlobals; - -ON_DLL_LOAD("engine.dll", R2Engine, (CModule module)) -{ - Cbuf_GetCurrentPlayer = module.Offset(0x120630).RCast<Cbuf_GetCurrentPlayerType>(); - Cbuf_AddText = module.Offset(0x1203B0).RCast<Cbuf_AddTextType>(); - Cbuf_Execute = module.Offset(0x1204B0).RCast<Cbuf_ExecuteType>(); - - CCommand__Tokenize = module.Offset(0x418380).RCast<bool (*)(CCommand&, const char*, cmd_source_t)>(); - - g_pEngine = module.Offset(0x7D70C8).Deref().RCast<CEngine*>(); - - CBaseClient__Disconnect = module.Offset(0x1012C0).RCast<void (*)(void*, uint32_t, const char*, ...)>(); - g_pClientArray = module.Offset(0x12A53F90).RCast<CBaseClient*>(); - - g_pServerState = module.Offset(0x12A53D48).RCast<server_state_t*>(); - - g_pGlobals = module.Offset(0x7C6F70).RCast<CGlobalVars*>(); -} diff --git a/NorthstarDLL/engine/r2engine.h b/NorthstarDLL/engine/r2engine.h deleted file mode 100644 index e3bcc37e..00000000 --- a/NorthstarDLL/engine/r2engine.h +++ /dev/null @@ -1,260 +0,0 @@ -#pragma once -#include "shared/keyvalues.h" - -// Cbuf -enum class ECommandTarget_t -{ - CBUF_FIRST_PLAYER = 0, - CBUF_LAST_PLAYER = 1, // MAX_SPLITSCREEN_CLIENTS - 1, MAX_SPLITSCREEN_CLIENTS = 2 - CBUF_SERVER = CBUF_LAST_PLAYER + 1, - - CBUF_COUNT, -}; - -enum class cmd_source_t -{ - // Added to the console buffer by gameplay code. Generally unrestricted. - kCommandSrcCode, - - // Sent from code via engine->ClientCmd, which is restricted to commands visible - // via FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS. - kCommandSrcClientCmd, - - // Typed in at the console or via a user key-bind. Generally unrestricted, although - // the client will throttle commands sent to the server this way to 16 per second. - kCommandSrcUserInput, - - // Came in over a net connection as a clc_stringcmd - // host_client will be valid during this state. - // - // Restricted to FCVAR_GAMEDLL commands (but not convars) and special non-ConCommand - // server commands hardcoded into gameplay code (e.g. "joingame") - kCommandSrcNetClient, - - // Received from the server as the client - // - // Restricted to commands with FCVAR_SERVER_CAN_EXECUTE - kCommandSrcNetServer, - - // Being played back from a demo file - // - // Not currently restricted by convar flag, but some commands manually ignore calls - // from this source. FIXME: Should be heavily restricted as demo commands can come - // from untrusted sources. - kCommandSrcDemoFile, - - // Invalid value used when cleared - kCommandSrcInvalid = -1 -}; - -typedef ECommandTarget_t (*Cbuf_GetCurrentPlayerType)(); -extern Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; - -typedef void (*Cbuf_AddTextType)(ECommandTarget_t eTarget, const char* text, cmd_source_t source); -extern Cbuf_AddTextType Cbuf_AddText; - -typedef void (*Cbuf_ExecuteType)(); -extern Cbuf_ExecuteType Cbuf_Execute; - -extern bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, cmd_source_t commandSource); - -// CEngine - -enum EngineQuitState -{ - QUIT_NOTQUITTING = 0, - QUIT_TODESKTOP, - QUIT_RESTART -}; - -enum class EngineState_t -{ - DLL_INACTIVE = 0, // no dll - DLL_ACTIVE, // engine is focused - DLL_CLOSE, // closing down dll - DLL_RESTART, // engine is shutting down but will restart right away - DLL_PAUSED, // engine is paused, can become active from this state -}; - -class CEngine -{ -public: - virtual void unknown() = 0; // unsure if this is where - virtual bool Load(bool dedicated, const char* baseDir) = 0; - virtual void Unload() = 0; - virtual void SetNextState(EngineState_t iNextState) = 0; - virtual EngineState_t GetState() = 0; - virtual void Frame() = 0; - virtual double GetFrameTime() = 0; - virtual float GetCurTime() = 0; - - EngineQuitState m_nQuitting; - EngineState_t m_nDllState; - EngineState_t m_nNextDllState; - double m_flCurrentTime; - float m_flFrameTime; - double m_flPreviousTime; - float m_flFilteredTime; - float m_flMinFrameTime; // Expected duration of a frame, or zero if it is unlimited. -}; - -extern CEngine* g_pEngine; - -extern void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); - -#pragma once -typedef enum -{ - NA_NULL = 0, - NA_LOOPBACK, - NA_IP, -} netadrtype_t; - -#pragma pack(push, 1) -typedef struct netadr_s -{ - netadrtype_t type; - unsigned char ip[16]; // IPv6 - // IPv4's 127.0.0.1 is [::ffff:127.0.0.1], that is: - // 00 00 00 00 00 00 00 00 00 00 FF FF 7F 00 00 01 - unsigned short port; -} netadr_t; -#pragma pack(pop) - -#pragma pack(push, 1) -typedef struct netpacket_s -{ - netadr_t adr; // sender address - // int source; // received source - char unk[10]; - double received_time; - unsigned char* data; // pointer to raw packet data - void* message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer) - char unk2[16]; - int size; - - // bf_read message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer) - // int size; // size in bytes - // int wiresize; // size in bytes before decompression - // bool stream; // was send as stream - // struct netpacket_s* pNext; // for internal use, should be NULL in public -} netpacket_t; -#pragma pack(pop) - -// #56169 $DB69 PData size -// #512 $200 Trailing data -// #100 $64 Safety buffer -const int PERSISTENCE_MAX_SIZE = 0xDDCD; - -// note: NOT_READY and READY are the only entries we have here that are defined by the vanilla game -// entries after this are custom and used to determine the source of persistence, e.g. whether it is local or remote -enum class ePersistenceReady : char -{ - NOT_READY, - READY = 3, - READY_INSECURE = 3, - READY_REMOTE -}; - -enum class eSignonState : int -{ - NONE = 0, // no state yet; about to connect - CHALLENGE = 1, // client challenging server; all OOB packets - CONNECTED = 2, // client is connected to server; netchans ready - NEW = 3, // just got serverinfo and string tables - PRESPAWN = 4, // received signon buffers - GETTINGDATA = 5, // respawn-defined signonstate, assumedly this is for persistence - SPAWN = 6, // ready to receive entity packets - FIRSTSNAP = 7, // another respawn-defined one - FULL = 8, // we are fully connected; first non-delta packet received - CHANGELEVEL = 9, // server is changing level; please wait -}; - -// clang-format off -OFFSET_STRUCT(CBaseClient) -{ - STRUCT_SIZE(0x2D728) - FIELD(0x16, char m_Name[64]) - FIELD(0x258, KeyValues* m_ConVars) - FIELD(0x2A0, eSignonState m_Signon) - FIELD(0x358, char m_ClanTag[16]) - FIELD(0x484, bool m_bFakePlayer) - FIELD(0x4A0, ePersistenceReady m_iPersistenceReady) - FIELD(0x4FA, char m_PersistenceBuffer[PERSISTENCE_MAX_SIZE]) - FIELD(0xF500, char m_UID[32]) -}; -// clang-format on - -extern CBaseClient* g_pClientArray; - -enum server_state_t -{ - ss_dead = 0, // Dead - ss_loading, // Spawning - ss_active, // Running - ss_paused, // Running, but paused -}; - -extern server_state_t* g_pServerState; - -extern char* g_pModName; - -// clang-format off -OFFSET_STRUCT(CGlobalVars) -{ - FIELD(0x0, - // Absolute time (per frame still - Use Plat_FloatTime() for a high precision real time - // perf clock, but not that it doesn't obey host_timescale/host_framerate) - double m_flRealTime); - - FIELDS(0x8, - // Absolute frame counter - continues to increase even if game is paused - int m_nFrameCount; - - // Non-paused frametime - float m_flAbsoluteFrameTime; - - // Current time - // - // On the client, this (along with tickcount) takes a different meaning based on what - // piece of code you're in: - // - // - While receiving network packets (like in PreDataUpdate/PostDataUpdate and proxies), - // this is set to the SERVER TICKCOUNT for that packet. There is no interval between - // the server ticks. - // [server_current_Tick * tick_interval] - // - // - While rendering, this is the exact client clock - // [client_current_tick * tick_interval + interpolation_amount] - // - // - During prediction, this is based on the client's current tick: - // [client_current_tick * tick_interval] - float m_flCurTime; - ) - - FIELDS(0x30, - // Time spent on last server or client frame (has nothing to do with think intervals) - float m_flFrameTime; - - // current maxplayers setting - int m_nMaxClients; - ) - - FIELDS(0x3C, - // Simulation ticks - does not increase when game is paused - uint32_t m_nTickCount; // this is weird and doesn't seem to increase once per frame? - - // Simulation tick interval - float m_flTickInterval; - ) - - FIELDS(0x60, - const char* m_pMapName; - int m_nMapVersion; - ) - - //FIELD(0x98, double m_flRealTime); // again? -}; -// clang-format on - -extern CGlobalVars* g_pGlobals; diff --git a/NorthstarDLL/engine/runframe.cpp b/NorthstarDLL/engine/runframe.cpp deleted file mode 100644 index ddfd9253..00000000 --- a/NorthstarDLL/engine/runframe.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "engine/r2engine.h" -#include "server/r2server.h" -#include "hoststate.h" -#include "server/serverpresence.h" - -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(CEngine__Frame, engine.dll + 0x1C8650, -void, __fastcall, (CEngine* self)) -// clang-format on -{ - CEngine__Frame(self); -} - -ON_DLL_LOAD("engine.dll", RunFrame, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/logging/crashhandler.cpp b/NorthstarDLL/logging/crashhandler.cpp deleted file mode 100644 index a01de5a1..00000000 --- a/NorthstarDLL/logging/crashhandler.cpp +++ /dev/null @@ -1,590 +0,0 @@ -#include "crashhandler.h" -#include "config/profile.h" -#include "dedicated/dedicated.h" -#include "util/version.h" -#include "mods/modmanager.h" -#include "plugins/plugins.h" - -#include <minidumpapiset.h> - -#define CRASHHANDLER_MAX_FRAMES 32 -#define CRASHHANDLER_GETMODULEHANDLE_FAIL "GetModuleHandleExA failed!" - -//----------------------------------------------------------------------------- -// Purpose: Vectored exception callback -//----------------------------------------------------------------------------- -LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo) -{ - g_pCrashHandler->Lock(); - - g_pCrashHandler->SetExceptionInfos(pExceptionInfo); - - // Check if we should handle this - // NOTE [Fifty]: This gets called before even a try{} catch() {} can handle an exception - // we don't handle these unless "-crash_handle_all" is passed as a launch arg - if (!g_pCrashHandler->IsExceptionFatal() && !g_pCrashHandler->GetAllFatal()) - { - g_pCrashHandler->Unlock(); - return EXCEPTION_CONTINUE_SEARCH; - } - - // Don't run if a debbuger is attached - if (IsDebuggerPresent()) - { - g_pCrashHandler->Unlock(); - return EXCEPTION_CONTINUE_SEARCH; - } - - // Prevent recursive calls - if (g_pCrashHandler->GetState()) - { - g_pCrashHandler->Unlock(); - ExitProcess(1); - } - - g_pCrashHandler->SetState(true); - - // Needs to be called first as we use the members this sets later on - g_pCrashHandler->SetCrashedModule(); - - // Format - g_pCrashHandler->FormatException(); - g_pCrashHandler->FormatCallstack(); - g_pCrashHandler->FormatRegisters(); - g_pCrashHandler->FormatLoadedMods(); - g_pCrashHandler->FormatLoadedPlugins(); - g_pCrashHandler->FormatModules(); - - // Flush - NS::log::FlushLoggers(); - - // Write minidump - g_pCrashHandler->WriteMinidump(); - - // Show message box - g_pCrashHandler->ShowPopUpMessage(); - - g_pCrashHandler->Unlock(); - - // We showed the "Northstar has crashed" message box - // make sure we terminate - if (!g_pCrashHandler->IsExceptionFatal()) - ExitProcess(1); - - return EXCEPTION_EXECUTE_HANDLER; -} - -//----------------------------------------------------------------------------- -// Purpose: console control signal handler -//----------------------------------------------------------------------------- -BOOL WINAPI ConsoleCtrlRoutine(DWORD dwCtrlType) -{ - // NOTE [Fifty]: When closing the process by closing the console we don't want - // to trigger the crash handler so we remove it - switch (dwCtrlType) - { - case CTRL_CLOSE_EVENT: - spdlog::info("Exiting due to console close..."); - delete g_pCrashHandler; - g_pCrashHandler = nullptr; - std::exit(EXIT_SUCCESS); - return TRUE; - } - - return FALSE; -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -//----------------------------------------------------------------------------- -CCrashHandler::CCrashHandler() - : m_hExceptionFilter(nullptr) - , m_pExceptionInfos(nullptr) - , m_bHasSetConsolehandler(false) - , m_bAllExceptionsFatal(false) - , m_bHasShownCrashMsg(false) - , m_bState(false) -{ - Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: Destructor -//----------------------------------------------------------------------------- -CCrashHandler::~CCrashHandler() -{ - Shutdown(); -} - -//----------------------------------------------------------------------------- -// Purpose: Initilazes crash handler -//----------------------------------------------------------------------------- -void CCrashHandler::Init() -{ - m_hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); - m_bHasSetConsolehandler = SetConsoleCtrlHandler(ConsoleCtrlRoutine, TRUE); -} - -//----------------------------------------------------------------------------- -// Purpose: Shutdowns crash handler -//----------------------------------------------------------------------------- -void CCrashHandler::Shutdown() -{ - if (m_hExceptionFilter) - { - RemoveVectoredExceptionHandler(m_hExceptionFilter); - m_hExceptionFilter = nullptr; - } - - if (m_bHasSetConsolehandler) - { - SetConsoleCtrlHandler(ConsoleCtrlRoutine, FALSE); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Sets the exception info -//----------------------------------------------------------------------------- -void CCrashHandler::SetExceptionInfos(EXCEPTION_POINTERS* pExceptionPointers) -{ - m_pExceptionInfos = pExceptionPointers; -} -//----------------------------------------------------------------------------- -// Purpose: Sets the exception stirngs for message box -//----------------------------------------------------------------------------- -void CCrashHandler::SetCrashedModule() -{ - LPCSTR pCrashAddress = static_cast<LPCSTR>(m_pExceptionInfos->ExceptionRecord->ExceptionAddress); - HMODULE hCrashedModule; - if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, pCrashAddress, &hCrashedModule)) - { - m_svCrashedModule = CRASHHANDLER_GETMODULEHANDLE_FAIL; - m_svCrashedOffset = ""; - - DWORD dwErrorID = GetLastError(); - if (dwErrorID != 0) - { - LPSTR pszBuffer; - DWORD dwSize = FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - dwErrorID, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&pszBuffer, - 0, - NULL); - - if (dwSize > 0) - { - m_svError = pszBuffer; - LocalFree(pszBuffer); - } - } - - return; - } - - // Get module filename - CHAR szCrashedModulePath[MAX_PATH]; - GetModuleFileNameExA(GetCurrentProcess(), hCrashedModule, szCrashedModulePath, sizeof(szCrashedModulePath)); - - const CHAR* pszCrashedModuleFileName = strrchr(szCrashedModulePath, '\\') + 1; - - // Get relative address - LPCSTR pModuleBase = reinterpret_cast<LPCSTR>(pCrashAddress - reinterpret_cast<LPCSTR>(hCrashedModule)); - - m_svCrashedModule = pszCrashedModuleFileName; - m_svCrashedOffset = fmt::format("{:#x}", reinterpret_cast<DWORD64>(pModuleBase)); -} - -//----------------------------------------------------------------------------- -// Purpose: Gets the exception null terminated stirng -//----------------------------------------------------------------------------- - -const CHAR* CCrashHandler::GetExceptionString() const -{ - return GetExceptionString(m_pExceptionInfos->ExceptionRecord->ExceptionCode); -} - -//----------------------------------------------------------------------------- -// Purpose: Gets the exception null terminated stirng -//----------------------------------------------------------------------------- -const CHAR* CCrashHandler::GetExceptionString(DWORD dwExceptionCode) const -{ - // clang-format off - switch (dwExceptionCode) - { - case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; - case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; - case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; - case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP"; - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; - case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND"; - case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; - case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT"; - case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION"; - case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; - case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK"; - case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; - case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; - case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; - case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; - case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR"; - case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; - case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; - case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; - case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION"; - case EXCEPTION_GUARD_PAGE: return "EXCEPTION_GUARD_PAGE"; - case EXCEPTION_INVALID_HANDLE: return "EXCEPTION_INVALID_HANDLE"; - case 3765269347: return "RUNTIME_EXCEPTION"; - } - // clang-format on - return "UNKNOWN_EXCEPTION"; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true if exception is known -//----------------------------------------------------------------------------- -bool CCrashHandler::IsExceptionFatal() const -{ - return IsExceptionFatal(m_pExceptionInfos->ExceptionRecord->ExceptionCode); -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true if exception is known -//----------------------------------------------------------------------------- -bool CCrashHandler::IsExceptionFatal(DWORD dwExceptionCode) const -{ - // clang-format off - switch (dwExceptionCode) - { - case EXCEPTION_ACCESS_VIOLATION: - case EXCEPTION_DATATYPE_MISALIGNMENT: - case EXCEPTION_BREAKPOINT: - case EXCEPTION_SINGLE_STEP: - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - case EXCEPTION_FLT_DENORMAL_OPERAND: - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - case EXCEPTION_FLT_INEXACT_RESULT: - case EXCEPTION_FLT_INVALID_OPERATION: - case EXCEPTION_FLT_OVERFLOW: - case EXCEPTION_FLT_STACK_CHECK: - case EXCEPTION_FLT_UNDERFLOW: - case EXCEPTION_INT_DIVIDE_BY_ZERO: - case EXCEPTION_INT_OVERFLOW: - case EXCEPTION_PRIV_INSTRUCTION: - case EXCEPTION_IN_PAGE_ERROR: - case EXCEPTION_ILLEGAL_INSTRUCTION: - case EXCEPTION_NONCONTINUABLE_EXCEPTION: - case EXCEPTION_STACK_OVERFLOW: - case EXCEPTION_INVALID_DISPOSITION: - case EXCEPTION_GUARD_PAGE: - case EXCEPTION_INVALID_HANDLE: - return true; - } - // clang-format on - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Shows a message box -//----------------------------------------------------------------------------- -void CCrashHandler::ShowPopUpMessage() -{ - if (m_bHasShownCrashMsg) - return; - - m_bHasShownCrashMsg = true; - - if (!IsDedicatedServer()) - { - std::string svMessage = fmt::format( - "Northstar has crashed! Crash info can be found at {}/logs!\n\n{}\n{} + {}", - GetNorthstarPrefix(), - GetExceptionString(), - m_svCrashedModule, - m_svCrashedOffset); - - MessageBoxA(GetForegroundWindow(), svMessage.c_str(), "Northstar has crashed!", MB_ICONERROR | MB_OK); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatException() -{ - spdlog::error("-------------------------------------------"); - spdlog::error("Northstar has crashed!"); - spdlog::error("\tVersion: {}", version); - if (!m_svError.empty()) - { - spdlog::info("\tEncountered an error when gathering crash information!"); - spdlog::info("\tWinApi Error: {}", m_svError.c_str()); - } - spdlog::error("\t{}", GetExceptionString()); - - DWORD dwExceptionCode = m_pExceptionInfos->ExceptionRecord->ExceptionCode; - if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION || dwExceptionCode == EXCEPTION_IN_PAGE_ERROR) - { - ULONG_PTR uExceptionInfo0 = m_pExceptionInfos->ExceptionRecord->ExceptionInformation[0]; - ULONG_PTR uExceptionInfo1 = m_pExceptionInfos->ExceptionRecord->ExceptionInformation[1]; - - if (!uExceptionInfo0) - spdlog::error("\tAttempted to read from: {:#x}", uExceptionInfo1); - else if (uExceptionInfo0 == 1) - spdlog::error("\tAttempted to write to: {:#x}", uExceptionInfo1); - else if (uExceptionInfo0 == 8) - spdlog::error("\tData Execution Prevention (DEP) at: {:#x}", uExceptionInfo1); - else - spdlog::error("\tUnknown access violation at: {:#x}", uExceptionInfo1); - } - - spdlog::error("\tAt: {} + {}", m_svCrashedModule, m_svCrashedOffset); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatCallstack() -{ - spdlog::error("Callstack:"); - - PVOID pFrames[CRASHHANDLER_MAX_FRAMES]; - - int iFrames = RtlCaptureStackBackTrace(0, CRASHHANDLER_MAX_FRAMES, pFrames, NULL); - - // Above call gives us frames after the crash occured, we only want to print the ones starting from where - // the exception was called - bool bSkipExceptionHandlingFrames = true; - - // We ran into an error when getting the offset, just print all frames - if (m_svCrashedOffset.empty()) - bSkipExceptionHandlingFrames = false; - - for (int i = 0; i < iFrames; i++) - { - const CHAR* pszModuleFileName; - - LPCSTR pAddress = static_cast<LPCSTR>(pFrames[i]); - HMODULE hModule; - if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, pAddress, &hModule)) - { - pszModuleFileName = CRASHHANDLER_GETMODULEHANDLE_FAIL; - // If we fail here it's too late to do any damage control - } - else - { - CHAR szModulePath[MAX_PATH]; - GetModuleFileNameExA(GetCurrentProcess(), hModule, szModulePath, sizeof(szModulePath)); - pszModuleFileName = strrchr(szModulePath, '\\') + 1; - } - - // Get relative address - LPCSTR pCrashOffset = reinterpret_cast<LPCSTR>(pAddress - reinterpret_cast<LPCSTR>(hModule)); - std::string svCrashOffset = fmt::format("{:#x}", reinterpret_cast<DWORD64>(pCrashOffset)); - - // Should we log this frame - if (bSkipExceptionHandlingFrames) - { - if (m_svCrashedModule == pszModuleFileName && m_svCrashedOffset == svCrashOffset) - { - bSkipExceptionHandlingFrames = false; - } - else - { - continue; - } - } - - // Log module + offset - spdlog::error("\t{} + {:#x}", pszModuleFileName, reinterpret_cast<DWORD64>(pCrashOffset)); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatFlags(const CHAR* pszRegister, DWORD nValue) -{ - spdlog::error("\t{}: {:#b}", pszRegister, nValue); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatIntReg(const CHAR* pszRegister, DWORD64 nValue) -{ - spdlog::error("\t{}: {:#x}", pszRegister, nValue); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatFloatReg(const CHAR* pszRegister, M128A nValue) -{ - DWORD nVec[4] = { - static_cast<DWORD>(nValue.Low & UINT_MAX), - static_cast<DWORD>(nValue.Low >> 32), - static_cast<DWORD>(nValue.High & UINT_MAX), - static_cast<DWORD>(nValue.High >> 32)}; - - spdlog::error( - "\t{}: [ {:G}, {:G}, {:G}, {:G} ]; [ {:#x}, {:#x}, {:#x}, {:#x} ]", - pszRegister, - static_cast<float>(nVec[0]), - static_cast<float>(nVec[1]), - static_cast<float>(nVec[2]), - static_cast<float>(nVec[3]), - nVec[0], - nVec[1], - nVec[2], - nVec[3]); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatRegisters() -{ - spdlog::error("Registers:"); - - PCONTEXT pContext = m_pExceptionInfos->ContextRecord; - - FormatFlags("Flags:", pContext->ContextFlags); - - FormatIntReg("Rax", pContext->Rax); - FormatIntReg("Rcx", pContext->Rcx); - FormatIntReg("Rdx", pContext->Rdx); - FormatIntReg("Rbx", pContext->Rbx); - FormatIntReg("Rsp", pContext->Rsp); - FormatIntReg("Rbp", pContext->Rbp); - FormatIntReg("Rsi", pContext->Rsi); - FormatIntReg("Rdi", pContext->Rdi); - FormatIntReg("R8 ", pContext->R8); - FormatIntReg("R9 ", pContext->R9); - FormatIntReg("R10", pContext->R10); - FormatIntReg("R11", pContext->R11); - FormatIntReg("R12", pContext->R12); - FormatIntReg("R13", pContext->R13); - FormatIntReg("R14", pContext->R14); - FormatIntReg("R15", pContext->R15); - FormatIntReg("Rip", pContext->Rip); - - FormatFloatReg("Xmm0 ", pContext->Xmm0); - FormatFloatReg("Xmm1 ", pContext->Xmm1); - FormatFloatReg("Xmm2 ", pContext->Xmm2); - FormatFloatReg("Xmm3 ", pContext->Xmm3); - FormatFloatReg("Xmm4 ", pContext->Xmm4); - FormatFloatReg("Xmm5 ", pContext->Xmm5); - FormatFloatReg("Xmm6 ", pContext->Xmm6); - FormatFloatReg("Xmm7 ", pContext->Xmm7); - FormatFloatReg("Xmm8 ", pContext->Xmm8); - FormatFloatReg("Xmm9 ", pContext->Xmm9); - FormatFloatReg("Xmm10", pContext->Xmm10); - FormatFloatReg("Xmm11", pContext->Xmm11); - FormatFloatReg("Xmm12", pContext->Xmm12); - FormatFloatReg("Xmm13", pContext->Xmm13); - FormatFloatReg("Xmm14", pContext->Xmm14); - FormatFloatReg("Xmm15", pContext->Xmm15); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatLoadedMods() -{ - if (g_pModManager) - { - spdlog::error("Enabled mods:"); - for (const Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - spdlog::error("\t{}", mod.Name); - } - - spdlog::error("Disabled mods:"); - for (const Mod& mod : g_pModManager->m_LoadedMods) - { - if (mod.m_bEnabled) - continue; - - spdlog::error("\t{}", mod.Name); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatLoadedPlugins() -{ - if (g_pPluginManager) - { - spdlog::error("Loaded Plugins:"); - for (const Plugin& plugin : g_pPluginManager->m_vLoadedPlugins) - { - spdlog::error("\t{}", plugin.name); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCrashHandler::FormatModules() -{ - spdlog::error("Loaded modules:"); - HMODULE hModules[1024]; - DWORD cbNeeded; - - if (EnumProcessModules(GetCurrentProcess(), hModules, sizeof(hModules), &cbNeeded)) - { - for (DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) - { - CHAR szModulePath[MAX_PATH]; - if (GetModuleFileNameExA(GetCurrentProcess(), hModules[i], szModulePath, sizeof(szModulePath))) - { - const CHAR* pszModuleFileName = strrchr(szModulePath, '\\') + 1; - spdlog::error("\t{}", pszModuleFileName); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Writes minidump to disk -//----------------------------------------------------------------------------- -void CCrashHandler::WriteMinidump() -{ - time_t time = std::time(nullptr); - tm currentTime = *std::localtime(&time); - std::stringstream stream; - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); - - HANDLE hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (hMinidumpFile) - { - MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo; - dumpExceptionInfo.ThreadId = GetCurrentThreadId(); - dumpExceptionInfo.ExceptionPointers = m_pExceptionInfos; - dumpExceptionInfo.ClientPointers = false; - - MiniDumpWriteDump( - GetCurrentProcess(), - GetCurrentProcessId(), - hMinidumpFile, - MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), - &dumpExceptionInfo, - nullptr, - nullptr); - CloseHandle(hMinidumpFile); - } - else - spdlog::error("Failed to write minidump file {}!", stream.str()); -} - -//----------------------------------------------------------------------------- -CCrashHandler* g_pCrashHandler = nullptr; diff --git a/NorthstarDLL/logging/crashhandler.h b/NorthstarDLL/logging/crashhandler.h deleted file mode 100644 index c059a8ca..00000000 --- a/NorthstarDLL/logging/crashhandler.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include <mutex> - -//----------------------------------------------------------------------------- -// Purpose: Exception handling -//----------------------------------------------------------------------------- -class CCrashHandler -{ -public: - CCrashHandler(); - ~CCrashHandler(); - - void Init(); - void Shutdown(); - - void Lock() - { - m_Mutex.lock(); - } - - void Unlock() - { - m_Mutex.unlock(); - } - - void SetState(bool bState) - { - m_bState = bState; - } - - bool GetState() const - { - return m_bState; - } - - void SetAllFatal(bool bState) - { - m_bAllExceptionsFatal = bState; - } - - bool GetAllFatal() const - { - return m_bAllExceptionsFatal; - } - - //----------------------------------------------------------------------------- - // Exception helpers - //----------------------------------------------------------------------------- - void SetExceptionInfos(EXCEPTION_POINTERS* pExceptionPointers); - - void SetCrashedModule(); - - const CHAR* GetExceptionString() const; - const CHAR* GetExceptionString(DWORD dwExceptionCode) const; - - bool IsExceptionFatal() const; - bool IsExceptionFatal(DWORD dwExceptionCode) const; - - //----------------------------------------------------------------------------- - // Formatting - //----------------------------------------------------------------------------- - void ShowPopUpMessage(); - - void FormatException(); - void FormatCallstack(); - void FormatFlags(const CHAR* pszRegister, DWORD nValue); - void FormatIntReg(const CHAR* pszRegister, DWORD64 nValue); - void FormatFloatReg(const CHAR* pszRegister, M128A nValue); - void FormatRegisters(); - void FormatLoadedMods(); - void FormatLoadedPlugins(); - void FormatModules(); - - //----------------------------------------------------------------------------- - // Minidump - //----------------------------------------------------------------------------- - void WriteMinidump(); - -private: - PVOID m_hExceptionFilter; - EXCEPTION_POINTERS* m_pExceptionInfos; - - bool m_bHasSetConsolehandler; - bool m_bAllExceptionsFatal; - bool m_bHasShownCrashMsg; - bool m_bState; - - std::string m_svCrashedModule; - std::string m_svCrashedOffset; - - std::string m_svError; - - std::mutex m_Mutex; -}; - -extern CCrashHandler* g_pCrashHandler; diff --git a/NorthstarDLL/logging/logging.cpp b/NorthstarDLL/logging/logging.cpp deleted file mode 100644 index 3416bb8c..00000000 --- a/NorthstarDLL/logging/logging.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "logging.h" -#include "core/convar/convar.h" -#include "core/convar/concommand.h" -#include "config/profile.h" -#include "core/tier0.h" -#include "util/version.h" -#include "spdlog/sinks/basic_file_sink.h" - -#include <winternl.h> -#include <cstdlib> -#include <iomanip> -#include <sstream> - -AUTOHOOK_INIT() - -std::vector<std::shared_ptr<ColoredLogger>> loggers {}; - -namespace NS::log -{ - std::shared_ptr<ColoredLogger> SCRIPT_UI; - std::shared_ptr<ColoredLogger> SCRIPT_CL; - std::shared_ptr<ColoredLogger> SCRIPT_SV; - - std::shared_ptr<ColoredLogger> NATIVE_UI; - std::shared_ptr<ColoredLogger> NATIVE_CL; - std::shared_ptr<ColoredLogger> NATIVE_SV; - std::shared_ptr<ColoredLogger> NATIVE_EN; - - std::shared_ptr<ColoredLogger> fs; - std::shared_ptr<ColoredLogger> rpak; - std::shared_ptr<ColoredLogger> echo; - - std::shared_ptr<ColoredLogger> NORTHSTAR; - std::shared_ptr<ColoredLogger> PLUGINSYS; -}; // namespace NS::log - -// This needs to be called after hooks are loaded so we can access the command line args -void CreateLogFiles() -{ - if (strstr(GetCommandLineA(), "-disablelogs")) - { - spdlog::default_logger()->set_level(spdlog::level::off); - } - else - { - try - { - // todo: might be good to delete logs that are too old - time_t time = std::time(nullptr); - tm currentTime = *std::localtime(&time); - std::stringstream stream; - - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str()); - auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(stream.str(), false); - sink->set_pattern("[%Y-%m-%d] [%H:%M:%S] [%n] [%l] %v"); - for (auto& logger : loggers) - { - logger->sinks().push_back(sink); - } - spdlog::flush_on(spdlog::level::info); - } - catch (...) - { - spdlog::error("Failed creating log file!"); - MessageBoxA( - 0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK); - } - } -} - -void ExternalConsoleSink::sink_it_(const spdlog::details::log_msg& msg) -{ - throw std::runtime_error("sink_it_ called on SourceConsoleSink with pure log_msg. This is an error!"); -} - -void ExternalConsoleSink::custom_sink_it_(const custom_log_msg& msg) -{ - spdlog::memory_buf_t formatted; - spdlog::sinks::base_sink<std::mutex>::formatter_->format(msg, formatted); - - std::string out = ""; - // if ansi colour is turned off, just use WriteConsoleA and return - if (!g_bSpdLog_UseAnsiColor) - { - out += fmt::to_string(formatted); - } - - // print to the console with colours - else - { - // get message string - std::string str = fmt::to_string(formatted); - - std::string levelColor = m_LogColours[msg.level]; - std::string name {msg.logger_name.begin(), msg.logger_name.end()}; - - std::string name_str = "[NAME]"; - int name_pos = str.find(name_str); - str.replace(name_pos, name_str.length(), msg.origin->ANSIColor + "[" + name + "]" + default_color); - - std::string level_str = "[LVL]"; - int level_pos = str.find(level_str); - str.replace(level_pos, level_str.length(), levelColor + "[" + std::string(level_names[msg.level]) + "]" + default_color); - - out += str; - } - // print the string to the console - this is definitely bad i think - HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); - auto ignored = WriteConsoleA(handle, out.c_str(), std::strlen(out.c_str()), nullptr, nullptr); - (void)ignored; -} - -void ExternalConsoleSink::flush_() -{ - std::cout << std::flush; -} - -void CustomSink::custom_log(const custom_log_msg& msg) -{ - std::lock_guard<std::mutex> lock(mutex_); - custom_sink_it_(msg); -} - -void InitialiseConsole() -{ - if (AllocConsole() == FALSE) - { - std::cout << "[*] Failed to create a console window, maybe a console already exists?" << std::endl; - } - else - { - freopen("CONOUT$", "w", stdout); - freopen("CONOUT$", "w", stderr); - } - - // this if statement is adapted from r5sdk - if (!strstr(GetCommandLineA(), "-noansiclr")) - { - g_bSpdLog_UseAnsiColor = true; - DWORD dwMode = 0; - HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); - - GetConsoleMode(hOutput, &dwMode); - dwMode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING; - - if (!SetConsoleMode(hOutput, dwMode)) // Some editions of Windows have 'VirtualTerminalLevel' disabled by default. - { - // If 'VirtualTerminalLevel' can't be set, just disable ANSI color, since it wouldnt work anyway. - spdlog::warn("could not set VirtualTerminalLevel. Disabling color output"); - g_bSpdLog_UseAnsiColor = false; - } - } -} - -void RegisterLogger(std::shared_ptr<ColoredLogger> logger) -{ - loggers.push_back(logger); -} - -void RegisterCustomSink(std::shared_ptr<CustomSink> sink) -{ - for (auto& logger : loggers) - { - logger->custom_sinks_.push_back(sink); - } -}; - -void InitialiseLogging() -{ - // create a logger, and set it to default - NS::log::NORTHSTAR = std::make_shared<ColoredLogger>("NORTHSTAR", NS::Colors::NORTHSTAR, true); - NS::log::NORTHSTAR->sinks().clear(); - loggers.push_back(NS::log::NORTHSTAR); - spdlog::set_default_logger(NS::log::NORTHSTAR); - - // create our console sink - auto sink = std::make_shared<ExternalConsoleSink>(); - // set the pattern - if (g_bSpdLog_UseAnsiColor) - // dont put the log level in the pattern if we are using colours, as the colour will show the log level - sink->set_pattern("[%H:%M:%S] [NAME] [LVL] %v"); - else - sink->set_pattern("[%H:%M:%S] [%n] [%l] %v"); - - // add our sink to the logger - NS::log::NORTHSTAR->custom_sinks_.push_back(sink); - - NS::log::SCRIPT_UI = std::make_shared<ColoredLogger>("SCRIPT UI", NS::Colors::SCRIPT_UI); - NS::log::SCRIPT_CL = std::make_shared<ColoredLogger>("SCRIPT CL", NS::Colors::SCRIPT_CL); - NS::log::SCRIPT_SV = std::make_shared<ColoredLogger>("SCRIPT SV", NS::Colors::SCRIPT_SV); - - NS::log::NATIVE_UI = std::make_shared<ColoredLogger>("NATIVE UI", NS::Colors::NATIVE_UI); - NS::log::NATIVE_CL = std::make_shared<ColoredLogger>("NATIVE CL", NS::Colors::NATIVE_CL); - NS::log::NATIVE_SV = std::make_shared<ColoredLogger>("NATIVE SV", NS::Colors::NATIVE_SV); - NS::log::NATIVE_EN = std::make_shared<ColoredLogger>("NATIVE EN", NS::Colors::NATIVE_ENGINE); - - NS::log::fs = std::make_shared<ColoredLogger>("FILESYSTM", NS::Colors::FILESYSTEM); - NS::log::rpak = std::make_shared<ColoredLogger>("RPAK_FSYS", NS::Colors::RPAK); - NS::log::echo = std::make_shared<ColoredLogger>("ECHO", NS::Colors::ECHO); - - NS::log::PLUGINSYS = std::make_shared<ColoredLogger>("PLUGINSYS", NS::Colors::PLUGINSYS); - - loggers.push_back(NS::log::SCRIPT_UI); - loggers.push_back(NS::log::SCRIPT_CL); - loggers.push_back(NS::log::SCRIPT_SV); - - loggers.push_back(NS::log::NATIVE_UI); - loggers.push_back(NS::log::NATIVE_CL); - loggers.push_back(NS::log::NATIVE_SV); - loggers.push_back(NS::log::NATIVE_EN); - - loggers.push_back(NS::log::PLUGINSYS); - - loggers.push_back(NS::log::fs); - loggers.push_back(NS::log::rpak); - loggers.push_back(NS::log::echo); -} - -void NS::log::FlushLoggers() -{ - for (auto& logger : loggers) - logger->flush(); - - spdlog::default_logger()->flush(); -} - -// Wine specific functions -typedef const char*(CDECL* wine_get_host_version_type)(const char**, const char**); -wine_get_host_version_type wine_get_host_version; - -typedef const char*(CDECL* wine_get_build_id_type)(void); -wine_get_build_id_type wine_get_build_id; - -// Not exported Winapi methods -typedef NTSTATUS(WINAPI* RtlGetVersion_type)(PRTL_OSVERSIONINFOW); -RtlGetVersion_type RtlGetVersion; - -void StartupLog() -{ - spdlog::info("NorthstarLauncher version: {}", version); - spdlog::info("Command line: {}", GetCommandLineA()); - spdlog::info("Using profile: {}", GetNorthstarPrefix()); - - HMODULE ntdll = GetModuleHandleA("ntdll.dll"); - if (!ntdll) - { - // How did we get here - spdlog::info("Operating System: Unknown"); - return; - } - - wine_get_host_version = (wine_get_host_version_type)GetProcAddress(ntdll, "wine_get_host_version"); - if (wine_get_host_version) - { - // Load the rest of the functions we need - wine_get_build_id = (wine_get_build_id_type)GetProcAddress(ntdll, "wine_get_build_id"); - - const char* sysname; - wine_get_host_version(&sysname, NULL); - - spdlog::info("Operating System: {} (Wine)", sysname); - spdlog::info("Wine build: {}", wine_get_build_id()); - - // STEAM_COMPAT_TOOL_PATHS is a colon separated lists of all compat tool paths used - // The first one tends to be the Proton path itself - // We extract the basename out of it to get the name used - char* compatToolPtr = std::getenv("STEAM_COMPAT_TOOL_PATHS"); - if (compatToolPtr) - { - std::string_view compatToolPath(compatToolPtr); - - auto protonBasenameEnd = compatToolPath.find(":"); - if (protonBasenameEnd == std::string_view::npos) - protonBasenameEnd = 0; - auto protonBasenameStart = compatToolPath.rfind("/", protonBasenameEnd) + 1; - if (protonBasenameStart == std::string_view::npos) - protonBasenameStart = 0; - - spdlog::info("Proton build: {}", compatToolPath.substr(protonBasenameStart, protonBasenameEnd - protonBasenameStart)); - } - } - else - { - // We are real Windows (hopefully) - const char* win_ver = "Unknown"; - - RTL_OSVERSIONINFOW osvi; - osvi.dwOSVersionInfoSize = sizeof(osvi); - - RtlGetVersion = (RtlGetVersion_type)GetProcAddress(ntdll, "RtlGetVersion"); - if (RtlGetVersion && !RtlGetVersion(&osvi)) - { - // Version reference table - // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks - spdlog::info("Operating System: Windows (NT{}.{})", osvi.dwMajorVersion, osvi.dwMinorVersion); - } - else - { - spdlog::info("Operating System: Windows"); - } - } -} diff --git a/NorthstarDLL/logging/logging.h b/NorthstarDLL/logging/logging.h deleted file mode 100644 index 5056af27..00000000 --- a/NorthstarDLL/logging/logging.h +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once -#include "spdlog/sinks/base_sink.h" -#include "spdlog/logger.h" -#include "squirrel/squirrel.h" -#include "core/math/color.h" - -void CreateLogFiles(); -void InitialiseLogging(); -void InitialiseConsole(); -void StartupLog(); - -class ColoredLogger; - -struct custom_log_msg : spdlog::details::log_msg -{ -public: - custom_log_msg(ColoredLogger* origin, spdlog::details::log_msg msg) : origin(origin), spdlog::details::log_msg(msg) {} - - ColoredLogger* origin; -}; - -class CustomSink : public spdlog::sinks::base_sink<std::mutex> -{ -public: - void custom_log(const custom_log_msg& msg); - virtual void custom_sink_it_(const custom_log_msg& msg) - { - throw std::runtime_error("Pure virtual call to CustomSink::custom_sink_it_"); - } -}; - -class ColoredLogger : public spdlog::logger -{ -public: - std::string ANSIColor; - SourceColor SRCColor; - - std::vector<std::shared_ptr<CustomSink>> custom_sinks_; - - ColoredLogger(std::string name, Color color, bool first = false) : spdlog::logger(*spdlog::default_logger()) - { - name_ = std::move(name); - if (!first) - { - custom_sinks_ = dynamic_pointer_cast<ColoredLogger>(spdlog::default_logger())->custom_sinks_; - } - - ANSIColor = color.ToANSIColor(); - SRCColor = color.ToSourceColor(); - } - - void sink_it_(const spdlog::details::log_msg& msg) - { - custom_log_msg custom_msg {this, msg}; - - // Ugh - for (auto& sink : sinks_) - { - SPDLOG_TRY - { - sink->log(custom_msg); - } - SPDLOG_LOGGER_CATCH() - } - - for (auto& sink : custom_sinks_) - { - SPDLOG_TRY - { - sink->custom_log(custom_msg); - } - SPDLOG_LOGGER_CATCH() - } - - if (should_flush_(custom_msg)) - { - flush_(); - } - } -}; - -namespace NS::log -{ - // Squirrel - extern std::shared_ptr<ColoredLogger> SCRIPT_UI; - extern std::shared_ptr<ColoredLogger> SCRIPT_CL; - extern std::shared_ptr<ColoredLogger> SCRIPT_SV; - - // Native code - extern std::shared_ptr<ColoredLogger> NATIVE_UI; - extern std::shared_ptr<ColoredLogger> NATIVE_CL; - extern std::shared_ptr<ColoredLogger> NATIVE_SV; - extern std::shared_ptr<ColoredLogger> NATIVE_EN; - - // File system - extern std::shared_ptr<ColoredLogger> fs; - // RPak - extern std::shared_ptr<ColoredLogger> rpak; - // Echo - extern std::shared_ptr<ColoredLogger> echo; - - extern std::shared_ptr<ColoredLogger> NORTHSTAR; - - extern std::shared_ptr<ColoredLogger> PLUGINSYS; - - void FlushLoggers(); -}; // namespace NS::log - -void RegisterCustomSink(std::shared_ptr<CustomSink> sink); -void RegisterLogger(std::shared_ptr<ColoredLogger> logger); - -inline bool g_bSpdLog_UseAnsiColor = true; - -// Could maybe use some different names here, idk -static const char* level_names[] {"trac", "dbug", "info", "warn", "errr", "crit", "off"}; - -// spdlog logger, for cool colour things -class ExternalConsoleSink : public CustomSink -{ -private: - std::map<spdlog::level::level_enum, std::string> m_LogColours = { - {spdlog::level::trace, NS::Colors::TRACE.ToANSIColor()}, - {spdlog::level::debug, NS::Colors::DEBUG.ToANSIColor()}, - {spdlog::level::info, NS::Colors::INFO.ToANSIColor()}, - {spdlog::level::warn, NS::Colors::WARN.ToANSIColor()}, - {spdlog::level::err, NS::Colors::ERR.ToANSIColor()}, - {spdlog::level::critical, NS::Colors::CRIT.ToANSIColor()}, - {spdlog::level::off, NS::Colors::OFF.ToANSIColor()}}; - - std::string default_color = "\033[39;49m"; - -protected: - void sink_it_(const spdlog::details::log_msg& msg) override; - void custom_sink_it_(const custom_log_msg& msg); - void flush_() override; -}; diff --git a/NorthstarDLL/logging/loghooks.cpp b/NorthstarDLL/logging/loghooks.cpp deleted file mode 100644 index 7efb5b99..00000000 --- a/NorthstarDLL/logging/loghooks.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include "logging.h" -#include "loghooks.h" -#include "core/convar/convar.h" -#include "core/convar/concommand.h" -#include "core/math/bitbuf.h" -#include "config/profile.h" -#include "core/tier0.h" -#include "squirrel/squirrel.h" -#include <iomanip> -#include <sstream> - -AUTOHOOK_INIT() - -ConVar* Cvar_spewlog_enable; -ConVar* Cvar_cl_showtextmsg; - -enum class TextMsgPrintType_t -{ - HUD_PRINTNOTIFY = 1, - HUD_PRINTCONSOLE, - HUD_PRINTTALK, - HUD_PRINTCENTER -}; - -class ICenterPrint -{ -public: - virtual void ctor() = 0; - virtual void Clear(void) = 0; - virtual void ColorPrint(int r, int g, int b, int a, wchar_t* text) = 0; - virtual void ColorPrint(int r, int g, int b, int a, char* text) = 0; - virtual void Print(wchar_t* text) = 0; - virtual void Print(char* text) = 0; - virtual void SetTextColor(int r, int g, int b, int a) = 0; -}; - -enum class SpewType_t -{ - SPEW_MESSAGE = 0, - - SPEW_WARNING, - SPEW_ASSERT, - SPEW_ERROR, - SPEW_LOG, - - SPEW_TYPE_COUNT -}; - -const std::unordered_map<SpewType_t, const char*> PrintSpewTypes = { - {SpewType_t::SPEW_MESSAGE, "SPEW_MESSAGE"}, - {SpewType_t::SPEW_WARNING, "SPEW_WARNING"}, - {SpewType_t::SPEW_ASSERT, "SPEW_ASSERT"}, - {SpewType_t::SPEW_ERROR, "SPEW_ERROR"}, - {SpewType_t::SPEW_LOG, "SPEW_LOG"}}; - -// these are used to define the base text colour for these things -const std::unordered_map<SpewType_t, spdlog::level::level_enum> PrintSpewLevels = { - {SpewType_t::SPEW_MESSAGE, spdlog::level::level_enum::info}, - {SpewType_t::SPEW_WARNING, spdlog::level::level_enum::warn}, - {SpewType_t::SPEW_ASSERT, spdlog::level::level_enum::err}, - {SpewType_t::SPEW_ERROR, spdlog::level::level_enum::err}, - {SpewType_t::SPEW_LOG, spdlog::level::level_enum::info}}; - -const std::unordered_map<SpewType_t, const char> PrintSpewTypes_Short = { - {SpewType_t::SPEW_MESSAGE, 'M'}, - {SpewType_t::SPEW_WARNING, 'W'}, - {SpewType_t::SPEW_ASSERT, 'A'}, - {SpewType_t::SPEW_ERROR, 'E'}, - {SpewType_t::SPEW_LOG, 'L'}}; - -ICenterPrint* pInternalCenterPrint = NULL; - -// clang-format off -AUTOHOOK(TextMsg, client.dll + 0x198710, -void,, (BFRead* msg)) -// clang-format on -{ - TextMsgPrintType_t msg_dest = (TextMsgPrintType_t)msg->ReadByte(); - - char text[256]; - msg->ReadString(text, sizeof(text)); - - if (!Cvar_cl_showtextmsg->GetBool()) - return; - - switch (msg_dest) - { - case TextMsgPrintType_t::HUD_PRINTCENTER: - pInternalCenterPrint->Print(text); - break; - - default: - spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest); - [[fallthrough]]; - - case TextMsgPrintType_t::HUD_PRINTCONSOLE: - auto endpos = strlen(text); - if (text[endpos - 1] == '\n') - text[endpos - 1] = '\0'; // cut off repeated newline - - spdlog::info(text); - break; - } -} - -// clang-format off -AUTOHOOK(Hook_fprintf, engine.dll + 0x51B1F0, -int,, (void* const stream, const char* const format, ...)) -// clang-format on -{ - va_list va; - va_start(va, format); - - SQChar buf[1024]; - int charsWritten = vsnprintf_s(buf, _TRUNCATE, format, va); - - if (charsWritten > 0) - { - if (buf[charsWritten - 1] == '\n') - buf[charsWritten - 1] = '\0'; - NS::log::NATIVE_EN->info("{}", buf); - } - - va_end(va); - return 0; -} - -// clang-format off -AUTOHOOK(ConCommand_echo, engine.dll + 0x123680, -void,, (const CCommand& arg)) -// clang-format on -{ - if (arg.ArgC() >= 2) - NS::log::echo->info("{}", arg.ArgS()); -} - -// clang-format off -AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80, -void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_list args)) -// clang-format on -{ - if (!Cvar_spewlog_enable->GetBool()) - return; - - const char* typeStr = PrintSpewTypes.at(type); - char formatted[2048] = {0}; - bool bShouldFormat = true; - - // because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash - // ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds - // so we use this instead - for (int i = 0; format[i]; i++) - { - if (format[i] == '%') - { - switch (format[i + 1]) - { - // this is fucking awful lol - case 'd': - case 'i': - case 'u': - case 'x': - case 'X': - case 'f': - case 'F': - case 'g': - case 'G': - case 'a': - case 'A': - case 'c': - case 's': - case 'p': - case 'n': - case '%': - case '-': - case '+': - case ' ': - case '#': - case '*': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - break; - - default: - { - bShouldFormat = false; - break; - } - } - } - } - - if (bShouldFormat) - vsnprintf(formatted, sizeof(formatted), format, args); - else - spdlog::warn("Failed to format {} \"{}\"", typeStr, format); - - auto endpos = strlen(formatted); - if (formatted[endpos - 1] == '\n') - formatted[endpos - 1] = '\0'; // cut off repeated newline - - NS::log::NATIVE_SV->log(PrintSpewLevels.at(type), "{}", formatted); -} - -// used for printing the output of status -// clang-format off -AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0, -void,, (const char* text, ...)) -// clang-format on -{ - char formatted[2048]; - va_list list; - - va_start(list, text); - vsprintf_s(formatted, text, list); - va_end(list); - - auto endpos = strlen(formatted); - if (formatted[endpos - 1] == '\n') - formatted[endpos - 1] = '\0'; // cut off repeated newline - - spdlog::info(formatted); -} - -// clang-format off -AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530, -bool,, (void* thisptr, uintptr_t msg)) -// clang-format on -{ - char* text = *(char**)(msg + 0x20); - - auto endpos = strlen(text); - if (text[endpos - 1] == '\n') - text[endpos - 1] = '\0'; // cut off repeated newline - - spdlog::info(text); - return true; -} - -ON_DLL_LOAD_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - Cvar_spewlog_enable = new ConVar("spewlog_enable", "0", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged"); -} - -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(client.dll) - - Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen."); - pInternalCenterPrint = module.Offset(0x216E940).RCast<ICenterPrint*>(); -} diff --git a/NorthstarDLL/logging/loghooks.h b/NorthstarDLL/logging/loghooks.h deleted file mode 100644 index 6f70f09b..00000000 --- a/NorthstarDLL/logging/loghooks.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/NorthstarDLL/logging/sourceconsole.cpp b/NorthstarDLL/logging/sourceconsole.cpp deleted file mode 100644 index e436d1d4..00000000 --- a/NorthstarDLL/logging/sourceconsole.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "core/convar/convar.h" -#include "sourceconsole.h" -#include "core/sourceinterface.h" -#include "core/convar/concommand.h" -#include "util/printcommands.h" - -SourceInterface<CGameConsole>* g_pSourceGameConsole; - -void ConCommand_toggleconsole(const CCommand& arg) -{ - if ((*g_pSourceGameConsole)->IsConsoleVisible()) - (*g_pSourceGameConsole)->Hide(); - else - (*g_pSourceGameConsole)->Activate(); -} - -void ConCommand_showconsole(const CCommand& arg) -{ - (*g_pSourceGameConsole)->Activate(); -} - -void ConCommand_hideconsole(const CCommand& arg) -{ - (*g_pSourceGameConsole)->Hide(); -} - -void SourceConsoleSink::custom_sink_it_(const custom_log_msg& msg) -{ - if (!(*g_pSourceGameConsole)->m_bInitialized) - return; - - spdlog::memory_buf_t formatted; - spdlog::sinks::base_sink<std::mutex>::formatter_->format(msg, formatted); - - // get message string - std::string str = fmt::to_string(formatted); - - SourceColor levelColor = m_LogColours[msg.level]; - std::string name {msg.logger_name.begin(), msg.logger_name.end()}; - - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->ColorPrint(msg.origin->SRCColor, ("[" + name + "]").c_str()); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->Print(" "); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->ColorPrint(levelColor, ("[" + std::string(level_names[msg.level]) + "]").c_str()); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->Print(" "); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->Print(fmt::to_string(formatted).c_str()); -} - -void SourceConsoleSink::sink_it_(const spdlog::details::log_msg& msg) -{ - throw std::runtime_error("sink_it_ called on SourceConsoleSink with pure log_msg. This is an error!"); -} - -void SourceConsoleSink::flush_() {} - -// clang-format off -HOOK(OnCommandSubmittedHook, OnCommandSubmitted, -void, __fastcall, (CConsoleDialog* consoleDialog, const char* pCommand)) -// clang-format on -{ - consoleDialog->m_pConsolePanel->Print("] "); - consoleDialog->m_pConsolePanel->Print(pCommand); - consoleDialog->m_pConsolePanel->Print("\n"); - - TryPrintCvarHelpForCommand(pCommand); - - OnCommandSubmitted(consoleDialog, pCommand); -} - -// called from sourceinterface.cpp in client createinterface hooks, on GameClientExports001 -void InitialiseConsoleOnInterfaceCreation() -{ - (*g_pSourceGameConsole)->Initialize(); - // hook OnCommandSubmitted so we print inputted commands - OnCommandSubmittedHook.Dispatch((LPVOID)(*g_pSourceGameConsole)->m_pConsole->m_vtable->OnCommandSubmitted); - - auto consoleSink = std::make_shared<SourceConsoleSink>(); - if (g_bSpdLog_UseAnsiColor) - consoleSink->set_pattern("%v"); // no need to include the level in the game console, the text colour signifies it anyway - else - consoleSink->set_pattern("[%n] [%l] %v"); // no colour, so we should show the level for colourblind people - RegisterCustomSink(consoleSink); -} - -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", SourceConsole, ConCommand, (CModule module)) -{ - g_pSourceGameConsole = new SourceInterface<CGameConsole>("client.dll", "GameConsole004"); - - RegisterConCommand("toggleconsole", ConCommand_toggleconsole, "Show/hide the console.", FCVAR_DONTRECORD); - RegisterConCommand("showconsole", ConCommand_showconsole, "Show the console.", FCVAR_DONTRECORD); - RegisterConCommand("hideconsole", ConCommand_hideconsole, "Hide the console.", FCVAR_DONTRECORD); -} diff --git a/NorthstarDLL/logging/sourceconsole.h b/NorthstarDLL/logging/sourceconsole.h deleted file mode 100644 index 44d73843..00000000 --- a/NorthstarDLL/logging/sourceconsole.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -#include "core/sourceinterface.h" -#include "spdlog/sinks/base_sink.h" -#include <map> - -class EditablePanel -{ -public: - virtual ~EditablePanel() = 0; - unsigned char unknown[0x2B0]; -}; - -class IConsoleDisplayFunc -{ -public: - virtual void ColorPrint(const SourceColor& clr, const char* pMessage) = 0; - virtual void Print(const char* pMessage) = 0; - virtual void DPrint(const char* pMessage) = 0; -}; - -class CConsolePanel : public EditablePanel, public IConsoleDisplayFunc -{ -}; - -class CConsoleDialog -{ -public: - struct VTable - { - void* unknown[298]; - void (*OnCommandSubmitted)(CConsoleDialog* consoleDialog, const char* pCommand); - }; - - VTable* m_vtable; - unsigned char unknown[0x398]; - CConsolePanel* m_pConsolePanel; -}; - -class CGameConsole -{ -public: - virtual ~CGameConsole() = 0; - - // activates the console, makes it visible and brings it to the foreground - virtual void Activate() = 0; - - virtual void Initialize() = 0; - - // hides the console - virtual void Hide() = 0; - - // clears the console - virtual void Clear() = 0; - - // return true if the console has focus - virtual bool IsConsoleVisible() = 0; - - virtual void SetParent(int parent) = 0; - - bool m_bInitialized; - CConsoleDialog* m_pConsole; -}; - -extern SourceInterface<CGameConsole>* g_pSourceGameConsole; - -// spdlog logger -class SourceConsoleSink : public CustomSink -{ -private: - std::map<spdlog::level::level_enum, SourceColor> m_LogColours = { - {spdlog::level::trace, NS::Colors::TRACE.ToSourceColor()}, - {spdlog::level::debug, NS::Colors::DEBUG.ToSourceColor()}, - {spdlog::level::info, NS::Colors::INFO.ToSourceColor()}, - {spdlog::level::warn, NS::Colors::WARN.ToSourceColor()}, - {spdlog::level::err, NS::Colors::ERR.ToSourceColor()}, - {spdlog::level::critical, NS::Colors::CRIT.ToSourceColor()}, - {spdlog::level::off, NS::Colors::OFF.ToSourceColor()}}; - -protected: - void custom_sink_it_(const custom_log_msg& msg); - void sink_it_(const spdlog::details::log_msg& msg) override; - void flush_() override; -}; - -void InitialiseConsoleOnInterfaceCreation(); diff --git a/NorthstarDLL/masterserver/masterserver.cpp b/NorthstarDLL/masterserver/masterserver.cpp deleted file mode 100644 index aa248464..00000000 --- a/NorthstarDLL/masterserver/masterserver.cpp +++ /dev/null @@ -1,1459 +0,0 @@ -#include "masterserver/masterserver.h" -#include "core/convar/concommand.h" -#include "shared/playlist.h" -#include "server/auth/serverauthentication.h" -#include "core/tier0.h" -#include "core/vanilla.h" -#include "engine/r2engine.h" -#include "mods/modmanager.h" -#include "shared/misccommands.h" -#include "util/version.h" -#include "server/auth/bansystem.h" -#include "dedicated/dedicated.h" - -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/writer.h" -#include "rapidjson/error/en.h" - -#include <cstring> -#include <regex> - -using namespace std::chrono_literals; - -MasterServerManager* g_pMasterServerManager; - -ConVar* Cvar_ns_masterserver_hostname; -ConVar* Cvar_ns_curl_log_enable; - -RemoteServerInfo::RemoteServerInfo( - const char* newId, - const char* newName, - const char* newDescription, - const char* newMap, - const char* newPlaylist, - const char* newRegion, - int newPlayerCount, - int newMaxPlayers, - bool newRequiresPassword) -{ - // passworded servers don't have public ips - requiresPassword = newRequiresPassword; - - strncpy_s((char*)id, sizeof(id), newId, sizeof(id) - 1); - strncpy_s((char*)name, sizeof(name), newName, sizeof(name) - 1); - - description = std::string(newDescription); - - strncpy_s((char*)map, sizeof(map), newMap, sizeof(map) - 1); - strncpy_s((char*)playlist, sizeof(playlist), newPlaylist, sizeof(playlist) - 1); - - strncpy((char*)region, newRegion, sizeof(region)); - region[sizeof(region) - 1] = 0; - - playerCount = newPlayerCount; - maxPlayers = newMaxPlayers; -} - -void SetCommonHttpClientOptions(CURL* curl) -{ - curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - curl_easy_setopt(curl, CURLOPT_VERBOSE, Cvar_ns_curl_log_enable->GetBool()); - curl_easy_setopt(curl, CURLOPT_USERAGENT, &NSUserAgent); - // Timeout since the MS has fucky async functions without await, making curl hang due to a successful connection but no response for ~90 - // seconds. - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); - // curl_easy_setopt(curl, CURLOPT_STDERR, stdout); - if (CommandLine()->FindParm("-msinsecure")) // TODO: this check doesn't seem to work - { - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - } -} - -void MasterServerManager::ClearServerList() -{ - // this doesn't really do anything lol, probably isn't threadsafe - m_bRequestingServerList = true; - - m_vRemoteServers.clear(); - - m_bRequestingServerList = false; -} - -size_t CurlWriteToStringBufferCallback(char* contents, size_t size, size_t nmemb, void* userp) -{ - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; -} - -void MasterServerManager::AuthenticateOriginWithMasterServer(const char* uid, const char* originToken) -{ - if (m_bOriginAuthWithMasterServerInProgress || g_pVanillaCompatibility->GetVanillaCompatibility()) - return; - - // do this here so it's instantly set - m_bOriginAuthWithMasterServerInProgress = true; - std::string uidStr(uid); - std::string tokenStr(originToken); - - m_bOriginAuthWithMasterServerSuccessful = false; - m_sOriginAuthWithMasterServerErrorCode = ""; - m_sOriginAuthWithMasterServerErrorMessage = ""; - - std::thread requestThread( - [this, uidStr, tokenStr]() - { - spdlog::info("Trying to authenticate with northstar masterserver for user {}", uidStr); - - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - std::string readBuffer; - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format("{}/client/origin_auth?id={}&token={}", Cvar_ns_masterserver_hostname->GetString(), uidStr, tokenStr).c_str()); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - m_bSuccessfullyConnected = true; - - rapidjson_document originAuthInfo; - originAuthInfo.Parse(readBuffer.c_str()); - - if (originAuthInfo.HasParseError()) - { - spdlog::error( - "Failed reading origin auth info response: encountered parse error \"{}\"", - rapidjson::GetParseError_En(originAuthInfo.GetParseError())); - goto REQUEST_END_CLEANUP; - } - - if (!originAuthInfo.IsObject() || !originAuthInfo.HasMember("success")) - { - spdlog::error("Failed reading origin auth info response: malformed response object {}", readBuffer); - goto REQUEST_END_CLEANUP; - } - - if (originAuthInfo["success"].IsTrue() && originAuthInfo.HasMember("token") && originAuthInfo["token"].IsString()) - { - strncpy_s( - m_sOwnClientAuthToken, - sizeof(m_sOwnClientAuthToken), - originAuthInfo["token"].GetString(), - sizeof(m_sOwnClientAuthToken) - 1); - spdlog::info("Northstar origin authentication completed successfully!"); - m_bOriginAuthWithMasterServerSuccessful = true; - } - else - { - spdlog::error("Northstar origin authentication failed"); - - if (originAuthInfo.HasMember("error") && originAuthInfo["error"].IsObject()) - { - - if (originAuthInfo["error"].HasMember("enum") && originAuthInfo["error"]["enum"].IsString()) - { - m_sOriginAuthWithMasterServerErrorCode = originAuthInfo["error"]["enum"].GetString(); - } - - if (originAuthInfo["error"].HasMember("msg") && originAuthInfo["error"]["msg"].IsString()) - { - m_sOriginAuthWithMasterServerErrorMessage = originAuthInfo["error"]["msg"].GetString(); - } - } - } - } - else - { - spdlog::error("Failed performing northstar origin auth: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; - } - - // we goto this instead of returning so we always hit this - REQUEST_END_CLEANUP: - m_bOriginAuthWithMasterServerInProgress = false; - m_bOriginAuthWithMasterServerDone = true; - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::RequestServerList() -{ - // do this here so it's instantly set on call for scripts - m_bScriptRequestingServerList = true; - - std::thread requestThread( - [this]() - { - // make sure we never have 2 threads writing at once - // i sure do hope this is actually threadsafe - while (m_bRequestingServerList) - Sleep(100); - - m_bRequestingServerList = true; - m_bScriptRequestingServerList = true; - - spdlog::info("Requesting server list from {}", Cvar_ns_masterserver_hostname->GetString()); - - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/client/servers", Cvar_ns_masterserver_hostname->GetString()).c_str()); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - m_bSuccessfullyConnected = true; - - rapidjson_document serverInfoJson; - serverInfoJson.Parse(readBuffer.c_str()); - - if (serverInfoJson.HasParseError()) - { - spdlog::error( - "Failed reading masterserver response: encountered parse error \"{}\"", - rapidjson::GetParseError_En(serverInfoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } - - if (serverInfoJson.IsObject() && serverInfoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(readBuffer); - goto REQUEST_END_CLEANUP; - } - - if (!serverInfoJson.IsArray()) - { - spdlog::error("Failed reading masterserver response: root object is not an array"); - goto REQUEST_END_CLEANUP; - } - - rapidjson::GenericArray<false, rapidjson_document::GenericValue> serverArray = serverInfoJson.GetArray(); - - spdlog::info("Got {} servers", serverArray.Size()); - - for (auto& serverObj : serverArray) - { - if (!serverObj.IsObject()) - { - spdlog::error("Failed reading masterserver response: member of server array is not an object"); - goto REQUEST_END_CLEANUP; - } - - // todo: verify json props are fine before adding to m_remoteServers - if (!serverObj.HasMember("id") || !serverObj["id"].IsString() || !serverObj.HasMember("name") || - !serverObj["name"].IsString() || !serverObj.HasMember("description") || !serverObj["description"].IsString() || - !serverObj.HasMember("map") || !serverObj["map"].IsString() || !serverObj.HasMember("playlist") || - !serverObj["playlist"].IsString() || !serverObj.HasMember("playerCount") || !serverObj["playerCount"].IsNumber() || - !serverObj.HasMember("maxPlayers") || !serverObj["maxPlayers"].IsNumber() || !serverObj.HasMember("hasPassword") || - !serverObj["hasPassword"].IsBool() || !serverObj.HasMember("modInfo") || !serverObj["modInfo"].HasMember("Mods") || - !serverObj["modInfo"]["Mods"].IsArray()) - { - spdlog::error("Failed reading masterserver response: malformed server object"); - continue; - }; - - const char* id = serverObj["id"].GetString(); - - RemoteServerInfo* newServer = nullptr; - - bool createNewServerInfo = true; - for (RemoteServerInfo& server : m_vRemoteServers) - { - // if server already exists, update info rather than adding to it - if (!strncmp((const char*)server.id, id, 32)) - { - server = RemoteServerInfo( - id, - serverObj["name"].GetString(), - serverObj["description"].GetString(), - serverObj["map"].GetString(), - serverObj["playlist"].GetString(), - (serverObj.HasMember("region") && serverObj["region"].IsString()) ? serverObj["region"].GetString() : "", - serverObj["playerCount"].GetInt(), - serverObj["maxPlayers"].GetInt(), - serverObj["hasPassword"].IsTrue()); - newServer = &server; - createNewServerInfo = false; - break; - } - } - - // server didn't exist - if (createNewServerInfo) - newServer = &m_vRemoteServers.emplace_back( - id, - serverObj["name"].GetString(), - serverObj["description"].GetString(), - serverObj["map"].GetString(), - serverObj["playlist"].GetString(), - (serverObj.HasMember("region") && serverObj["region"].IsString()) ? serverObj["region"].GetString() : "", - serverObj["playerCount"].GetInt(), - serverObj["maxPlayers"].GetInt(), - serverObj["hasPassword"].IsTrue()); - - newServer->requiredMods.clear(); - for (auto& requiredMod : serverObj["modInfo"]["Mods"].GetArray()) - { - RemoteModInfo modInfo; - - if (!requiredMod.HasMember("RequiredOnClient") || !requiredMod["RequiredOnClient"].IsTrue()) - continue; - - if (!requiredMod.HasMember("Name") || !requiredMod["Name"].IsString()) - continue; - modInfo.Name = requiredMod["Name"].GetString(); - - if (!requiredMod.HasMember("Version") || !requiredMod["Version"].IsString()) - continue; - modInfo.Version = requiredMod["Version"].GetString(); - - newServer->requiredMods.push_back(modInfo); - } - // Can probably re-enable this later with a -verbose flag, but slows down loading of the server browser quite a bit as - // is - // spdlog::info( - // "Server {} on map {} with playlist {} has {}/{} players", serverObj["name"].GetString(), - // serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), - // serverObj["maxPlayers"].GetInt()); - } - - std::sort( - m_vRemoteServers.begin(), - m_vRemoteServers.end(), - [](RemoteServerInfo& a, RemoteServerInfo& b) { return a.playerCount > b.playerCount; }); - } - else - { - spdlog::error("Failed requesting servers: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; - } - - // we goto this instead of returning so we always hit this - REQUEST_END_CLEANUP: - m_bRequestingServerList = false; - m_bScriptRequestingServerList = false; - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::RequestMainMenuPromos() -{ - m_bHasMainMenuPromoData = false; - - std::thread requestThread( - [this]() - { - while (m_bOriginAuthWithMasterServerInProgress || !m_bOriginAuthWithMasterServerDone) - Sleep(500); - - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt( - curl, CURLOPT_URL, fmt::format("{}/client/mainmenupromos", Cvar_ns_masterserver_hostname->GetString()).c_str()); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - m_bSuccessfullyConnected = true; - - rapidjson_document mainMenuPromoJson; - mainMenuPromoJson.Parse(readBuffer.c_str()); - - if (mainMenuPromoJson.HasParseError()) - { - spdlog::error( - "Failed reading masterserver main menu promos response: encountered parse error \"{}\"", - rapidjson::GetParseError_En(mainMenuPromoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } - - if (!mainMenuPromoJson.IsObject()) - { - spdlog::error("Failed reading masterserver main menu promos response: root object is not an object"); - goto REQUEST_END_CLEANUP; - } - - if (mainMenuPromoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(readBuffer); - goto REQUEST_END_CLEANUP; - } - - if (!mainMenuPromoJson.HasMember("newInfo") || !mainMenuPromoJson["newInfo"].IsObject() || - !mainMenuPromoJson["newInfo"].HasMember("Title1") || !mainMenuPromoJson["newInfo"]["Title1"].IsString() || - !mainMenuPromoJson["newInfo"].HasMember("Title2") || !mainMenuPromoJson["newInfo"]["Title2"].IsString() || - !mainMenuPromoJson["newInfo"].HasMember("Title3") || !mainMenuPromoJson["newInfo"]["Title3"].IsString() || - - !mainMenuPromoJson.HasMember("largeButton") || !mainMenuPromoJson["largeButton"].IsObject() || - !mainMenuPromoJson["largeButton"].HasMember("Title") || !mainMenuPromoJson["largeButton"]["Title"].IsString() || - !mainMenuPromoJson["largeButton"].HasMember("Text") || !mainMenuPromoJson["largeButton"]["Text"].IsString() || - !mainMenuPromoJson["largeButton"].HasMember("Url") || !mainMenuPromoJson["largeButton"]["Url"].IsString() || - !mainMenuPromoJson["largeButton"].HasMember("ImageIndex") || - !mainMenuPromoJson["largeButton"]["ImageIndex"].IsNumber() || - - !mainMenuPromoJson.HasMember("smallButton1") || !mainMenuPromoJson["smallButton1"].IsObject() || - !mainMenuPromoJson["smallButton1"].HasMember("Title") || !mainMenuPromoJson["smallButton1"]["Title"].IsString() || - !mainMenuPromoJson["smallButton1"].HasMember("Url") || !mainMenuPromoJson["smallButton1"]["Url"].IsString() || - !mainMenuPromoJson["smallButton1"].HasMember("ImageIndex") || - !mainMenuPromoJson["smallButton1"]["ImageIndex"].IsNumber() || - - !mainMenuPromoJson.HasMember("smallButton2") || !mainMenuPromoJson["smallButton2"].IsObject() || - !mainMenuPromoJson["smallButton2"].HasMember("Title") || !mainMenuPromoJson["smallButton2"]["Title"].IsString() || - !mainMenuPromoJson["smallButton2"].HasMember("Url") || !mainMenuPromoJson["smallButton2"]["Url"].IsString() || - !mainMenuPromoJson["smallButton2"].HasMember("ImageIndex") || - !mainMenuPromoJson["smallButton2"]["ImageIndex"].IsNumber()) - { - spdlog::error("Failed reading masterserver main menu promos response: malformed json object"); - goto REQUEST_END_CLEANUP; - } - - m_sMainMenuPromoData.newInfoTitle1 = mainMenuPromoJson["newInfo"]["Title1"].GetString(); - m_sMainMenuPromoData.newInfoTitle2 = mainMenuPromoJson["newInfo"]["Title2"].GetString(); - m_sMainMenuPromoData.newInfoTitle3 = mainMenuPromoJson["newInfo"]["Title3"].GetString(); - - m_sMainMenuPromoData.largeButtonTitle = mainMenuPromoJson["largeButton"]["Title"].GetString(); - m_sMainMenuPromoData.largeButtonText = mainMenuPromoJson["largeButton"]["Text"].GetString(); - m_sMainMenuPromoData.largeButtonUrl = mainMenuPromoJson["largeButton"]["Url"].GetString(); - m_sMainMenuPromoData.largeButtonImageIndex = mainMenuPromoJson["largeButton"]["ImageIndex"].GetInt(); - - m_sMainMenuPromoData.smallButton1Title = mainMenuPromoJson["smallButton1"]["Title"].GetString(); - m_sMainMenuPromoData.smallButton1Url = mainMenuPromoJson["smallButton1"]["Url"].GetString(); - m_sMainMenuPromoData.smallButton1ImageIndex = mainMenuPromoJson["smallButton1"]["ImageIndex"].GetInt(); - - m_sMainMenuPromoData.smallButton2Title = mainMenuPromoJson["smallButton2"]["Title"].GetString(); - m_sMainMenuPromoData.smallButton2Url = mainMenuPromoJson["smallButton2"]["Url"].GetString(); - m_sMainMenuPromoData.smallButton2ImageIndex = mainMenuPromoJson["smallButton2"]["ImageIndex"].GetInt(); - - m_bHasMainMenuPromoData = true; - } - else - { - spdlog::error("Failed requesting main menu promos: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; - } - - REQUEST_END_CLEANUP: - // nothing lol - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::AuthenticateWithOwnServer(const char* uid, const char* playerToken) -{ - // dont wait, just stop if we're trying to do 2 auth requests at once - if (m_bAuthenticatingWithGameServer || g_pVanillaCompatibility->GetVanillaCompatibility()) - return; - - m_bAuthenticatingWithGameServer = true; - m_bScriptAuthenticatingWithGameServer = true; - m_bSuccessfullyAuthenticatedWithGameServer = false; - m_sAuthFailureReason = "Authentication Failed"; - - std::string uidStr(uid); - std::string tokenStr(playerToken); - - std::thread requestThread( - [this, uidStr, tokenStr]() - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format("{}/client/auth_with_self?id={}&playerToken={}", Cvar_ns_masterserver_hostname->GetString(), uidStr, tokenStr) - .c_str()); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - m_bSuccessfullyConnected = true; - - rapidjson_document authInfoJson; - authInfoJson.Parse(readBuffer.c_str()); - - if (authInfoJson.HasParseError()) - { - spdlog::error( - "Failed reading masterserver authentication response: encountered parse error \"{}\"", - rapidjson::GetParseError_En(authInfoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } - - if (!authInfoJson.IsObject()) - { - spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - goto REQUEST_END_CLEANUP; - } - - if (authInfoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(readBuffer); - - if (authInfoJson["error"].HasMember("msg")) - m_sAuthFailureReason = authInfoJson["error"]["msg"].GetString(); - else if (authInfoJson["error"].HasMember("enum")) - m_sAuthFailureReason = authInfoJson["error"]["enum"].GetString(); - else - m_sAuthFailureReason = "No error message provided"; - - goto REQUEST_END_CLEANUP; - } - - if (!authInfoJson["success"].IsTrue()) - { - spdlog::error("Authentication with masterserver failed: \"success\" is not true"); - goto REQUEST_END_CLEANUP; - } - - if (!authInfoJson.HasMember("success") || !authInfoJson.HasMember("id") || !authInfoJson["id"].IsString() || - !authInfoJson.HasMember("authToken") || !authInfoJson["authToken"].IsString() || - !authInfoJson.HasMember("persistentData") || !authInfoJson["persistentData"].IsArray()) - { - spdlog::error("Failed reading masterserver authentication response: malformed json object"); - goto REQUEST_END_CLEANUP; - } - - RemoteAuthData newAuthData {}; - strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), authInfoJson["id"].GetString(), sizeof(newAuthData.uid) - 1); - - newAuthData.pdataSize = authInfoJson["persistentData"].GetArray().Size(); - newAuthData.pdata = new char[newAuthData.pdataSize]; - // memcpy(newAuthData.pdata, authInfoJson["persistentData"].GetString(), newAuthData.pdataSize); - - int i = 0; - // note: persistentData is a uint8array because i had problems getting strings to behave, it sucks but it's just how it be - // unfortunately potentially refactor later - for (auto& byte : authInfoJson["persistentData"].GetArray()) - { - if (!byte.IsUint() || byte.GetUint() > 255) - { - spdlog::error("Failed reading masterserver authentication response: malformed json object"); - goto REQUEST_END_CLEANUP; - } - - newAuthData.pdata[i++] = static_cast<char>(byte.GetUint()); - } - - std::lock_guard<std::mutex> guard(g_pServerAuthentication->m_AuthDataMutex); - g_pServerAuthentication->m_RemoteAuthenticationData.clear(); - g_pServerAuthentication->m_RemoteAuthenticationData.insert( - std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); - - m_bSuccessfullyAuthenticatedWithGameServer = true; - } - else - { - spdlog::error("Failed authenticating with own server: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; - m_bSuccessfullyAuthenticatedWithGameServer = false; - m_bScriptAuthenticatingWithGameServer = false; - } - - REQUEST_END_CLEANUP: - m_bAuthenticatingWithGameServer = false; - m_bScriptAuthenticatingWithGameServer = false; - - if (m_bNewgameAfterSelfAuth) - { - // pretty sure this is threadsafe? - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", cmd_source_t::kCommandSrcCode); - m_bNewgameAfterSelfAuth = false; - } - - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::AuthenticateWithServer(const char* uid, const char* playerToken, RemoteServerInfo server, const char* password) -{ - // dont wait, just stop if we're trying to do 2 auth requests at once - if (m_bAuthenticatingWithGameServer || g_pVanillaCompatibility->GetVanillaCompatibility()) - return; - - m_bAuthenticatingWithGameServer = true; - m_bScriptAuthenticatingWithGameServer = true; - m_bSuccessfullyAuthenticatedWithGameServer = false; - m_sAuthFailureReason = "Authentication Failed"; - - std::string uidStr(uid); - std::string tokenStr(playerToken); - std::string serverIdStr(server.id); - std::string passwordStr(password); - - std::thread requestThread( - [this, uidStr, tokenStr, serverIdStr, passwordStr, server]() - { - // esnure that any persistence saving is done, so we know masterserver has newest - while (m_bSavingPersistentData) - Sleep(100); - - spdlog::info("Attempting authentication with server of id \"{}\"", serverIdStr); - - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - { - char* escapedPassword = curl_easy_escape(curl, passwordStr.c_str(), passwordStr.length()); - - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/client/auth_with_server?id={}&playerToken={}&server={}&password={}", - Cvar_ns_masterserver_hostname->GetString(), - uidStr, - tokenStr, - serverIdStr, - escapedPassword) - .c_str()); - - curl_free(escapedPassword); - } - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - m_bSuccessfullyConnected = true; - - rapidjson_document connectionInfoJson; - connectionInfoJson.Parse(readBuffer.c_str()); - - if (connectionInfoJson.HasParseError()) - { - spdlog::error( - "Failed reading masterserver authentication response: encountered parse error \"{}\"", - rapidjson::GetParseError_En(connectionInfoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } - - if (!connectionInfoJson.IsObject()) - { - spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - goto REQUEST_END_CLEANUP; - } - - if (connectionInfoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(readBuffer); - - if (connectionInfoJson["error"].HasMember("msg")) - m_sAuthFailureReason = connectionInfoJson["error"]["msg"].GetString(); - else if (connectionInfoJson["error"].HasMember("enum")) - m_sAuthFailureReason = connectionInfoJson["error"]["enum"].GetString(); - else - m_sAuthFailureReason = "No error message provided"; - - goto REQUEST_END_CLEANUP; - } - - if (!connectionInfoJson["success"].IsTrue()) - { - spdlog::error("Authentication with masterserver failed: \"success\" is not true"); - goto REQUEST_END_CLEANUP; - } - - if (!connectionInfoJson.HasMember("success") || !connectionInfoJson.HasMember("ip") || - !connectionInfoJson["ip"].IsString() || !connectionInfoJson.HasMember("port") || - !connectionInfoJson["port"].IsNumber() || !connectionInfoJson.HasMember("authToken") || - !connectionInfoJson["authToken"].IsString()) - { - spdlog::error("Failed reading masterserver authentication response: malformed json object"); - goto REQUEST_END_CLEANUP; - } - - m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(connectionInfoJson["ip"].GetString()); - m_pendingConnectionInfo.port = (unsigned short)connectionInfoJson["port"].GetUint(); - - strncpy_s( - m_pendingConnectionInfo.authToken, - sizeof(m_pendingConnectionInfo.authToken), - connectionInfoJson["authToken"].GetString(), - sizeof(m_pendingConnectionInfo.authToken) - 1); - - m_bHasPendingConnectionInfo = true; - m_bSuccessfullyAuthenticatedWithGameServer = true; - - m_currentServer = server; - m_sCurrentServerPassword = passwordStr; - } - else - { - spdlog::error("Failed authenticating with server: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; - m_bSuccessfullyAuthenticatedWithGameServer = false; - m_bScriptAuthenticatingWithGameServer = false; - } - - REQUEST_END_CLEANUP: - m_bAuthenticatingWithGameServer = false; - m_bScriptAuthenticatingWithGameServer = false; - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize) -{ - // still call this if we don't have a server id, since lobbies that aren't port forwarded need to be able to call it - m_bSavingPersistentData = true; - if (!pdataSize) - { - spdlog::warn("attempted to write pdata of size 0!"); - return; - } - - std::string strPlayerId(playerId); - std::string strPdata(pdata, pdataSize); - - std::thread requestThread( - [this, strPlayerId, strPdata, pdataSize] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/accounts/write_persistence?id={}&serverId={}", - Cvar_ns_masterserver_hostname->GetString(), - strPlayerId, - m_sOwnServerId) - .c_str()); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - curl_mime* mime = curl_mime_init(curl); - curl_mimepart* part = curl_mime_addpart(mime); - - curl_mime_data(part, strPdata.c_str(), pdataSize); - curl_mime_name(part, "pdata"); - curl_mime_filename(part, "file.pdata"); - curl_mime_type(part, "application/octet-stream"); - - curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - - m_bSavingPersistentData = false; - }); - - requestThread.detach(); -} - -void MasterServerManager::ProcessConnectionlessPacketSigreq1(std::string data) -{ - rapidjson_document obj; - obj.Parse(data); - - if (obj.HasParseError()) - { - // note: it's okay to print the data as-is since we've already checked that it actually came from Atlas - spdlog::error("invalid Atlas connectionless packet request ({}): {}", data, GetParseError_En(obj.GetParseError())); - return; - } - - if (!obj.HasMember("type") || !obj["type"].IsString()) - { - spdlog::error("invalid Atlas connectionless packet request ({}): missing type", data); - return; - } - - std::string type = obj["type"].GetString(); - - if (type == "connect") - { - if (!obj.HasMember("token") || !obj["token"].IsString()) - { - spdlog::error("failed to handle Atlas connect request: missing or invalid connection token field"); - return; - } - std::string token = obj["token"].GetString(); - - if (!m_handledServerConnections.contains(token)) - m_handledServerConnections.insert(token); - else - return; // already handled - - spdlog::info("handling Atlas connect request {}", data); - - if (!obj.HasMember("uid") || !obj["uid"].IsUint64()) - { - spdlog::error("failed to handle Atlas connect request {}: missing or invalid uid field", token); - return; - } - uint64_t uid = obj["uid"].GetUint64(); - - std::string username; - if (obj.HasMember("username") && obj["username"].IsString()) - username = obj["username"].GetString(); - - std::string reject; - if (!g_pBanSystem->IsUIDAllowed(uid)) - reject = "Banned from this server."; - - std::string pdata; - if (reject == "") - { - spdlog::info("getting pdata for connection {} (uid={} username={})", token, uid, username); - - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format("{}/server/connect?serverId={}&token={}", Cvar_ns_masterserver_hostname->GetString(), m_sOwnServerId, token) - .c_str()); - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &pdata); - - CURLcode result = curl_easy_perform(curl); - if (result != CURLcode::CURLE_OK) - { - spdlog::error("failed to make Atlas connect pdata request {}: {}", token, curl_easy_strerror(result)); - curl_easy_cleanup(curl); - return; - } - - long respStatus = -1; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &respStatus); - - curl_easy_cleanup(curl); - - if (respStatus != 200) - { - rapidjson_document obj; - obj.Parse(pdata.c_str()); - - if (!obj.HasParseError() && obj.HasMember("error") && obj["error"].IsObject()) - spdlog::error( - "failed to make Atlas connect pdata request {}: response status {}, error: {} ({})", - token, - respStatus, - ((obj["error"].HasMember("enum") && obj["error"]["enum"].IsString()) ? obj["error"]["enum"].GetString() : ""), - ((obj["error"].HasMember("msg") && obj["error"]["msg"].IsString()) ? obj["error"]["msg"].GetString() : "")); - else - spdlog::error("failed to make Atlas connect pdata request {}: response status {}", token, respStatus); - return; - } - - if (!pdata.length()) - { - spdlog::error("failed to make Atlas connect pdata request {}: pdata response is empty", token); - return; - } - - if (pdata.length() > PERSISTENCE_MAX_SIZE) - { - spdlog::error( - "failed to make Atlas connect pdata request {}: pdata is too large (max={} len={})", - token, - PERSISTENCE_MAX_SIZE, - pdata.length()); - return; - } - } - - if (reject == "") - spdlog::info("accepting connection {} (uid={} username={}) with {} bytes of pdata", token, uid, username, pdata.length()); - else - spdlog::info("rejecting connection {} (uid={} username={}) with reason \"{}\"", token, uid, username, reject); - - if (reject == "") - g_pServerAuthentication->AddRemotePlayer(token, uid, username, pdata); - - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - char* rejectEnc = curl_easy_escape(curl, reject.c_str(), reject.length()); - if (!rejectEnc) - { - spdlog::error("failed to handle Atlas connect request {}: failed to escape reject", token); - return; - } - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/connect?serverId={}&token={}&reject={}", - Cvar_ns_masterserver_hostname->GetString(), - m_sOwnServerId, - token, - rejectEnc) - .c_str()); - curl_free(rejectEnc); - - // note: we don't actually have any POST data, so we can't use CURLOPT_POST or the behavior is undefined (e.g., hangs in wine) - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - - std::string buf; - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf); - - CURLcode result = curl_easy_perform(curl); - if (result != CURLcode::CURLE_OK) - { - spdlog::error("failed to respond to Atlas connect request {}: {}", token, curl_easy_strerror(result)); - curl_easy_cleanup(curl); - return; - } - - long respStatus = -1; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &respStatus); - - curl_easy_cleanup(curl); - - if (respStatus != 200) - { - rapidjson_document obj; - obj.Parse(buf.c_str()); - - if (!obj.HasParseError() && obj.HasMember("error") && obj["error"].IsObject()) - spdlog::error( - "failed to respond to Atlas connect request {}: response status {}, error: {} ({})", - token, - respStatus, - ((obj["error"].HasMember("enum") && obj["error"]["enum"].IsString()) ? obj["error"]["enum"].GetString() : ""), - ((obj["error"].HasMember("msg") && obj["error"]["msg"].IsString()) ? obj["error"]["msg"].GetString() : "")); - else - spdlog::error("failed to respond to Atlas connect request {}: response status {}", token, respStatus); - return; - } - } - - return; - } - - spdlog::error("invalid Atlas connectionless packet request: unknown type {}", type); -} - -void ConCommand_ns_fetchservers(const CCommand& args) -{ - g_pMasterServerManager->RequestServerList(); -} - -MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} - -ON_DLL_LOAD_RELIESON("engine.dll", MasterServer, (ConCommand, ServerPresence), (CModule module)) -{ - g_pMasterServerManager = new MasterServerManager; - - Cvar_ns_masterserver_hostname = new ConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, ""); - Cvar_ns_curl_log_enable = new ConVar("ns_curl_log_enable", "0", FCVAR_NONE, "Whether curl should log to the console"); - - RegisterConCommand("ns_fetchservers", ConCommand_ns_fetchservers, "Fetch all servers from the masterserver", FCVAR_CLIENTDLL); - - MasterServerPresenceReporter* presenceReporter = new MasterServerPresenceReporter; - g_pServerPresence->AddPresenceReporter(presenceReporter); -} - -void MasterServerPresenceReporter::CreatePresence(const ServerPresence* pServerPresence) -{ - m_nNumRegistrationAttempts = 0; -} - -void MasterServerPresenceReporter::ReportPresence(const ServerPresence* pServerPresence) -{ - // make a copy of presence for multithreading purposes - ServerPresence threadedPresence(pServerPresence); - - if (!*g_pMasterServerManager->m_sOwnServerId) - { - // Don't try if we've reached the max registration attempts. - // In the future, we should probably allow servers to re-authenticate after a while if the MS was down. - if (m_nNumRegistrationAttempts >= MAX_REGISTRATION_ATTEMPTS) - { - return; - } - - // Make sure to wait til the cooldown is over for DUPLICATE_SERVER failures. - if (Plat_FloatTime() < m_fNextAddServerAttemptTime) - { - return; - } - - // If we're not running any InternalAddServer() attempt in the background. - if (!addServerFuture.valid()) - { - // Launch an attempt to add the local server to the master server. - InternalAddServer(pServerPresence); - } - } - else - { - // If we're not running any InternalUpdateServer() attempt in the background. - if (!updateServerFuture.valid()) - { - // Launch an attempt to update the local server on the master server. - InternalUpdateServer(pServerPresence); - } - } -} - -void MasterServerPresenceReporter::DestroyPresence(const ServerPresence* pServerPresence) -{ - // Don't call this if we don't have a server id. - if (!*g_pMasterServerManager->m_sOwnServerId) - { - return; - } - - // Not bothering with better thread safety in this case since DestroyPresence() is called when the game is shutting down. - *g_pMasterServerManager->m_sOwnServerId = 0; - - std::thread requestThread( - [this] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/remove_server?id={}", Cvar_ns_masterserver_hostname->GetString(), g_pMasterServerManager->m_sOwnServerId) - .c_str()); - - CURLcode result = curl_easy_perform(curl); - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerPresenceReporter::RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) -{ - // Check if we're already running an InternalAddServer() call in the background. - // If so, grab the result if it's ready. - if (addServerFuture.valid()) - { - std::future_status status = addServerFuture.wait_for(0ms); - if (status != std::future_status::ready) - { - // Still running, no need to do anything. - return; - } - - // Check the result. - auto resultData = addServerFuture.get(); - - g_pMasterServerManager->m_bSuccessfullyConnected = resultData.result != MasterServerReportPresenceResult::FailedNoConnect; - - switch (resultData.result) - { - case MasterServerReportPresenceResult::Success: - // Copy over the server id and auth token granted by the MS. - strncpy_s( - g_pMasterServerManager->m_sOwnServerId, - sizeof(g_pMasterServerManager->m_sOwnServerId), - resultData.id.value().c_str(), - sizeof(g_pMasterServerManager->m_sOwnServerId) - 1); - strncpy_s( - g_pMasterServerManager->m_sOwnServerAuthToken, - sizeof(g_pMasterServerManager->m_sOwnServerAuthToken), - resultData.serverAuthToken.value().c_str(), - sizeof(g_pMasterServerManager->m_sOwnServerAuthToken) - 1); - break; - case MasterServerReportPresenceResult::FailedNoRetry: - case MasterServerReportPresenceResult::FailedNoConnect: - // If we failed to connect to the master server, or failed with no retry, stop trying. - m_nNumRegistrationAttempts = MAX_REGISTRATION_ATTEMPTS; - break; - case MasterServerReportPresenceResult::Failed: - ++m_nNumRegistrationAttempts; - break; - case MasterServerReportPresenceResult::FailedDuplicateServer: - ++m_nNumRegistrationAttempts; - // Wait at least twenty seconds until we re-attempt to add the server. - m_fNextAddServerAttemptTime = Plat_FloatTime() + 20.0f; - break; - } - - if (m_nNumRegistrationAttempts >= MAX_REGISTRATION_ATTEMPTS) - { - spdlog::log( - IsDedicatedServer() ? spdlog::level::level_enum::err : spdlog::level::level_enum::warn, - "Reached max ms server registration attempts."); - } - } - else if (updateServerFuture.valid()) - { - // Check if the InternalUpdateServer() call completed. - std::future_status status = updateServerFuture.wait_for(0ms); - if (status != std::future_status::ready) - { - // Still running, no need to do anything. - return; - } - - auto resultData = updateServerFuture.get(); - if (resultData.result == MasterServerReportPresenceResult::Success) - { - if (resultData.id) - { - strncpy_s( - g_pMasterServerManager->m_sOwnServerId, - sizeof(g_pMasterServerManager->m_sOwnServerId), - resultData.id.value().c_str(), - sizeof(g_pMasterServerManager->m_sOwnServerId) - 1); - } - - if (resultData.serverAuthToken) - { - strncpy_s( - g_pMasterServerManager->m_sOwnServerAuthToken, - sizeof(g_pMasterServerManager->m_sOwnServerAuthToken), - resultData.serverAuthToken.value().c_str(), - sizeof(g_pMasterServerManager->m_sOwnServerAuthToken) - 1); - } - } - } -} - -void MasterServerPresenceReporter::InternalAddServer(const ServerPresence* pServerPresence) -{ - const ServerPresence threadedPresence(pServerPresence); - // Never call this with an ongoing InternalAddServer() call. - assert(!addServerFuture.valid()); - - g_pMasterServerManager->m_sOwnServerId[0] = 0; - g_pMasterServerManager->m_sOwnServerAuthToken[0] = 0; - - std::string modInfo = g_pMasterServerManager->m_sOwnModInfoJson; - std::string hostname = Cvar_ns_masterserver_hostname->GetString(); - - spdlog::info("Attempting to register the local server to the master server."); - - addServerFuture = std::async( - std::launch::async, - [threadedPresence, modInfo, hostname, pServerPresence] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - curl_mime* mime = curl_mime_init(curl); - curl_mimepart* part = curl_mime_addpart(mime); - - // Lambda to quickly cleanup resources and return a value. - auto ReturnCleanup = - [curl, mime](MasterServerReportPresenceResult result, const char* id = "", const char* serverAuthToken = "") - { - curl_easy_cleanup(curl); - curl_mime_free(mime); - - MasterServerPresenceReporter::ReportPresenceResultData data; - data.result = result; - data.id = id; - data.serverAuthToken = serverAuthToken; - - return data; - }; - - // don't log errors if we wouldn't actually show up in the server list anyway (stop tickets) - // except for dedis, for which this error logging is actually pretty important - bool shouldLogError = IsDedicatedServer() || (!strstr(pServerPresence->m_MapName, "mp_lobby") && - strstr(pServerPresence->m_PlaylistName, "private_match")); - - curl_mime_data(part, modInfo.c_str(), modInfo.size()); - curl_mime_name(part, "modinfo"); - curl_mime_filename(part, "modinfo.json"); - curl_mime_type(part, "application/json"); - - curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); - - // format every paramter because computers hate me - { - char* nameEscaped = curl_easy_escape(curl, threadedPresence.m_sServerName.c_str(), 0); - char* descEscaped = curl_easy_escape(curl, threadedPresence.m_sServerDesc.c_str(), 0); - char* mapEscaped = curl_easy_escape(curl, threadedPresence.m_MapName, 0); - char* playlistEscaped = curl_easy_escape(curl, threadedPresence.m_PlaylistName, 0); - char* passwordEscaped = curl_easy_escape(curl, threadedPresence.m_Password, 0); - - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/" - "add_server?port={}&authPort=udp&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", - hostname.c_str(), - threadedPresence.m_iPort, - nameEscaped, - descEscaped, - mapEscaped, - playlistEscaped, - threadedPresence.m_iMaxPlayers, - passwordEscaped) - .c_str()); - - curl_free(nameEscaped); - curl_free(descEscaped); - curl_free(mapEscaped); - curl_free(playlistEscaped); - curl_free(passwordEscaped); - } - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - rapidjson_document serverAddedJson; - serverAddedJson.Parse(readBuffer.c_str()); - - // If we could not parse the JSON or it isn't an object, assume the MS is either wrong or we're completely out of date. - // No retry. - if (serverAddedJson.HasParseError()) - { - if (shouldLogError) - spdlog::error( - "Failed reading masterserver authentication response: encountered parse error \"{}\"", - rapidjson::GetParseError_En(serverAddedJson.GetParseError())); - return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); - } - - if (!serverAddedJson.IsObject()) - { - if (shouldLogError) - spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); - } - - if (serverAddedJson.HasMember("error")) - { - if (shouldLogError) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(readBuffer); - } - - // If this is DUPLICATE_SERVER, we'll retry adding the server every 20 seconds. - // The master server will only update its internal server list and clean up dead servers on certain events. - // And then again, only if a player requests the server list after the cooldown (1 second by default), or a server is - // added/updated/removed. In any case this needs to be fixed in the master server rewrite. - if (serverAddedJson["error"].HasMember("enum") && - strcmp(serverAddedJson["error"]["enum"].GetString(), "DUPLICATE_SERVER") == 0) - { - if (shouldLogError) - spdlog::error("Cooling down while the master server cleans the dead server entry, if any."); - return ReturnCleanup(MasterServerReportPresenceResult::FailedDuplicateServer); - } - - // Retry until we reach max retries. - return ReturnCleanup(MasterServerReportPresenceResult::Failed); - } - - if (!serverAddedJson["success"].IsTrue()) - { - if (shouldLogError) - spdlog::error("Adding server to masterserver failed: \"success\" is not true"); - return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); - } - - if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString() || - !serverAddedJson.HasMember("serverAuthToken") || !serverAddedJson["serverAuthToken"].IsString()) - { - if (shouldLogError) - spdlog::error("Failed reading masterserver response: malformed json object"); - return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); - } - - spdlog::info("Successfully registered the local server to the master server."); - return ReturnCleanup( - MasterServerReportPresenceResult::Success, - serverAddedJson["id"].GetString(), - serverAddedJson["serverAuthToken"].GetString()); - } - else - { - if (shouldLogError) - spdlog::error("Failed adding self to server list: error {}", curl_easy_strerror(result)); - return ReturnCleanup(MasterServerReportPresenceResult::FailedNoConnect); - } - }); -} - -void MasterServerPresenceReporter::InternalUpdateServer(const ServerPresence* pServerPresence) -{ - const ServerPresence threadedPresence(pServerPresence); - - // Never call this with an ongoing InternalUpdateServer() call. - assert(!updateServerFuture.valid()); - - const std::string serverId = g_pMasterServerManager->m_sOwnServerId; - const std::string hostname = Cvar_ns_masterserver_hostname->GetString(); - const std::string modinfo = g_pMasterServerManager->m_sOwnModInfoJson; - - updateServerFuture = std::async( - std::launch::async, - [threadedPresence, serverId, hostname, modinfo] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - // Lambda to quickly cleanup resources and return a value. - auto ReturnCleanup = [curl](MasterServerReportPresenceResult result, const char* id = "", const char* serverAuthToken = "") - { - curl_easy_cleanup(curl); - - MasterServerPresenceReporter::ReportPresenceResultData data; - data.result = result; - - if (id != nullptr) - { - data.id = id; - } - - if (serverAuthToken != nullptr) - { - data.serverAuthToken = serverAuthToken; - } - - return data; - }; - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); - - // send all registration info so we have all necessary info to reregister our server if masterserver goes down, - // without a restart this isn't threadsafe :terror: - { - char* nameEscaped = curl_easy_escape(curl, threadedPresence.m_sServerName.c_str(), 0); - char* descEscaped = curl_easy_escape(curl, threadedPresence.m_sServerDesc.c_str(), 0); - char* mapEscaped = curl_easy_escape(curl, threadedPresence.m_MapName, 0); - char* playlistEscaped = curl_easy_escape(curl, threadedPresence.m_PlaylistName, 0); - char* passwordEscaped = curl_easy_escape(curl, threadedPresence.m_Password, 0); - - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/" - "update_values?id={}&port={}&authPort=udp&name={}&description={}&map={}&playlist={}&playerCount={}&" - "maxPlayers={}&password={}", - hostname.c_str(), - serverId.c_str(), - threadedPresence.m_iPort, - nameEscaped, - descEscaped, - mapEscaped, - playlistEscaped, - threadedPresence.m_iPlayerCount, - threadedPresence.m_iMaxPlayers, - passwordEscaped) - .c_str()); - - curl_free(nameEscaped); - curl_free(descEscaped); - curl_free(mapEscaped); - curl_free(playlistEscaped); - curl_free(passwordEscaped); - } - - curl_mime* mime = curl_mime_init(curl); - curl_mimepart* part = curl_mime_addpart(mime); - - curl_mime_data(part, modinfo.c_str(), modinfo.size()); - curl_mime_name(part, "modinfo"); - curl_mime_filename(part, "modinfo.json"); - curl_mime_type(part, "application/json"); - - curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - { - rapidjson_document serverAddedJson; - serverAddedJson.Parse(readBuffer.c_str()); - - const char* updatedId = nullptr; - const char* updatedAuthToken = nullptr; - - if (!serverAddedJson.HasParseError() && serverAddedJson.IsObject()) - { - if (serverAddedJson.HasMember("id") && serverAddedJson["id"].IsString()) - { - updatedId = serverAddedJson["id"].GetString(); - } - - if (serverAddedJson.HasMember("serverAuthToken") && serverAddedJson["serverAuthToken"].IsString()) - { - updatedAuthToken = serverAddedJson["serverAuthToken"].GetString(); - } - } - - return ReturnCleanup(MasterServerReportPresenceResult::Success, updatedId, updatedAuthToken); - } - else - { - spdlog::warn("Heartbeat failed with error {}", curl_easy_strerror(result)); - return ReturnCleanup(MasterServerReportPresenceResult::Failed); - } - }); -} diff --git a/NorthstarDLL/masterserver/masterserver.h b/NorthstarDLL/masterserver/masterserver.h deleted file mode 100644 index 570db619..00000000 --- a/NorthstarDLL/masterserver/masterserver.h +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once - -#include "core/convar/convar.h" -#include "server/serverpresence.h" -#include <winsock2.h> -#include <string> -#include <cstring> -#include <future> -#include <unordered_set> - -extern ConVar* Cvar_ns_masterserver_hostname; -extern ConVar* Cvar_ns_curl_log_enable; - -struct RemoteModInfo -{ -public: - std::string Name; - std::string Version; -}; - -class RemoteServerInfo -{ -public: - char id[33]; // 32 bytes + nullterminator - - // server info - char name[64]; - std::string description; - char map[32]; - char playlist[16]; - char region[32]; - std::vector<RemoteModInfo> requiredMods; - - int playerCount; - int maxPlayers; - - // connection stuff - bool requiresPassword; - -public: - RemoteServerInfo( - const char* newId, - const char* newName, - const char* newDescription, - const char* newMap, - const char* newPlaylist, - const char* newRegion, - int newPlayerCount, - int newMaxPlayers, - bool newRequiresPassword); -}; - -struct RemoteServerConnectionInfo -{ -public: - char authToken[32]; - - in_addr ip; - unsigned short port; -}; - -struct MainMenuPromoData -{ -public: - std::string newInfoTitle1; - std::string newInfoTitle2; - std::string newInfoTitle3; - - std::string largeButtonTitle; - std::string largeButtonText; - std::string largeButtonUrl; - int largeButtonImageIndex; - - std::string smallButton1Title; - std::string smallButton1Url; - int smallButton1ImageIndex; - - std::string smallButton2Title; - std::string smallButton2Url; - int smallButton2ImageIndex; -}; - -class MasterServerManager -{ -private: - bool m_bRequestingServerList = false; - bool m_bAuthenticatingWithGameServer = false; - -public: - char m_sOwnServerId[33]; - char m_sOwnServerAuthToken[33]; - char m_sOwnClientAuthToken[33]; - - std::string m_sOwnModInfoJson; - - bool m_bOriginAuthWithMasterServerDone = false; - bool m_bOriginAuthWithMasterServerInProgress = false; - - bool m_bOriginAuthWithMasterServerSuccessful = false; - std::string m_sOriginAuthWithMasterServerErrorCode = ""; - std::string m_sOriginAuthWithMasterServerErrorMessage = ""; - - bool m_bSavingPersistentData = false; - - bool m_bScriptRequestingServerList = false; - bool m_bSuccessfullyConnected = true; - - bool m_bNewgameAfterSelfAuth = false; - bool m_bScriptAuthenticatingWithGameServer = false; - bool m_bSuccessfullyAuthenticatedWithGameServer = false; - std::string m_sAuthFailureReason {}; - - bool m_bHasPendingConnectionInfo = false; - RemoteServerConnectionInfo m_pendingConnectionInfo; - - std::vector<RemoteServerInfo> m_vRemoteServers; - - bool m_bHasMainMenuPromoData = false; - MainMenuPromoData m_sMainMenuPromoData; - - std::optional<RemoteServerInfo> m_currentServer; - std::string m_sCurrentServerPassword; - - std::unordered_set<std::string> m_handledServerConnections; - -public: - MasterServerManager(); - - void ClearServerList(); - void RequestServerList(); - void RequestMainMenuPromos(); - void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken); - void AuthenticateWithOwnServer(const char* uid, const char* playerToken); - void AuthenticateWithServer(const char* uid, const char* playerToken, RemoteServerInfo server, const char* password); - void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize); - void ProcessConnectionlessPacketSigreq1(std::string req); -}; - -extern MasterServerManager* g_pMasterServerManager; -extern ConVar* Cvar_ns_masterserver_hostname; - -/** Result returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */ -enum class MasterServerReportPresenceResult -{ - // Adding this server to the MS was successful. - Success, - // We failed to add this server to the MS and should retry. - Failed, - // We failed to add this server to the MS and shouldn't retry. - FailedNoRetry, - // We failed to even reach the MS. - FailedNoConnect, - // We failed to add the server because an existing server with the same ip:port exists. - FailedDuplicateServer, -}; - -class MasterServerPresenceReporter : public ServerPresenceReporter -{ -public: - /** Full data returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */ - struct ReportPresenceResultData - { - MasterServerReportPresenceResult result; - - std::optional<std::string> id; - std::optional<std::string> serverAuthToken; - }; - - const int MAX_REGISTRATION_ATTEMPTS = 5; - - // Called to initialise the master server presence reporter's state. - void CreatePresence(const ServerPresence* pServerPresence) override; - - // Run on an internal to either add the server to the MS or update it. - void ReportPresence(const ServerPresence* pServerPresence) override; - - // Called when we need to remove the server from the master server. - void DestroyPresence(const ServerPresence* pServerPresence) override; - - // Called every frame. - void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) override; - -protected: - // Contains the async logic to add the server to the MS. - void InternalAddServer(const ServerPresence* pServerPresence); - - // Contains the async logic to update the server on the MS. - void InternalUpdateServer(const ServerPresence* pServerPresence); - - // The future used for InternalAddServer() calls. - std::future<ReportPresenceResultData> addServerFuture; - - // The future used for InternalAddServer() calls. - std::future<ReportPresenceResultData> updateServerFuture; - - int m_nNumRegistrationAttempts; - - double m_fNextAddServerAttemptTime; -}; diff --git a/NorthstarDLL/mods/autodownload/moddownloader.cpp b/NorthstarDLL/mods/autodownload/moddownloader.cpp deleted file mode 100644 index 165399e3..00000000 --- a/NorthstarDLL/mods/autodownload/moddownloader.cpp +++ /dev/null @@ -1,638 +0,0 @@ -#include "moddownloader.h" -#include <rapidjson/fwd.h> -#include <mz_strm_mem.h> -#include <mz.h> -#include <mz_strm.h> -#include <mz_zip.h> -#include <mz_compat.h> -#include <thread> -#include <future> -#include <bcrypt.h> -#include <winternl.h> -#include <fstream> - -ModDownloader* g_pModDownloader; - -ModDownloader::ModDownloader() -{ - spdlog::info("Mod downloader initialized"); - - // Initialise mods list URI - char* clachar = strstr(GetCommandLineA(), CUSTOM_MODS_URL_FLAG); - if (clachar) - { - std::string url; - int iFlagStringLength = strlen(CUSTOM_MODS_URL_FLAG); - std::string cla = std::string(clachar); - if (strncmp(cla.substr(iFlagStringLength, 1).c_str(), "\"", 1)) - { - int space = cla.find(" "); - url = cla.substr(iFlagStringLength, space - iFlagStringLength); - } - else - { - std::string quote = "\""; - int quote1 = cla.find(quote); - int quote2 = (cla.substr(quote1 + 1)).find(quote); - url = cla.substr(quote1 + 1, quote2); - } - spdlog::info("Found custom verified mods URL in command line argument: {}", url); - modsListUrl = strdup(url.c_str()); - } - else - { - spdlog::info("Custom verified mods URL not found in command line arguments, using default URL."); - modsListUrl = strdup(DEFAULT_MODS_LIST_URL); - } -} - -size_t WriteToString(void* ptr, size_t size, size_t count, void* stream) -{ - ((std::string*)stream)->append((char*)ptr, 0, size * count); - return size * count; -} - -void ModDownloader::FetchModsListFromAPI() -{ - std::thread requestThread( - [this]() - { - CURLcode result; - CURL* easyhandle; - rapidjson::Document verifiedModsJson; - std::string url = modsListUrl; - - curl_global_init(CURL_GLOBAL_ALL); - easyhandle = curl_easy_init(); - std::string readBuffer; - - // Fetching mods list from GitHub repository - curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "GET"); - curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, 30L); - curl_easy_setopt(easyhandle, CURLOPT_URL, url.c_str()); - curl_easy_setopt(easyhandle, CURLOPT_FAILONERROR, 1L); - curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, WriteToString); - result = curl_easy_perform(easyhandle); - - if (result == CURLcode::CURLE_OK) - { - spdlog::info("Mods list successfully fetched."); - } - else - { - spdlog::error("Fetching mods list failed: {}", curl_easy_strerror(result)); - goto REQUEST_END_CLEANUP; - } - - // Load mods list into local state - spdlog::info("Loading mods configuration..."); - verifiedModsJson.Parse(readBuffer); - for (auto i = verifiedModsJson.MemberBegin(); i != verifiedModsJson.MemberEnd(); ++i) - { - std::string name = i->name.GetString(); - std::string dependency = i->value["DependencyPrefix"].GetString(); - - std::unordered_map<std::string, VerifiedModVersion> modVersions; - rapidjson::Value& versions = i->value["Versions"]; - assert(versions.IsArray()); - for (auto& attribute : versions.GetArray()) - { - assert(attribute.IsObject()); - std::string version = attribute["Version"].GetString(); - std::string checksum = attribute["Checksum"].GetString(); - modVersions.insert({version, {.checksum = checksum}}); - } - - VerifiedModDetails modConfig = {.dependencyPrefix = dependency, .versions = modVersions}; - verifiedMods.insert({name, modConfig}); - spdlog::info("==> Loaded configuration for mod \"" + name + "\""); - } - - spdlog::info("Done loading verified mods list."); - - REQUEST_END_CLEANUP: - curl_easy_cleanup(easyhandle); - }); - requestThread.detach(); -} - -size_t WriteData(void* ptr, size_t size, size_t nmemb, FILE* stream) -{ - size_t written; - written = fwrite(ptr, size, nmemb, stream); - return written; -} - -int ModDownloader::ModFetchingProgressCallback( - void* ptr, curl_off_t totalDownloadSize, curl_off_t finishedDownloadSize, curl_off_t totalToUpload, curl_off_t nowUploaded) -{ - if (totalDownloadSize != 0 && finishedDownloadSize != 0) - { - ModDownloader* instance = static_cast<ModDownloader*>(ptr); - auto currentDownloadProgress = roundf(static_cast<float>(finishedDownloadSize) / totalDownloadSize * 100); - instance->modState.progress = finishedDownloadSize; - instance->modState.total = totalDownloadSize; - instance->modState.ratio = currentDownloadProgress; - } - - return 0; -} - -std::optional<fs::path> ModDownloader::FetchModFromDistantStore(std::string_view modName, std::string_view modVersion) -{ - // Retrieve mod prefix from local mods list, or use mod name as mod prefix if bypass flag is set - std::string modPrefix = strstr(GetCommandLineA(), VERIFICATION_FLAG) ? modName.data() : verifiedMods[modName.data()].dependencyPrefix; - // Build archive distant URI - std::string archiveName = std::format("{}-{}.zip", modPrefix, modVersion.data()); - std::string url = STORE_URL + archiveName; - spdlog::info(std::format("Fetching mod archive from {}", url)); - - // Download destination - std::filesystem::path downloadPath = std::filesystem::temp_directory_path() / archiveName; - spdlog::info(std::format("Downloading archive to {}", downloadPath.generic_string())); - - // Update state - modState.state = DOWNLOADING; - - // Download the actual archive - bool failed = false; - FILE* fp = fopen(downloadPath.generic_string().c_str(), "wb"); - CURLcode result; - CURL* easyhandle; - easyhandle = curl_easy_init(); - - curl_easy_setopt(easyhandle, CURLOPT_URL, url.data()); - curl_easy_setopt(easyhandle, CURLOPT_FAILONERROR, 1L); - curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, fp); - curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, WriteData); - curl_easy_setopt(easyhandle, CURLOPT_NOPROGRESS, 0L); - curl_easy_setopt(easyhandle, CURLOPT_XFERINFOFUNCTION, ModDownloader::ModFetchingProgressCallback); - curl_easy_setopt(easyhandle, CURLOPT_XFERINFODATA, this); - result = curl_easy_perform(easyhandle); - - if (result == CURLcode::CURLE_OK) - { - spdlog::info("Mod archive successfully fetched."); - goto REQUEST_END_CLEANUP; - } - else - { - spdlog::error("Fetching mod archive failed: {}", curl_easy_strerror(result)); - failed = true; - goto REQUEST_END_CLEANUP; - } - -REQUEST_END_CLEANUP: - curl_easy_cleanup(easyhandle); - fclose(fp); - return failed ? std::optional<fs::path>() : std::optional<fs::path>(downloadPath); -} - -bool ModDownloader::IsModLegit(fs::path modPath, std::string_view expectedChecksum) -{ - if (strstr(GetCommandLineA(), VERIFICATION_FLAG)) - { - spdlog::info("Bypassing mod verification due to flag set up."); - return true; - } - - // Update state - modState.state = CHECKSUMING; - - NTSTATUS status; - BCRYPT_ALG_HANDLE algorithmHandle = NULL; - BCRYPT_HASH_HANDLE hashHandle = NULL; - std::vector<uint8_t> hash; - DWORD hashLength = 0; - DWORD resultLength = 0; - std::stringstream ss; - - constexpr size_t bufferSize {1 << 12}; - std::vector<char> buffer(bufferSize, '\0'); - std::ifstream fp(modPath.generic_string(), std::ios::binary); - - // Open an algorithm handle - // This sample passes BCRYPT_HASH_REUSABLE_FLAG with BCryptAlgorithmProvider(...) to load a provider which supports reusable hash - status = BCryptOpenAlgorithmProvider( - &algorithmHandle, // Alg Handle pointer - BCRYPT_SHA256_ALGORITHM, // Cryptographic Algorithm name (null terminated unicode string) - NULL, // Provider name; if null, the default provider is loaded - BCRYPT_HASH_REUSABLE_FLAG); // Flags; Loads a provider which supports reusable hash - if (!NT_SUCCESS(status)) - { - modState.state = MOD_CORRUPTED; - goto cleanup; - } - - // Obtain the length of the hash - status = BCryptGetProperty( - algorithmHandle, // Handle to a CNG object - BCRYPT_HASH_LENGTH, // Property name (null terminated unicode string) - (PBYTE)&hashLength, // Address of the output buffer which recieves the property value - sizeof(hashLength), // Size of the buffer in bytes - &resultLength, // Number of bytes that were copied into the buffer - 0); // Flags - if (!NT_SUCCESS(status)) - { - // goto cleanup; - modState.state = MOD_CORRUPTED; - return false; - } - - // Create a hash handle - status = BCryptCreateHash( - algorithmHandle, // Handle to an algorithm provider - &hashHandle, // A pointer to a hash handle - can be a hash or hmac object - NULL, // Pointer to the buffer that recieves the hash/hmac object - 0, // Size of the buffer in bytes - NULL, // A pointer to a key to use for the hash or MAC - 0, // Size of the key in bytes - 0); // Flags - if (!NT_SUCCESS(status)) - { - modState.state = MOD_CORRUPTED; - goto cleanup; - } - - // Hash archive content - if (!fp.is_open()) - { - spdlog::error("Unable to open archive."); - modState.state = FAILED_READING_ARCHIVE; - return false; - } - fp.seekg(0, fp.beg); - while (fp.good()) - { - fp.read(buffer.data(), bufferSize); - std::streamsize bytesRead = fp.gcount(); - if (bytesRead > 0) - { - status = BCryptHashData(hashHandle, (PBYTE)buffer.data(), bytesRead, 0); - if (!NT_SUCCESS(status)) - { - modState.state = MOD_CORRUPTED; - goto cleanup; - } - } - } - - hash = std::vector<uint8_t>(hashLength); - - // Obtain the hash of the message(s) into the hash buffer - status = BCryptFinishHash( - hashHandle, // Handle to the hash or MAC object - hash.data(), // A pointer to a buffer that receives the hash or MAC value - hashLength, // Size of the buffer in bytes - 0); // Flags - if (!NT_SUCCESS(status)) - { - modState.state = MOD_CORRUPTED; - goto cleanup; - } - - // Convert hash to string using bytes raw values - ss << std::hex << std::setfill('0'); - for (int i = 0; i < hashLength; i++) - { - ss << std::hex << std::setw(2) << static_cast<int>(hash.data()[i]); - } - - spdlog::info("Expected checksum: {}", expectedChecksum.data()); - spdlog::info("Computed checksum: {}", ss.str()); - return expectedChecksum.compare(ss.str()) == 0; - -cleanup: - if (NULL != hashHandle) - { - BCryptDestroyHash(hashHandle); // Handle to hash/MAC object which needs to be destroyed - } - - if (NULL != algorithmHandle) - { - BCryptCloseAlgorithmProvider( - algorithmHandle, // Handle to the algorithm provider which needs to be closed - 0); // Flags - } - - return false; -} - -bool ModDownloader::IsModAuthorized(std::string_view modName, std::string_view modVersion) -{ - if (strstr(GetCommandLineA(), VERIFICATION_FLAG)) - { - spdlog::info("Bypassing mod verification due to flag set up."); - return true; - } - - if (!verifiedMods.contains(modName.data())) - { - return false; - } - - std::unordered_map<std::string, VerifiedModVersion> versions = verifiedMods[modName.data()].versions; - return versions.count(modVersion.data()) != 0; -} - -int GetModArchiveSize(unzFile file, unz_global_info64 info) -{ - int totalSize = 0; - - for (int i = 0; i < info.number_entry; i++) - { - char zipFilename[256]; - unz_file_info64 fileInfo; - unzGetCurrentFileInfo64(file, &fileInfo, zipFilename, sizeof(zipFilename), NULL, 0, NULL, 0); - - totalSize += fileInfo.uncompressed_size; - - if ((i + 1) < info.number_entry) - { - unzGoToNextFile(file); - } - } - - // Reset file pointer for archive extraction - unzGoToFirstFile(file); - - return totalSize; -} - -void ModDownloader::ExtractMod(fs::path modPath) -{ - unzFile file; - std::string name; - fs::path modDirectory; - - file = unzOpen(modPath.generic_string().c_str()); - if (file == NULL) - { - spdlog::error("Cannot open archive located at {}.", modPath.generic_string()); - modState.state = FAILED_READING_ARCHIVE; - goto EXTRACTION_CLEANUP; - } - - unz_global_info64 gi; - int status; - status = unzGetGlobalInfo64(file, &gi); - if (status != UNZ_OK) - { - spdlog::error("Failed getting information from archive (error code: {})", status); - modState.state = FAILED_READING_ARCHIVE; - goto EXTRACTION_CLEANUP; - } - - // Update state - modState.state = EXTRACTING; - modState.total = GetModArchiveSize(file, gi); - modState.progress = 0; - - // Mod directory name (removing the ".zip" fom the archive name) - name = modPath.filename().string(); - name = name.substr(0, name.length() - 4); - modDirectory = GetRemoteModFolderPath() / name; - - for (int i = 0; i < gi.number_entry; i++) - { - char zipFilename[256]; - unz_file_info64 fileInfo; - status = unzGetCurrentFileInfo64(file, &fileInfo, zipFilename, sizeof(zipFilename), NULL, 0, NULL, 0); - - // Extract file - { - std::error_code ec; - fs::path fileDestination = modDirectory / zipFilename; - spdlog::info("=> {}", fileDestination.generic_string()); - - // Create parent directory if needed - if (!std::filesystem::exists(fileDestination.parent_path())) - { - spdlog::info("Parent directory does not exist, creating it.", fileDestination.generic_string()); - if (!std::filesystem::create_directories(fileDestination.parent_path(), ec) && ec.value() != 0) - { - spdlog::error("Parent directory ({}) creation failed.", fileDestination.parent_path().generic_string()); - modState.state = FAILED_WRITING_TO_DISK; - goto EXTRACTION_CLEANUP; - } - } - - // If current file is a directory, create directory... - if (fileDestination.generic_string().back() == '/') - { - // Create directory - if (!std::filesystem::create_directory(fileDestination, ec) && ec.value() != 0) - { - spdlog::error("Directory creation failed: {}", ec.message()); - modState.state = FAILED_WRITING_TO_DISK; - goto EXTRACTION_CLEANUP; - } - } - // ...else create file - else - { - // Ensure file is in zip archive - if (unzLocateFile(file, zipFilename, 0) != UNZ_OK) - { - spdlog::error("File \"{}\" was not found in archive.", zipFilename); - modState.state = FAILED_READING_ARCHIVE; - goto EXTRACTION_CLEANUP; - } - - // Create file - const int bufferSize = 8192; - void* buffer; - int err = UNZ_OK; - FILE* fout = NULL; - - // Open zip file to prepare its extraction - status = unzOpenCurrentFile(file); - if (status != UNZ_OK) - { - spdlog::error("Could not open file {} from archive.", zipFilename); - modState.state = FAILED_READING_ARCHIVE; - goto EXTRACTION_CLEANUP; - } - - // Create destination file - fout = fopen(fileDestination.generic_string().c_str(), "wb"); - if (fout == NULL) - { - spdlog::error("Failed creating destination file."); - modState.state = FAILED_WRITING_TO_DISK; - goto EXTRACTION_CLEANUP; - } - - // Allocate memory for buffer - buffer = (void*)malloc(bufferSize); - if (buffer == NULL) - { - spdlog::error("Error while allocating memory."); - modState.state = FAILED_WRITING_TO_DISK; - goto EXTRACTION_CLEANUP; - } - - // Extract file to destination - do - { - err = unzReadCurrentFile(file, buffer, bufferSize); - if (err < 0) - { - spdlog::error("error {} with zipfile in unzReadCurrentFile", err); - break; - } - if (err > 0) - { - if (fwrite(buffer, (unsigned)err, 1, fout) != 1) - { - spdlog::error("error in writing extracted file\n"); - err = UNZ_ERRNO; - break; - } - } - - // Update extraction stats - modState.progress += bufferSize; - modState.ratio = roundf(static_cast<float>(modState.progress) / modState.total * 100); - } while (err > 0); - - if (err != UNZ_OK) - { - spdlog::error("An error occurred during file extraction (code: {})", err); - modState.state = FAILED_WRITING_TO_DISK; - goto EXTRACTION_CLEANUP; - } - err = unzCloseCurrentFile(file); - if (err != UNZ_OK) - { - spdlog::error("error {} with zipfile in unzCloseCurrentFile", err); - } - - // Cleanup - if (fout) - fclose(fout); - } - } - - // Go to next file - if ((i + 1) < gi.number_entry) - { - status = unzGoToNextFile(file); - if (status != UNZ_OK) - { - spdlog::error("Error while browsing archive files (error code: {}).", status); - goto EXTRACTION_CLEANUP; - } - } - } - -EXTRACTION_CLEANUP: - if (unzClose(file) != MZ_OK) - { - spdlog::error("Failed closing mod archive after extraction."); - } -} - -void ModDownloader::DownloadMod(std::string modName, std::string modVersion) -{ - // Check if mod can be auto-downloaded - if (!IsModAuthorized(std::string_view(modName), std::string_view(modVersion))) - { - spdlog::warn("Tried to download a mod that is not verified, aborting."); - return; - } - - std::thread requestThread( - [this, modName, modVersion]() - { - fs::path archiveLocation; - - // Download mod archive - std::string expectedHash = verifiedMods[modName].versions[modVersion].checksum; - std::optional<fs::path> fetchingResult = FetchModFromDistantStore(std::string_view(modName), std::string_view(modVersion)); - if (!fetchingResult.has_value()) - { - spdlog::error("Something went wrong while fetching archive, aborting."); - modState.state = MOD_FETCHING_FAILED; - goto REQUEST_END_CLEANUP; - } - archiveLocation = fetchingResult.value(); - if (!IsModLegit(archiveLocation, std::string_view(expectedHash))) - { - spdlog::warn("Archive hash does not match expected checksum, aborting."); - modState.state = MOD_CORRUPTED; - goto REQUEST_END_CLEANUP; - } - - // Extract downloaded mod archive - ExtractMod(archiveLocation); - - REQUEST_END_CLEANUP: - try - { - remove(archiveLocation); - } - catch (const std::exception& a) - { - spdlog::error("Error while removing downloaded archive: {}", a.what()); - } - - modState.state = DONE; - spdlog::info("Done downloading {}.", modName); - }); - - requestThread.detach(); -} - -ON_DLL_LOAD_RELIESON("engine.dll", ModDownloader, (ConCommand), (CModule module)) -{ - g_pModDownloader = new ModDownloader(); - g_pModDownloader->FetchModsListFromAPI(); -} - -ADD_SQFUNC( - "bool", NSIsModDownloadable, "string name, string version", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - g_pSquirrel<context>->newarray(sqvm, 0); - - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - const SQChar* modVersion = g_pSquirrel<context>->getstring(sqvm, 2); - - bool result = g_pModDownloader->IsModAuthorized(modName, modVersion); - g_pSquirrel<context>->pushbool(sqvm, result); - - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("void", NSDownloadMod, "string name, string version", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - const SQChar* modVersion = g_pSquirrel<context>->getstring(sqvm, 2); - g_pModDownloader->DownloadMod(modName, modVersion); - - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("ModInstallState", NSGetModInstallState, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - g_pSquirrel<context>->pushnewstructinstance(sqvm, 4); - - // state - g_pSquirrel<context>->pushinteger(sqvm, g_pModDownloader->modState.state); - g_pSquirrel<context>->sealstructslot(sqvm, 0); - - // progress - g_pSquirrel<context>->pushinteger(sqvm, g_pModDownloader->modState.progress); - g_pSquirrel<context>->sealstructslot(sqvm, 1); - - // total - g_pSquirrel<context>->pushinteger(sqvm, g_pModDownloader->modState.total); - g_pSquirrel<context>->sealstructslot(sqvm, 2); - - // ratio - g_pSquirrel<context>->pushfloat(sqvm, g_pModDownloader->modState.ratio); - g_pSquirrel<context>->sealstructslot(sqvm, 3); - - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/mods/autodownload/moddownloader.h b/NorthstarDLL/mods/autodownload/moddownloader.h deleted file mode 100644 index 5302c21e..00000000 --- a/NorthstarDLL/mods/autodownload/moddownloader.h +++ /dev/null @@ -1,151 +0,0 @@ -class ModDownloader -{ -private: - const char* VERIFICATION_FLAG = "-disablemodverification"; - const char* CUSTOM_MODS_URL_FLAG = "-customverifiedurl="; - const char* STORE_URL = "https://gcdn.thunderstore.io/live/repository/packages/"; - const char* DEFAULT_MODS_LIST_URL = "https://raw.githubusercontent.com/R2Northstar/VerifiedMods/master/verified-mods.json"; - char* modsListUrl; - - struct VerifiedModVersion - { - std::string checksum; - }; - struct VerifiedModDetails - { - std::string dependencyPrefix; - std::unordered_map<std::string, VerifiedModVersion> versions = {}; - }; - std::unordered_map<std::string, VerifiedModDetails> verifiedMods = {}; - - /** - * Mod archive download callback. - * - * This function is called by curl as it's downloading the mod archive; this - * will retrieve the current `ModDownloader` instance and update its `modState` - * member accordingly. - */ - static int ModFetchingProgressCallback( - void* ptr, curl_off_t totalDownloadSize, curl_off_t finishedDownloadSize, curl_off_t totalToUpload, curl_off_t nowUploaded); - - /** - * Downloads a mod archive from distant store. - * - * This rebuilds the URI of the mod archive using both a predefined store URI - * and the mod dependency string from the `verifiedMods` variable, or using - * input mod name as mod dependency string if bypass flag is set up; fetched - * archive is then stored in a temporary location. - * - * If something went wrong during archive download, this will return an empty - * optional object. - * - * @param modName name of the mod to be downloaded - * @param modVersion version of the mod to be downloaded - * @returns location of the downloaded archive - */ - std::optional<fs::path> FetchModFromDistantStore(std::string_view modName, std::string_view modVersion); - - /** - * Tells if a mod archive has not been corrupted. - * - * The mod validation procedure includes computing the SHA256 hash of the final - * archive, which is stored in the verified mods list. This hash is used by this - * very method to ensure the archive downloaded from the Internet is the exact - * same that has been manually verified. - * - * @param modPath path of the archive to check - * @param expectedChecksum checksum the archive should have - * @returns whether archive is legit - */ - bool IsModLegit(fs::path modPath, std::string_view expectedChecksum); - - /** - * Extracts a mod archive to the game folder. - * - * This extracts a downloaded mod archive from its original location to the - * current game profile, in the remote mods folder. - * - * @param modPath location of the downloaded archive - * @returns nothing - */ - void ExtractMod(fs::path modPath); - -public: - ModDownloader(); - - /** - * Retrieves the verified mods list from the central authority. - * - * The Northstar auto-downloading feature does NOT allow automatically installing - * all mods for various (notably security) reasons; mods that are candidate to - * auto-downloading are rather listed on a GitHub repository - * (https://raw.githubusercontent.com/R2Northstar/VerifiedMods/master/verified-mods.json), - * which this method gets via a HTTP call to load into local state. - * - * If list fetching fails, local mods list will be initialized as empty, thus - * preventing any mod from being auto-downloaded. - * - * @returns nothing - */ - void FetchModsListFromAPI(); - - /** - * Checks whether a mod is verified. - * - * A mod is deemed verified/authorized through a manual validation process that is - * described here: https://github.com/R2Northstar/VerifiedMods; in practice, a mod - * is considered authorized if their name AND exact version appear in the - * `verifiedMods` variable. - * - * @param modName name of the mod to be checked - * @param modVersion version of the mod to be checked, must follow semantic versioning - * @returns whether the mod is authorized and can be auto-downloaded - */ - bool IsModAuthorized(std::string_view modName, std::string_view modVersion); - - /** - * Downloads a given mod from Thunderstore API to local game profile. - * - * @param modName name of the mod to be downloaded - * @param modVersion version of the mod to be downloaded - * @returns nothing - **/ - void DownloadMod(std::string modName, std::string modVersion); - - enum ModInstallState - { - // Normal installation process - DOWNLOADING, - CHECKSUMING, - EXTRACTING, - DONE, // Everything went great, mod can be used in-game - - // Errors - FAILED, // Generic error message, should be avoided as much as possible - FAILED_READING_ARCHIVE, - FAILED_WRITING_TO_DISK, - MOD_FETCHING_FAILED, - MOD_CORRUPTED, // Downloaded archive checksum does not match verified hash - NO_DISK_SPACE_AVAILABLE, - NOT_FOUND // Mod is not currently being auto-downloaded - }; - - struct MOD_STATE - { - ModInstallState state; - int progress; - int total; - float ratio; - } modState = {}; - - /** - * Cancels installation of the mod. - * - * Prevents installation of the mod currently being installed, no matter the install - * progress (downloading, checksuming, extracting), and frees all resources currently - * being used in this purpose. - * - * @returns nothing - */ - void CancelDownload(); -}; diff --git a/NorthstarDLL/mods/compiled/kb_act.cpp b/NorthstarDLL/mods/compiled/kb_act.cpp deleted file mode 100644 index 6117fd28..00000000 --- a/NorthstarDLL/mods/compiled/kb_act.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "mods/modmanager.h" -#include "core/filesystem/filesystem.h" - -#include <fstream> - -const char* KB_ACT_PATH = "scripts\\kb_act.lst"; - -// compiles the file kb_act.lst, that defines entries for keybindings in the options menu -void ModManager::BuildKBActionsList() -{ - spdlog::info("Building kb_act.lst"); - - fs::create_directories(GetCompiledAssetsPath() / "scripts"); - std::ofstream soCompiledKeys(GetCompiledAssetsPath() / KB_ACT_PATH, std::ios::binary); - - // write vanilla file's content to compiled file - soCompiledKeys << ReadVPKOriginalFile(KB_ACT_PATH); - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // write content of each modded file to compiled file - std::ifstream siModKeys(mod.m_ModDirectory / "kb_act.lst"); - - if (siModKeys.good()) - soCompiledKeys << siModKeys.rdbuf() << std::endl; - - siModKeys.close(); - } - - soCompiledKeys.close(); - - // push to overrides - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = KB_ACT_PATH; - - if (m_ModFiles.find(KB_ACT_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(KB_ACT_PATH, overrideFile)); - else - m_ModFiles[KB_ACT_PATH] = overrideFile; -} diff --git a/NorthstarDLL/mods/compiled/modkeyvalues.cpp b/NorthstarDLL/mods/compiled/modkeyvalues.cpp deleted file mode 100644 index e44a81d3..00000000 --- a/NorthstarDLL/mods/compiled/modkeyvalues.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "mods/modmanager.h" -#include "core/filesystem/filesystem.h" - -#include <fstream> - -AUTOHOOK_INIT() - -void ModManager::TryBuildKeyValues(const char* filename) -{ - spdlog::info("Building KeyValues for file {}", filename); - - std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename)); - fs::path compiledPath = GetCompiledAssetsPath() / filename; - fs::path compiledDir = compiledPath.parent_path(); - fs::create_directories(compiledDir); - - fs::path kvPath(filename); - std::string ogFilePath = "mod_original_"; - ogFilePath += kvPath.filename().string(); - - std::string newKvs = "// AUTOGENERATED: MOD PATCH KV\n"; - - int patchNum = 0; - - // copy over patch kv files, and add #bases to new file, last mods' patches should be applied first - // note: #include should be identical but it's actually just broken, thanks respawn - for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) - { - if (!m_LoadedMods[i].m_bEnabled) - continue; - - size_t fileHash = STR_HASH(normalisedPath); - auto modKv = m_LoadedMods[i].KeyValues.find(fileHash); - if (modKv != m_LoadedMods[i].KeyValues.end()) - { - // should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt" - - std::string patchFilePath = "mod_patch_"; - patchFilePath += std::to_string(patchNum++); - patchFilePath += "_"; - patchFilePath += kvPath.filename().string(); - - newKvs += "#base \""; - newKvs += patchFilePath; - newKvs += "\"\n"; - - fs::remove(compiledDir / patchFilePath); - - fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath); - } - } - - // add original #base last, #bases don't override preexisting keys, including the ones we've just done - newKvs += "#base \""; - newKvs += ogFilePath; - newKvs += "\"\n"; - - // load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons) - std::string originalFile = ReadVPKOriginalFile(filename); - - if (!originalFile.length()) - { - spdlog::warn("Tried to patch kv {} but no base kv was found!", ogFilePath); - return; - } - - char rootName[64]; - memset(rootName, 0, sizeof(rootName)); - - // iterate until we hit an ascii char that isn't in a # command or comment to get root obj name - int i = 0; - while (!(originalFile[i] >= 65 && originalFile[i] <= 122)) - { - // if we hit a comment or # thing, iterate until end of line - if (originalFile[i] == '/' || originalFile[i] == '#') - while (originalFile[i] != '\n') - i++; - - i++; - } - - int j = 0; - for (int j = 0; originalFile[i] >= 65 && originalFile[i] <= 122; j++) - rootName[j] = originalFile[i++]; - - // empty kv, all the other stuff gets #base'd - newKvs += rootName; - newKvs += "\n{\n}\n"; - - std::ofstream originalFileWriteStream(compiledDir / ogFilePath, std::ios::binary); - originalFileWriteStream << originalFile; - originalFileWriteStream.close(); - - std::ofstream writeStream(compiledPath, std::ios::binary); - writeStream << newKvs; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = normalisedPath; - - if (m_ModFiles.find(normalisedPath) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile)); - else - m_ModFiles[normalisedPath] = overrideFile; -} diff --git a/NorthstarDLL/mods/compiled/modpdef.cpp b/NorthstarDLL/mods/compiled/modpdef.cpp deleted file mode 100644 index d268a063..00000000 --- a/NorthstarDLL/mods/compiled/modpdef.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "mods/modmanager.h" -#include "core/filesystem/filesystem.h" - -#include <map> -#include <sstream> -#include <fstream> - -const fs::path MOD_PDEF_SUFFIX = "cfg/server/persistent_player_data_version_231.pdef"; -const char* VPK_PDEF_PATH = "cfg/server/persistent_player_data_version_231.pdef"; - -void ModManager::BuildPdef() -{ - spdlog::info("Building persistent_player_data_version_231.pdef..."); - - fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); - - fs::remove(MOD_PDEF_PATH); - std::string pdef = ReadVPKOriginalFile(VPK_PDEF_PATH); - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled || !mod.Pdiff.size()) - continue; - - // this code probably isn't going to be pretty lol - // refer to shared/pjson.js for an actual okish parser of the pdiff format - // but pretty much, $ENUM_ADD blocks define members added to preexisting enums - // $PROP_START ends the custom stuff, and from there it's just normal props we append to the pdef - - std::map<std::string, std::vector<std::string>> enumAdds; - - // read pdiff - bool inEnum = false; - bool inProp = false; - std::string currentEnum; - std::string currentLine; - std::istringstream pdiffStream(mod.Pdiff); - - while (std::getline(pdiffStream, currentLine)) - { - if (inProp) - { - // just append to pdef here - pdef += currentLine; - pdef += '\n'; - continue; - } - - // trim leading whitespace - size_t start = currentLine.find_first_not_of(" \n\r\t\f\v"); - size_t end = currentLine.find("//"); - if (end == std::string::npos) - end = currentLine.size() - 1; // last char - - if (!currentLine.size() || !currentLine.compare(start, 2, "//")) - continue; - - if (inEnum) - { - if (!currentLine.compare(start, 9, "$ENUM_END")) - inEnum = false; - else - enumAdds[currentEnum].push_back(currentLine); // only need to push_back current line, if there's syntax errors then game - // pdef parser will handle them - } - else if (!currentLine.compare(start, 9, "$ENUM_ADD")) - { - inEnum = true; - currentEnum = currentLine.substr(start + 10 /*$ENUM_ADD + 1*/, currentLine.size() - end - (start + 10)); - enumAdds.insert(std::make_pair(currentEnum, std::vector<std::string>())); - } - else if (!currentLine.compare(start, 11, "$PROP_START")) - { - inProp = true; - pdef += "\n// $PROP_START "; - pdef += mod.Name; - pdef += "\n"; - } - } - - // add new members to preexisting enums - // note: this code could 100% be messed up if people put //$ENUM_START comments and the like - // could make it protect against this, but honestly not worth atm - for (auto enumAdd : enumAdds) - { - std::string addStr; - for (std::string enumMember : enumAdd.second) - { - addStr += enumMember; - addStr += '\n'; - } - - // start of enum we're adding to - std::string startStr = "$ENUM_START "; - startStr += enumAdd.first; - - // insert enum values into enum - size_t insertIdx = pdef.find("$ENUM_END", pdef.find(startStr)); - pdef.reserve(addStr.size()); - pdef.insert(insertIdx, addStr); - } - } - - fs::create_directories(MOD_PDEF_PATH.parent_path()); - - std::ofstream writeStream(MOD_PDEF_PATH, std::ios::binary); - writeStream << pdef; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = VPK_PDEF_PATH; - - if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); - else - m_ModFiles[VPK_PDEF_PATH] = overrideFile; -} diff --git a/NorthstarDLL/mods/compiled/modscriptsrson.cpp b/NorthstarDLL/mods/compiled/modscriptsrson.cpp deleted file mode 100644 index d130745f..00000000 --- a/NorthstarDLL/mods/compiled/modscriptsrson.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "mods/modmanager.h" -#include "core/filesystem/filesystem.h" -#include "squirrel/squirrel.h" - -#include <fstream> - -const std::string MOD_SCRIPTS_RSON_SUFFIX = "scripts/vscripts/scripts.rson"; -const char* VPK_SCRIPTS_RSON_PATH = "scripts\\vscripts\\scripts.rson"; - -void ModManager::BuildScriptsRson() -{ - spdlog::info("Building custom scripts.rson"); - fs::path MOD_SCRIPTS_RSON_PATH = fs::path(GetCompiledAssetsPath() / MOD_SCRIPTS_RSON_SUFFIX); - fs::remove(MOD_SCRIPTS_RSON_PATH); - - std::string scriptsRson = ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); - scriptsRson += "\n\n// START MODDED SCRIPT CONTENT\n\n"; // newline before we start custom stuff - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // this isn't needed at all, just nice to have imo - scriptsRson += "// MOD: "; - scriptsRson += mod.Name; - scriptsRson += ":\n\n"; - - for (ModScript& script : mod.Scripts) - { - /* should create something with this format for each script - When: "CONTEXT" - Scripts: - [ - _coolscript.gnut - ]*/ - - scriptsRson += "When: \""; - scriptsRson += script.RunOn; - scriptsRson += "\"\n"; - - scriptsRson += "Scripts:\n[\n\t"; - scriptsRson += script.Path; - scriptsRson += "\n]\n\n"; - } - } - - fs::create_directories(MOD_SCRIPTS_RSON_PATH.parent_path()); - - std::ofstream writeStream(MOD_SCRIPTS_RSON_PATH, std::ios::binary); - writeStream << scriptsRson; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = VPK_SCRIPTS_RSON_PATH; - - if (m_ModFiles.find(VPK_SCRIPTS_RSON_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); - else - m_ModFiles[VPK_SCRIPTS_RSON_PATH] = overrideFile; - - // todo: for preventing dupe scripts in scripts.rson, we could actually parse when conditions with the squirrel vm, just need a way to - // get a result out of squirrelmanager.ExecuteCode this would probably be the best way to do this, imo -} diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp deleted file mode 100644 index 8a0eb71d..00000000 --- a/NorthstarDLL/mods/modmanager.cpp +++ /dev/null @@ -1,1150 +0,0 @@ -#include "modmanager.h" -#include "core/convar/convar.h" -#include "core/convar/concommand.h" -#include "client/audio.h" -#include "masterserver/masterserver.h" -#include "core/filesystem/filesystem.h" -#include "core/filesystem/rpakfilesystem.h" -#include "config/profile.h" - -#include "rapidjson/error/en.h" -#include "rapidjson/document.h" -#include "rapidjson/ostreamwrapper.h" -#include "rapidjson/prettywriter.h" -#include <filesystem> -#include <fstream> -#include <string> -#include <sstream> -#include <vector> -#include <regex> - -ModManager* g_pModManager; - -Mod::Mod(fs::path modDir, char* jsonBuf) -{ - m_bWasReadSuccessfully = false; - - m_ModDirectory = modDir; - - rapidjson_document modJson; - modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf); - - spdlog::info("Loading mod file at path '{}'", modDir.string()); - - // fail if parse error - if (modJson.HasParseError()) - { - spdlog::error( - "Failed reading mod file {}: encountered parse error \"{}\" at offset {}", - (modDir / "mod.json").string(), - GetParseError_En(modJson.GetParseError()), - modJson.GetErrorOffset()); - return; - } - - // fail if it's not a json obj (could be an array, string, etc) - if (!modJson.IsObject()) - { - spdlog::error("Failed reading mod file {}: file is not a JSON object", (modDir / "mod.json").string()); - return; - } - - // basic mod info - // name is required - if (!modJson.HasMember("Name")) - { - spdlog::error("Failed reading mod file {}: missing required member \"Name\"", (modDir / "mod.json").string()); - return; - } - - Name = modJson["Name"].GetString(); - spdlog::info("Loading mod '{}'", Name); - - // Don't load blacklisted mods - if (!strstr(GetCommandLineA(), "-nomodblacklist") && MODS_BLACKLIST.find(Name) != std::end(MODS_BLACKLIST)) - { - spdlog::warn("Skipping blacklisted mod \"{}\"!", Name); - return; - } - - if (modJson.HasMember("Description")) - Description = modJson["Description"].GetString(); - else - Description = ""; - - if (modJson.HasMember("Version")) - Version = modJson["Version"].GetString(); - else - { - Version = "0.0.0"; - spdlog::warn("Mod file {} is missing a version, consider adding a version", (modDir / "mod.json").string()); - } - - if (modJson.HasMember("DownloadLink")) - DownloadLink = modJson["DownloadLink"].GetString(); - else - DownloadLink = ""; - - if (modJson.HasMember("RequiredOnClient")) - RequiredOnClient = modJson["RequiredOnClient"].GetBool(); - else - RequiredOnClient = false; - - if (modJson.HasMember("LoadPriority")) - LoadPriority = modJson["LoadPriority"].GetInt(); - else - { - spdlog::info("Mod file {} is missing a LoadPriority, consider adding one", (modDir / "mod.json").string()); - LoadPriority = 0; - } - - // Parse all array fields - ParseConVars(modJson); - ParseConCommands(modJson); - ParseScripts(modJson); - ParseLocalization(modJson); - ParseDependencies(modJson); - ParsePluginDependencies(modJson); - ParseInitScript(modJson); - - // A mod is remote if it's located in the remote mods folder - m_bIsRemote = m_ModDirectory.generic_string().find(GetRemoteModFolderPath().generic_string()) != std::string::npos; - - m_bWasReadSuccessfully = true; -} - -void Mod::ParseConVars(rapidjson_document& json) -{ - if (!json.HasMember("ConVars")) - return; - - if (!json["ConVars"].IsArray()) - { - spdlog::warn("'ConVars' field is not an array, skipping..."); - return; - } - - for (auto& convarObj : json["ConVars"].GetArray()) - { - if (!convarObj.IsObject()) - { - spdlog::warn("ConVar is not an object, skipping..."); - continue; - } - if (!convarObj.HasMember("Name")) - { - spdlog::warn("ConVar does not have a Name, skipping..."); - continue; - } - // from here on, the ConVar can be referenced by name in logs - if (!convarObj.HasMember("DefaultValue")) - { - spdlog::warn("ConVar '{}' does not have a DefaultValue, skipping...", convarObj["Name"].GetString()); - continue; - } - - // have to allocate this manually, otherwise convar registration will break - // unfortunately this causes us to leak memory on reload, unsure of a way around this rn - ModConVar* convar = new ModConVar; - convar->Name = convarObj["Name"].GetString(); - convar->DefaultValue = convarObj["DefaultValue"].GetString(); - - if (convarObj.HasMember("HelpString")) - convar->HelpString = convarObj["HelpString"].GetString(); - else - convar->HelpString = ""; - - convar->Flags = FCVAR_NONE; - - if (convarObj.HasMember("Flags")) - { - // read raw integer flags - if (convarObj["Flags"].IsInt()) - convar->Flags = convarObj["Flags"].GetInt(); - else if (convarObj["Flags"].IsString()) - { - // parse cvar flags from string - // example string: ARCHIVE_PLAYERPROFILE | GAMEDLL - convar->Flags |= ParseConVarFlagsString(convar->Name, convarObj["Flags"].GetString()); - } - } - - ConVars.push_back(convar); - - spdlog::info("'{}' contains ConVar '{}'", Name, convar->Name); - } -} - -void Mod::ParseConCommands(rapidjson_document& json) -{ - if (!json.HasMember("ConCommands")) - return; - - if (!json["ConCommands"].IsArray()) - { - spdlog::warn("'ConCommands' field is not an array, skipping..."); - return; - } - - for (auto& concommandObj : json["ConCommands"].GetArray()) - { - if (!concommandObj.IsObject()) - { - spdlog::warn("ConCommand is not an object, skipping..."); - continue; - } - if (!concommandObj.HasMember("Name")) - { - spdlog::warn("ConCommand does not have a Name, skipping..."); - continue; - } - // from here on, the ConCommand can be referenced by name in logs - if (!concommandObj.HasMember("Function")) - { - spdlog::warn("ConCommand '{}' does not have a Function, skipping...", concommandObj["Name"].GetString()); - continue; - } - if (!concommandObj.HasMember("Context")) - { - spdlog::warn("ConCommand '{}' does not have a Context, skipping...", concommandObj["Name"].GetString()); - continue; - } - - // have to allocate this manually, otherwise concommand registration will break - // unfortunately this causes us to leak memory on reload, unsure of a way around this rn - ModConCommand* concommand = new ModConCommand; - concommand->Name = concommandObj["Name"].GetString(); - concommand->Function = concommandObj["Function"].GetString(); - concommand->Context = ScriptContextFromString(concommandObj["Context"].GetString()); - if (concommand->Context == ScriptContext::INVALID) - { - spdlog::warn("ConCommand '{}' has invalid context '{}', skipping...", concommand->Name, concommandObj["Context"].GetString()); - continue; - } - - if (concommandObj.HasMember("HelpString")) - concommand->HelpString = concommandObj["HelpString"].GetString(); - else - concommand->HelpString = ""; - - concommand->Flags = FCVAR_NONE; - - if (concommandObj.HasMember("Flags")) - { - // read raw integer flags - if (concommandObj["Flags"].IsInt()) - { - concommand->Flags = concommandObj["Flags"].GetInt(); - } - else if (concommandObj["Flags"].IsString()) - { - // parse cvar flags from string - // example string: ARCHIVE_PLAYERPROFILE | GAMEDLL - concommand->Flags |= ParseConVarFlagsString(concommand->Name, concommandObj["Flags"].GetString()); - } - } - - ConCommands.push_back(concommand); - - spdlog::info("'{}' contains ConCommand '{}'", Name, concommand->Name); - } -} - -void Mod::ParseScripts(rapidjson_document& json) -{ - if (!json.HasMember("Scripts")) - return; - - if (!json["Scripts"].IsArray()) - { - spdlog::warn("'Scripts' field is not an array, skipping..."); - return; - } - - for (auto& scriptObj : json["Scripts"].GetArray()) - { - if (!scriptObj.IsObject()) - { - spdlog::warn("Script is not an object, skipping..."); - continue; - } - if (!scriptObj.HasMember("Path")) - { - spdlog::warn("Script does not have a Path, skipping..."); - continue; - } - // from here on, the Path for a script is used as it's name in logs - if (!scriptObj.HasMember("RunOn")) - { - // "a RunOn" sounds dumb but anything else doesn't match the format of the warnings... - // this is the best i could think of within 20 seconds - spdlog::warn("Script '{}' does not have a RunOn field, skipping...", scriptObj["Path"].GetString()); - continue; - } - - ModScript script; - - script.Path = scriptObj["Path"].GetString(); - script.RunOn = scriptObj["RunOn"].GetString(); - - if (scriptObj.HasMember("ServerCallback")) - { - if (scriptObj["ServerCallback"].IsObject()) - { - ModScriptCallback callback; - callback.Context = ScriptContext::SERVER; - - if (scriptObj["ServerCallback"].HasMember("Before")) - { - if (scriptObj["ServerCallback"]["Before"].IsString()) - callback.BeforeCallback = scriptObj["ServerCallback"]["Before"].GetString(); - else - spdlog::warn("'Before' ServerCallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - if (scriptObj["ServerCallback"].HasMember("After")) - { - if (scriptObj["ServerCallback"]["After"].IsString()) - callback.AfterCallback = scriptObj["ServerCallback"]["After"].GetString(); - else - spdlog::warn("'After' ServerCallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - if (scriptObj["ServerCallback"].HasMember("Destroy")) - { - if (scriptObj["ServerCallback"]["Destroy"].IsString()) - callback.DestroyCallback = scriptObj["ServerCallback"]["Destroy"].GetString(); - else - spdlog::warn( - "'Destroy' ServerCallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - script.Callbacks.push_back(callback); - } - else - { - spdlog::warn("ServerCallback for script '{}' is not an object, skipping...", scriptObj["Path"].GetString()); - } - } - - if (scriptObj.HasMember("ClientCallback")) - { - if (scriptObj["ClientCallback"].IsObject()) - { - ModScriptCallback callback; - callback.Context = ScriptContext::CLIENT; - - if (scriptObj["ClientCallback"].HasMember("Before")) - { - if (scriptObj["ClientCallback"]["Before"].IsString()) - callback.BeforeCallback = scriptObj["ClientCallback"]["Before"].GetString(); - else - spdlog::warn("'Before' ClientCallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - if (scriptObj["ClientCallback"].HasMember("After")) - { - if (scriptObj["ClientCallback"]["After"].IsString()) - callback.AfterCallback = scriptObj["ClientCallback"]["After"].GetString(); - else - spdlog::warn("'After' ClientCallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - if (scriptObj["ClientCallback"].HasMember("Destroy")) - { - if (scriptObj["ClientCallback"]["Destroy"].IsString()) - callback.DestroyCallback = scriptObj["ClientCallback"]["Destroy"].GetString(); - else - spdlog::warn( - "'Destroy' ClientCallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - script.Callbacks.push_back(callback); - } - else - { - spdlog::warn("ClientCallback for script '{}' is not an object, skipping...", scriptObj["Path"].GetString()); - } - } - - if (scriptObj.HasMember("UICallback")) - { - if (scriptObj["UICallback"].IsObject()) - { - ModScriptCallback callback; - callback.Context = ScriptContext::UI; - - if (scriptObj["UICallback"].HasMember("Before")) - { - if (scriptObj["UICallback"]["Before"].IsString()) - callback.BeforeCallback = scriptObj["UICallback"]["Before"].GetString(); - else - spdlog::warn("'Before' UICallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - if (scriptObj["UICallback"].HasMember("After")) - { - if (scriptObj["UICallback"]["After"].IsString()) - callback.AfterCallback = scriptObj["UICallback"]["After"].GetString(); - else - spdlog::warn("'After' UICallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - if (scriptObj["UICallback"].HasMember("Destroy")) - { - if (scriptObj["UICallback"]["Destroy"].IsString()) - callback.DestroyCallback = scriptObj["UICallback"]["Destroy"].GetString(); - else - spdlog::warn("'Destroy' UICallback for script '{}' is not a string, skipping...", scriptObj["Path"].GetString()); - } - - script.Callbacks.push_back(callback); - } - else - { - spdlog::warn("UICallback for script '{}' is not an object, skipping...", scriptObj["Path"].GetString()); - } - } - - Scripts.push_back(script); - - spdlog::info("'{}' contains Script '{}'", Name, script.Path); - } -} - -void Mod::ParseLocalization(rapidjson_document& json) -{ - if (!json.HasMember("Localisation")) - return; - - if (!json["Localisation"].IsArray()) - { - spdlog::warn("'Localisation' field is not an array, skipping..."); - return; - } - - for (auto& localisationStr : json["Localisation"].GetArray()) - { - if (!localisationStr.IsString()) - { - // not a string but we still GetString() to log it :trol: - spdlog::warn("Localisation '{}' is not a string, skipping...", localisationStr.GetString()); - continue; - } - - LocalisationFiles.push_back(localisationStr.GetString()); - - spdlog::info("'{}' registered Localisation '{}'", Name, localisationStr.GetString()); - } -} - -void Mod::ParseDependencies(rapidjson_document& json) -{ - if (!json.HasMember("Dependencies")) - return; - - if (!json["Dependencies"].IsObject()) - { - spdlog::warn("'Dependencies' field is not an object, skipping..."); - return; - } - - for (auto v = json["Dependencies"].MemberBegin(); v != json["Dependencies"].MemberEnd(); v++) - { - if (!v->name.IsString()) - { - spdlog::warn("Dependency constant '{}' is not a string, skipping...", v->name.GetString()); - continue; - } - if (!v->value.IsString()) - { - spdlog::warn("Dependency constant '{}' is not a string, skipping...", v->value.GetString()); - continue; - } - - if (DependencyConstants.find(v->name.GetString()) != DependencyConstants.end() && - v->value.GetString() != DependencyConstants[v->name.GetString()]) - { - // this is fatal because otherwise the mod will probably try to use functions that dont exist, - // which will cause errors further down the line that are harder to debug - spdlog::error( - "'{}' attempted to register a dependency constant '{}' for '{}' that already exists for '{}'. " - "Change the constant name.", - Name, - v->name.GetString(), - v->value.GetString(), - DependencyConstants[v->name.GetString()]); - return; - } - - if (DependencyConstants.find(v->name.GetString()) == DependencyConstants.end()) - DependencyConstants.emplace(v->name.GetString(), v->value.GetString()); - - spdlog::info("'{}' registered dependency constant '{}' for mod '{}'", Name, v->name.GetString(), v->value.GetString()); - } -} - -void Mod::ParsePluginDependencies(rapidjson_document& json) -{ - if (!json.HasMember("PluginDependencies")) - return; - - if (!json["PluginDependencies"].IsArray()) - { - spdlog::warn("'PluginDependencies' field is not an object, skipping..."); - return; - } - - for (auto& name : json["PluginDependencies"].GetArray()) - { - if (!name.IsString()) - continue; - - spdlog::info("Plugin Constant {} defined by {}", name.GetString(), Name); - - PluginDependencyConstants.push_back(name.GetString()); - } -} - -void Mod::ParseInitScript(rapidjson_document& json) -{ - if (!json.HasMember("InitScript")) - return; - - if (!json["InitScript"].IsString()) - { - spdlog::warn("'InitScript' field is not a string, skipping..."); - return; - } - - initScript = json["InitScript"].GetString(); -} - -ModManager::ModManager() -{ - // precaculated string hashes - // note: use backslashes for these, since we use lexically_normal for file paths which uses them - m_hScriptsRsonHash = STR_HASH("scripts\\vscripts\\scripts.rson"); - m_hPdefHash = STR_HASH( - "cfg\\server\\persistent_player_data_version_231.pdef" // this can have multiple versions, but we use 231 so that's what we hash - ); - m_hKBActHash = STR_HASH("scripts\\kb_act.lst"); - - LoadMods(); -} - -struct Test -{ - std::string funcName; - ScriptContext context; -}; - -template <ScriptContext context> auto ModConCommandCallback_Internal(std::string name, const CCommand& command) -{ - if (g_pSquirrel<context>->m_pSQVM && g_pSquirrel<context>->m_pSQVM) - { - if (command.ArgC() == 1) - { - g_pSquirrel<context>->AsyncCall(name); - } - else - { - std::vector<std::string> args; - args.reserve(command.ArgC()); - for (int i = 1; i < command.ArgC(); i++) - args.push_back(command.Arg(i)); - g_pSquirrel<context>->AsyncCall(name, args); - } - } - else - { - spdlog::warn("ConCommand `{}` was called while the associated Squirrel VM `{}` was unloaded", name, GetContextName(context)); - } -} - -auto ModConCommandCallback(const CCommand& command) -{ - ModConCommand* found = nullptr; - auto commandString = std::string(command.GetCommandString()); - - // Finding the first space to remove the command's name - auto firstSpace = commandString.find(' '); - if (firstSpace) - { - commandString = commandString.substr(0, firstSpace); - } - - // Find the mod this command belongs to - for (auto& mod : g_pModManager->m_LoadedMods) - { - auto res = std::find_if( - mod.ConCommands.begin(), - mod.ConCommands.end(), - [&commandString](const ModConCommand* other) { return other->Name == commandString; }); - if (res != mod.ConCommands.end()) - { - found = *res; - break; - } - } - if (!found) - return; - - switch (found->Context) - { - case ScriptContext::CLIENT: - ModConCommandCallback_Internal<ScriptContext::CLIENT>(found->Function, command); - break; - case ScriptContext::SERVER: - ModConCommandCallback_Internal<ScriptContext::SERVER>(found->Function, command); - break; - case ScriptContext::UI: - ModConCommandCallback_Internal<ScriptContext::UI>(found->Function, command); - break; - }; -} - -void ModManager::LoadMods() -{ - if (m_bHasLoadedMods) - UnloadMods(); - - std::vector<fs::path> modDirs; - - // ensure dirs exist - fs::remove_all(GetCompiledAssetsPath()); - fs::create_directories(GetModFolderPath()); - fs::create_directories(GetThunderstoreModFolderPath()); - fs::create_directories(GetRemoteModFolderPath()); - - m_DependencyConstants.clear(); - - // read enabled mods cfg - std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json"); - std::stringstream enabledModsStringStream; - - if (!enabledModsStream.fail()) - { - while (enabledModsStream.peek() != EOF) - enabledModsStringStream << (char)enabledModsStream.get(); - - enabledModsStream.close(); - m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>( - enabledModsStringStream.str().c_str()); - - m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject(); - } - - // get mod directories - std::filesystem::directory_iterator classicModsDir = fs::directory_iterator(GetModFolderPath()); - std::filesystem::directory_iterator remoteModsDir = fs::directory_iterator(GetRemoteModFolderPath()); - std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreModFolderPath()); - - for (fs::directory_entry dir : classicModsDir) - if (fs::exists(dir.path() / "mod.json")) - modDirs.push_back(dir.path()); - - // Special case for Thunderstore and remote mods directories - // Set up regex for `AUTHOR-MOD-VERSION` pattern - std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))"); - - for (fs::directory_iterator dirIterator : {thunderstoreModsDir, remoteModsDir}) - { - for (fs::directory_entry dir : dirIterator) - { - fs::path modsDir = dir.path() / "mods"; // Check for mods folder in the Thunderstore mod - // Use regex to match `AUTHOR-MOD-VERSION` pattern - if (!std::regex_match(dir.path().string(), pattern)) - { - spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string()); - continue; // skip loading mod that doesn't match - } - if (fs::exists(modsDir) && fs::is_directory(modsDir)) - { - for (fs::directory_entry subDir : fs::directory_iterator(modsDir)) - { - if (fs::exists(subDir.path() / "mod.json")) - { - modDirs.push_back(subDir.path()); - } - } - } - } - } - - for (fs::path modDir : modDirs) - { - // read mod json file - std::ifstream jsonStream(modDir / "mod.json"); - std::stringstream jsonStringStream; - - // fail if no mod json - if (jsonStream.fail()) - { - spdlog::warn( - "Mod file at '{}' does not exist or could not be read, is it installed correctly?", (modDir / "mod.json").string()); - continue; - } - - while (jsonStream.peek() != EOF) - jsonStringStream << (char)jsonStream.get(); - - jsonStream.close(); - - Mod mod(modDir, (char*)jsonStringStream.str().c_str()); - - for (auto& pair : mod.DependencyConstants) - { - if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second) - { - spdlog::error( - "'{}' attempted to register a dependency constant '{}' for '{}' that already exists for '{}'. " - "Change the constant name.", - mod.Name, - pair.first, - pair.second, - m_DependencyConstants[pair.first]); - mod.m_bWasReadSuccessfully = false; - break; - } - if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end()) - m_DependencyConstants.emplace(pair); - } - - for (std::string& dependency : mod.PluginDependencyConstants) - { - m_PluginDependencyConstants.insert(dependency); - } - - if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str())) - mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue(); - else - mod.m_bEnabled = true; - - if (mod.m_bWasReadSuccessfully) - { - if (mod.m_bEnabled) - spdlog::info("'{}' loaded successfully, version {}", mod.Name, mod.Version); - else - spdlog::info("'{}' loaded successfully, version {} (DISABLED)", mod.Name, mod.Version); - - m_LoadedMods.push_back(mod); - } - else - spdlog::warn("Mod file at '{}' failed to load", (modDir / "mod.json").string()); - } - - // sort by load prio, lowest-highest - std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); - - // This is used to check if some mods have a folder but no entry in enabledmods.json - bool newModsDetected = false; - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // Add mod entry to enabledmods.json if it doesn't exist - if (!mod.m_bIsRemote && !m_EnabledModsCfg.HasMember(mod.Name.c_str())) - { - m_EnabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), true, m_EnabledModsCfg.GetAllocator()); - newModsDetected = true; - } - - // register convars - // for reloads, this is sorta barebones, when we have a good findconvar method, we could probably reset flags and stuff on - // preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this - // causes us to leak memory on reload, but not much, potentially find a way to not do this at some point - for (ModConVar* convar : mod.ConVars) - { - // make sure convar isn't registered yet, unsure if necessary but idk what - // behaviour is for defining same convar multiple times - if (!g_pCVar->FindVar(convar->Name.c_str())) - { - new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); - } - } - - for (ModConCommand* command : mod.ConCommands) - { - // make sure command isnt't registered multiple times. - if (!g_pCVar->FindCommand(command->Name.c_str())) - { - ConCommand* newCommand = new ConCommand(); - std::string funcName = command->Function; - RegisterConCommand(command->Name.c_str(), ModConCommandCallback, command->HelpString.c_str(), command->Flags); - } - } - - // read vpk paths - if (fs::exists(mod.m_ModDirectory / "vpk")) - { - // read vpk cfg - std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json"); - std::stringstream vpkJsonStringStream; - - bool bUseVPKJson = false; - rapidjson::Document dVpkJson; - - if (!vpkJsonStream.fail()) - { - while (vpkJsonStream.peek() != EOF) - vpkJsonStringStream << (char)vpkJsonStream.get(); - - vpkJsonStream.close(); - dVpkJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>( - vpkJsonStringStream.str().c_str()); - - bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject(); - } - - for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk")) - { - // a bunch of checks to make sure we're only adding dir vpks and their paths are good - // note: the game will literally only load vpks with the english prefix - if (fs::is_regular_file(file) && file.path().extension() == ".vpk" && - file.path().string().find("english") != std::string::npos && - file.path().string().find(".bsp.pak000_dir") != std::string::npos) - { - std::string formattedPath = file.path().filename().string(); - - // this really fucking sucks but it'll work - std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3); - - ModVPKEntry& modVpk = mod.Vpks.emplace_back(); - modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() && - dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue()); - modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string(); - - if (m_bHasLoadedMods && modVpk.m_bAutoLoad) - (*g_pFilesystem)->m_vtable->MountVPK(*g_pFilesystem, vpkName.c_str()); - } - } - } - - // read rpak paths - if (fs::exists(mod.m_ModDirectory / "paks")) - { - // read rpak cfg - std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json"); - std::stringstream rpakJsonStringStream; - - bool bUseRpakJson = false; - rapidjson::Document dRpakJson; - - if (!rpakJsonStream.fail()) - { - while (rpakJsonStream.peek() != EOF) - rpakJsonStringStream << (char)rpakJsonStream.get(); - - rpakJsonStream.close(); - dRpakJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>( - rpakJsonStringStream.str().c_str()); - - bUseRpakJson = !dRpakJson.HasParseError() && dRpakJson.IsObject(); - } - - // read pak aliases - if (bUseRpakJson && dRpakJson.HasMember("Aliases") && dRpakJson["Aliases"].IsObject()) - { - for (rapidjson::Value::ConstMemberIterator iterator = dRpakJson["Aliases"].MemberBegin(); - iterator != dRpakJson["Aliases"].MemberEnd(); - iterator++) - { - if (!iterator->name.IsString() || !iterator->value.IsString()) - continue; - - mod.RpakAliases.insert(std::make_pair(iterator->name.GetString(), iterator->value.GetString())); - } - } - - for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks")) - { - // ensure we're only loading rpaks - if (fs::is_regular_file(file) && file.path().extension() == ".rpak") - { - std::string pakName(file.path().filename().string()); - - ModRpakEntry& modPak = mod.Rpaks.emplace_back(); - modPak.m_bAutoLoad = - !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && - dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); - - // postload things - if (!bUseRpakJson || - (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) - modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); - - modPak.m_sPakName = pakName; - - // read header of file and get the starpak paths - // this is done here as opposed to on starpak load because multiple rpaks can load a starpak - // and there is seemingly no good way to tell which rpak is causing the load of a starpak :/ - - std::ifstream rpakStream(file.path(), std::ios::binary); - - // seek to the point in the header where the starpak reference size is - rpakStream.seekg(0x38, std::ios::beg); - int starpaksSize = 0; - rpakStream.read((char*)&starpaksSize, 2); - - // seek to just after the header - rpakStream.seekg(0x58, std::ios::beg); - // read the starpak reference(s) - std::vector<char> buf(starpaksSize); - rpakStream.read(buf.data(), starpaksSize); - - rpakStream.close(); - - // split the starpak reference(s) into strings to hash - std::string str = ""; - for (int i = 0; i < starpaksSize; i++) - { - // if the current char is null, that signals the end of the current starpak path - if (buf[i] != 0x00) - { - str += buf[i]; - } - else - { - // only add the string we are making if it isnt empty - if (!str.empty()) - { - mod.StarpakPaths.push_back(STR_HASH(str)); - spdlog::info("Mod {} registered starpak '{}'", mod.Name, str); - str = ""; - } - } - } - - // not using atm because we need to resolve path to rpak - // if (m_hasLoadedMods && modPak.m_bAutoLoad) - // g_pPakLoadManager->LoadPakAsync(pakName.c_str()); - } - } - } - - // read keyvalues paths - if (fs::exists(mod.m_ModDirectory / "keyvalues")) - { - for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues")) - { - if (fs::is_regular_file(file)) - { - std::string kvStr = - g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues")); - mod.KeyValues.emplace(STR_HASH(kvStr), kvStr); - } - } - } - - // read pdiff - if (fs::exists(mod.m_ModDirectory / "mod.pdiff")) - { - std::ifstream pdiffStream(mod.m_ModDirectory / "mod.pdiff"); - - if (!pdiffStream.fail()) - { - std::stringstream pdiffStringStream; - while (pdiffStream.peek() != EOF) - pdiffStringStream << (char)pdiffStream.get(); - - pdiffStream.close(); - - mod.Pdiff = pdiffStringStream.str(); - } - } - - // read bink video paths - if (fs::exists(mod.m_ModDirectory / "media")) - { - for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media")) - if (fs::is_regular_file(file) && file.path().extension() == ".bik") - mod.BinkVideos.push_back(file.path().filename().string()); - } - - // try to load audio - if (fs::exists(mod.m_ModDirectory / "audio")) - { - for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio")) - { - if (fs::is_regular_file(file) && file.path().extension().string() == ".json") - { - if (!g_CustomAudioManager.TryLoadAudioOverride(file.path())) - { - spdlog::warn("Mod {} has an invalid audio def {}", mod.Name, file.path().filename().string()); - continue; - } - } - } - } - } - - // If there are new mods, we write entries accordingly in enabledmods.json - if (newModsDetected) - { - std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json"); - rapidjson::OStreamWrapper writeStreamWrapper(writeStream); - rapidjson::PrettyWriter<rapidjson::OStreamWrapper> writer(writeStreamWrapper); - m_EnabledModsCfg.Accept(writer); - } - - // in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised - for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) - { - if (!m_LoadedMods[i].m_bEnabled) - continue; - - if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)) - { - for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)) - { - std::string path = - g_pModManager->NormaliseModFilePath(file.path().lexically_relative(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)); - if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end()) - { - ModOverrideFile modFile; - modFile.m_pOwningMod = &m_LoadedMods[i]; - modFile.m_Path = path; - m_ModFiles.insert(std::make_pair(path, modFile)); - } - } - } - } - - // build modinfo obj for masterserver - rapidjson_document modinfoDoc; - auto& alloc = modinfoDoc.GetAllocator(); - modinfoDoc.SetObject(); - modinfoDoc.AddMember("Mods", rapidjson::kArrayType, alloc); - - int currentModIndex = 0; - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) - continue; - - modinfoDoc["Mods"].PushBack(rapidjson::kObjectType, modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Name", rapidjson::StringRef(&mod.Name[0]), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Version", rapidjson::StringRef(&mod.Version[0]), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("RequiredOnClient", mod.RequiredOnClient, modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Pdiff", rapidjson::StringRef(&mod.Pdiff[0]), modinfoDoc.GetAllocator()); - - currentModIndex++; - } - - rapidjson::StringBuffer buffer; - buffer.Clear(); - rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); - modinfoDoc.Accept(writer); - g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString()); - - m_bHasLoadedMods = true; -} - -void ModManager::UnloadMods() -{ - // clean up stuff from mods before we unload - m_ModFiles.clear(); - fs::remove_all(GetCompiledAssetsPath()); - - g_CustomAudioManager.ClearAudioOverrides(); - - if (!m_bHasEnabledModsCfg) - m_EnabledModsCfg.SetObject(); - - for (Mod& mod : m_LoadedMods) - { - // remove all built kvs - for (std::pair<size_t, std::string> kvPaths : mod.KeyValues) - fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory)); - - mod.KeyValues.clear(); - - // write to m_enabledModsCfg - // should we be doing this here or should scripts be doing this manually? - // main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily - // what we wanna do - if (!m_EnabledModsCfg.HasMember(mod.Name.c_str())) - m_EnabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), false, m_EnabledModsCfg.GetAllocator()); - - m_EnabledModsCfg[mod.Name.c_str()].SetBool(mod.m_bEnabled); - } - - std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json"); - rapidjson::OStreamWrapper writeStreamWrapper(writeStream); - rapidjson::PrettyWriter<rapidjson::OStreamWrapper> writer(writeStreamWrapper); - m_EnabledModsCfg.Accept(writer); - - // do we need to dealloc individual entries in m_loadedMods? idk, rework - m_LoadedMods.clear(); -} - -std::string ModManager::NormaliseModFilePath(const fs::path path) -{ - std::string str = path.lexically_normal().string(); - - // force to lowercase - for (char& c : str) - if (c <= 'Z' && c >= 'A') - c = c - ('Z' - 'z'); - - return str; -} - -void ModManager::CompileAssetsForFile(const char* filename) -{ - size_t fileHash = STR_HASH(NormaliseModFilePath(fs::path(filename))); - - if (fileHash == m_hScriptsRsonHash) - BuildScriptsRson(); - else if (fileHash == m_hPdefHash) - BuildPdef(); - else if (fileHash == m_hKBActHash) - BuildKBActionsList(); - else - { - // check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - if (mod.KeyValues.find(fileHash) != mod.KeyValues.end()) - { - TryBuildKeyValues(filename); - return; - } - } - } -} - -void ConCommand_reload_mods(const CCommand& args) -{ - g_pModManager->LoadMods(); -} - -fs::path GetModFolderPath() -{ - return fs::path(GetNorthstarPrefix() + MOD_FOLDER_SUFFIX); -} -fs::path GetThunderstoreModFolderPath() -{ - return fs::path(GetNorthstarPrefix() + THUNDERSTORE_MOD_FOLDER_SUFFIX); -} -fs::path GetRemoteModFolderPath() -{ - return fs::path(GetNorthstarPrefix() + REMOTE_MOD_FOLDER_SUFFIX); -} -fs::path GetCompiledAssetsPath() -{ - return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX); -} - -ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CModule module)) -{ - g_pModManager = new ModManager; - - RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE); -} diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h deleted file mode 100644 index 233f004d..00000000 --- a/NorthstarDLL/mods/modmanager.h +++ /dev/null @@ -1,187 +0,0 @@ -#pragma once -#include "core/convar/convar.h" -#include "core/memalloc.h" -#include "squirrel/squirrel.h" - -#include "rapidjson/document.h" -#include <string> -#include <vector> -#include <filesystem> -#include <unordered_set> - -const std::string MOD_FOLDER_SUFFIX = "\\mods"; -const std::string THUNDERSTORE_MOD_FOLDER_SUFFIX = "\\packages"; -const std::string REMOTE_MOD_FOLDER_SUFFIX = "\\runtime\\remote\\mods"; -const fs::path MOD_OVERRIDE_DIR = "mod"; -const std::string COMPILED_ASSETS_SUFFIX = "\\runtime\\compiled"; - -const std::set<std::string> MODS_BLACKLIST = {"Mod Settings"}; - -struct ModConVar -{ -public: - std::string Name; - std::string DefaultValue; - std::string HelpString; - int Flags; -}; - -struct ModConCommand -{ -public: - std::string Name; - std::string Function; - std::string HelpString; - ScriptContext Context; - int Flags; -}; - -struct ModScriptCallback -{ -public: - ScriptContext Context; - - // called before the codecallback is executed - std::string BeforeCallback; - // called after the codecallback has finished executing - std::string AfterCallback; - // called right before the vm is destroyed. - std::string DestroyCallback; -}; - -struct ModScript -{ -public: - std::string Path; - std::string RunOn; - - std::vector<ModScriptCallback> Callbacks; -}; - -// these are pretty much identical, could refactor to use the same stuff? -struct ModVPKEntry -{ -public: - bool m_bAutoLoad; - std::string m_sVpkPath; -}; - -struct ModRpakEntry -{ -public: - bool m_bAutoLoad; - std::string m_sPakName; - std::string m_sLoadAfterPak; -}; - -class Mod -{ -public: - // runtime stuff - bool m_bEnabled = true; - bool m_bWasReadSuccessfully = false; - fs::path m_ModDirectory; - bool m_bIsRemote; - - // mod.json stuff: - - // the mod's name - std::string Name; - // the mod's description - std::string Description; - // the mod's version, should be in semver - std::string Version; - // a download link to the mod, for clients that try to join without the mod - std::string DownloadLink; - - // whether clients need the mod to join servers running this mod - bool RequiredOnClient; - // the priority for this mod's files, mods with prio 0 are loaded first, then 1, then 2, etc - int LoadPriority; - - // custom scripts used by the mod - std::vector<ModScript> Scripts; - // convars created by the mod - std::vector<ModConVar*> ConVars; - // concommands created by the mod - std::vector<ModConCommand*> ConCommands; - // custom localisation files created by the mod - std::vector<std::string> LocalisationFiles; - // custom script init.nut - std::string initScript; - - // other files: - - std::vector<ModVPKEntry> Vpks; - std::unordered_map<size_t, std::string> KeyValues; - std::vector<std::string> BinkVideos; - std::string Pdiff; // only need one per mod - - std::vector<ModRpakEntry> Rpaks; - std::unordered_map<std::string, std::string> - RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite - std::vector<size_t> StarpakPaths; // starpaks that this mod contains - // there seems to be no nice way to get the rpak that is causing the load of a starpak? - // hashed with STR_HASH - - std::unordered_map<std::string, std::string> DependencyConstants; - std::vector<std::string> PluginDependencyConstants; - -public: - Mod(fs::path modPath, char* jsonBuf); - -private: - void ParseConVars(rapidjson_document& json); - void ParseConCommands(rapidjson_document& json); - void ParseScripts(rapidjson_document& json); - void ParseLocalization(rapidjson_document& json); - void ParseDependencies(rapidjson_document& json); - void ParsePluginDependencies(rapidjson_document& json); - void ParseInitScript(rapidjson_document& json); -}; - -struct ModOverrideFile -{ -public: - Mod* m_pOwningMod; - fs::path m_Path; -}; - -class ModManager -{ -private: - bool m_bHasLoadedMods = false; - bool m_bHasEnabledModsCfg; - rapidjson_document m_EnabledModsCfg; - - // precalculated hashes - size_t m_hScriptsRsonHash; - size_t m_hPdefHash; - size_t m_hKBActHash; - -public: - std::vector<Mod> m_LoadedMods; - std::unordered_map<std::string, ModOverrideFile> m_ModFiles; - std::unordered_map<std::string, std::string> m_DependencyConstants; - std::unordered_set<std::string> m_PluginDependencyConstants; - -public: - ModManager(); - void LoadMods(); - void UnloadMods(); - std::string NormaliseModFilePath(const fs::path path); - void CompileAssetsForFile(const char* filename); - - // compile asset type stuff, these are done in files under runtime/compiled/ - void BuildScriptsRson(); - void TryBuildKeyValues(const char* filename); - void BuildPdef(); - void BuildKBActionsList(); -}; - -fs::path GetModFolderPath(); -fs::path GetRemoteModFolderPath(); -fs::path GetThunderstoreModFolderPath(); -fs::path GetCompiledAssetsPath(); - -extern ModManager* g_pModManager; diff --git a/NorthstarDLL/mods/modsavefiles.cpp b/NorthstarDLL/mods/modsavefiles.cpp deleted file mode 100644 index 68e33864..00000000 --- a/NorthstarDLL/mods/modsavefiles.cpp +++ /dev/null @@ -1,572 +0,0 @@ -#include <filesystem> -#include <sstream> -#include <fstream> -#include "squirrel/squirrel.h" -#include "util/utils.h" -#include "mods/modmanager.h" -#include "modsavefiles.h" -#include "rapidjson/document.h" -#include "rapidjson/writer.h" -#include "rapidjson/stringbuffer.h" -#include "config/profile.h" -#include "core/tier0.h" -#include "rapidjson/error/en.h" -#include "scripts/scriptjson.h" - -SaveFileManager* g_pSaveFileManager; -int MAX_FOLDER_SIZE = 52428800; // 50MB (50 * 1024 * 1024) -fs::path savePath; - -/// <summary></summary> -/// <param name="dir">The directory we want the size of.</param> -/// <param name="file">The file we're excluding from the calculation.</param> -/// <returns>The size of the contents of the current directory, excluding a specific file.</returns> -uintmax_t GetSizeOfFolderContentsMinusFile(fs::path dir, std::string file) -{ - uintmax_t result = 0; - for (const auto& entry : fs::directory_iterator(dir)) - { - if (entry.path().filename() == file) - continue; - // fs::file_size may not work on directories - but does in some cases. - // per cppreference.com, it's "implementation-defined". - try - { - result += fs::file_size(entry.path()); - } - catch (fs::filesystem_error& e) - { - if (entry.is_directory()) - { - result += GetSizeOfFolderContentsMinusFile(entry.path(), ""); - } - } - } - return result; -} - -uintmax_t GetSizeOfFolder(fs::path dir) -{ - uintmax_t result = 0; - for (const auto& entry : fs::directory_iterator(dir)) - { - // fs::file_size may not work on directories - but does in some cases. - // per cppreference.com, it's "implementation-defined". - try - { - result += fs::file_size(entry.path()); - } - catch (fs::filesystem_error& e) - { - if (entry.is_directory()) - { - result += GetSizeOfFolderContentsMinusFile(entry.path(), ""); - } - } - } - return result; -} - -// Saves a file asynchronously. -template <ScriptContext context> void SaveFileManager::SaveFileAsync(fs::path file, std::string contents) -{ - auto mutex = std::ref(fileMutex); - std::thread writeThread( - [mutex, file, contents]() - { - try - { - mutex.get().lock(); - - fs::path dir = file.parent_path(); - // this actually allows mods to go over the limit, but not by much - // the limit is to prevent mods from taking gigabytes of space, - // we don't need to be particularly strict. - if (GetSizeOfFolderContentsMinusFile(dir, file.filename().string()) + contents.length() > MAX_FOLDER_SIZE) - { - // tbh, you're either trying to fill the hard drive or use so much data, you SHOULD be congratulated. - spdlog::error(fmt::format("Mod spamming save requests? Folder limit bypassed despite previous checks. Not saving.")); - mutex.get().unlock(); - return; - } - - std::ofstream fileStr(file); - if (fileStr.fail()) - { - mutex.get().unlock(); - return; - } - - fileStr.write(contents.c_str(), contents.length()); - fileStr.close(); - - mutex.get().unlock(); - // side-note: this causes a leak? - // when a file is added to the map, it's never removed. - // no idea how to fix this - because we have no way to check if there are other threads waiting to use this file(?) - // tried to use m.try_lock(), but it's unreliable, it seems. - } - catch (std::exception ex) - { - spdlog::error("SAVE FAILED!"); - mutex.get().unlock(); - spdlog::error(ex.what()); - } - }); - - writeThread.detach(); -} - -// Loads a file asynchronously. -template <ScriptContext context> int SaveFileManager::LoadFileAsync(fs::path file) -{ - int handle = ++m_iLastRequestHandle; - auto mutex = std::ref(fileMutex); - std::thread readThread( - [mutex, file, handle]() - { - try - { - mutex.get().lock(); - - std::ifstream fileStr(file); - if (fileStr.fail()) - { - spdlog::error("A file was supposed to be loaded but we can't access it?!"); - - g_pSquirrel<context>->AsyncCall("NSHandleLoadResult", handle, false, ""); - mutex.get().unlock(); - return; - } - - std::stringstream stringStream; - stringStream << fileStr.rdbuf(); - - g_pSquirrel<context>->AsyncCall("NSHandleLoadResult", handle, true, stringStream.str()); - - fileStr.close(); - mutex.get().unlock(); - // side-note: this causes a leak? - // when a file is added to the map, it's never removed. - // no idea how to fix this - because we have no way to check if there are other threads waiting to use this file(?) - // tried to use m.try_lock(), but it's unreliable, it seems. - } - catch (std::exception ex) - { - spdlog::error("LOAD FAILED!"); - g_pSquirrel<context>->AsyncCall("NSHandleLoadResult", handle, false, ""); - mutex.get().unlock(); - spdlog::error(ex.what()); - } - }); - - readThread.detach(); - return handle; -} - -// Deletes a file asynchronously. -template <ScriptContext context> void SaveFileManager::DeleteFileAsync(fs::path file) -{ - // P.S. I don't like how we have to async delete calls but we do. - auto m = std::ref(fileMutex); - std::thread deleteThread( - [m, file]() - { - try - { - m.get().lock(); - - fs::remove(file); - - m.get().unlock(); - // side-note: this causes a leak? - // when a file is added to the map, it's never removed. - // no idea how to fix this - because we have no way to check if there are other threads waiting to use this file(?) - // tried to use m.try_lock(), but it's unreliable, it seems. - } - catch (std::exception ex) - { - spdlog::error("DELETE FAILED!"); - m.get().unlock(); - spdlog::error(ex.what()); - } - }); - - deleteThread.detach(); -} - -// Checks if a file contains null characters. -bool ContainsInvalidChars(std::string str) -{ - // we don't allow null characters either, even if they're ASCII characters because idk if people can - // use it to circumvent the file extension suffix. - return std::any_of(str.begin(), str.end(), [](char c) { return c == '\0'; }); -} - -// Checks if the relative path (param) remains inside the mod directory (dir). -// Paths are restricted to ASCII because encoding is fucked and we decided we won't bother. -bool IsPathSafe(const std::string param, fs::path dir) -{ - try - { - auto const normRoot = fs::weakly_canonical(dir); - auto const normChild = fs::weakly_canonical(dir / param); - - auto itr = std::search(normChild.begin(), normChild.end(), normRoot.begin(), normRoot.end()); - // we return if the file is safe (inside the directory) and uses only ASCII chars in the path. - return itr == normChild.begin() && std::none_of( - param.begin(), - param.end(), - [](char c) - { - unsigned char unsignedC = static_cast<unsigned char>(c); - return unsignedC > 127 || unsignedC < 0; - }); - } - catch (fs::filesystem_error err) - { - return false; - } -} - -// void NSSaveFile( string file, string data ) -ADD_SQFUNC("void", NSSaveFile, "string file, string data", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - if (mod == nullptr) - { - g_pSquirrel<context>->raiseerror(sqvm, "Has to be called from a mod function!"); - return SQRESULT_ERROR; - } - - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string fileName = g_pSquirrel<context>->getstring(sqvm, 1); - if (!IsPathSafe(fileName, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - fileName, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - std::string content = g_pSquirrel<context>->getstring(sqvm, 2); - if (ContainsInvalidChars(content)) - { - g_pSquirrel<context>->raiseerror( - sqvm, fmt::format("File contents may not contain NUL/\\0 characters! Make sure your strings are valid!", mod->Name).c_str()); - return SQRESULT_ERROR; - } - - fs::create_directories(dir); - // this actually allows mods to go over the limit, but not by much - // the limit is to prevent mods from taking gigabytes of space, - // this ain't a cloud service. - if (GetSizeOfFolderContentsMinusFile(dir, fileName) + content.length() > MAX_FOLDER_SIZE) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "The mod {} has reached the maximum folder size.\n\nAsk the mod developer to optimize their data usage," - "or increase the maximum folder size using the -maxfoldersize launch parameter.", - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSaveFileManager->SaveFileAsync<context>(dir / fileName, content); - - return SQRESULT_NULL; -} - -// void NSSaveJSONFile(string file, table data) -ADD_SQFUNC("void", NSSaveJSONFile, "string file, table data", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - if (mod == nullptr) - { - g_pSquirrel<context>->raiseerror(sqvm, "Has to be called from a mod function!"); - return SQRESULT_ERROR; - } - - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string fileName = g_pSquirrel<context>->getstring(sqvm, 1); - if (!IsPathSafe(fileName, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - fileName, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - // Note - this cannot be done in the async func since the table may get garbage collected. - // This means that especially large tables may still clog up the system. - std::string content = EncodeJSON<context>(sqvm); - if (ContainsInvalidChars(content)) - { - g_pSquirrel<context>->raiseerror( - sqvm, fmt::format("File contents may not contain NUL/\\0 characters! Make sure your strings are valid!", mod->Name).c_str()); - return SQRESULT_ERROR; - } - - fs::create_directories(dir); - // this actually allows mods to go over the limit, but not by much - // the limit is to prevent mods from taking gigabytes of space, - // this ain't a cloud service. - if (GetSizeOfFolderContentsMinusFile(dir, fileName) + content.length() > MAX_FOLDER_SIZE) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "The mod {} has reached the maximum folder size.\n\nAsk the mod developer to optimize their data usage," - "or increase the maximum folder size using the -maxfoldersize launch parameter.", - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSaveFileManager->SaveFileAsync<context>(dir / fileName, content); - - return SQRESULT_NULL; -} - -// int NS_InternalLoadFile(string file) -ADD_SQFUNC("int", NS_InternalLoadFile, "string file", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm, 1); // the function that called NSLoadFile :) - if (mod == nullptr) - { - g_pSquirrel<context>->raiseerror(sqvm, "Has to be called from a mod function!"); - return SQRESULT_ERROR; - } - - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string fileName = g_pSquirrel<context>->getstring(sqvm, 1); - if (!IsPathSafe(fileName, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - fileName, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushinteger(sqvm, g_pSaveFileManager->LoadFileAsync<context>(dir / fileName)); - - return SQRESULT_NOTNULL; -} - -// bool NSDoesFileExist(string file) -ADD_SQFUNC("bool", NSDoesFileExist, "string file", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string fileName = g_pSquirrel<context>->getstring(sqvm, 1); - if (!IsPathSafe(fileName, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - fileName, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushbool(sqvm, fs::exists(dir / (fileName))); - return SQRESULT_NOTNULL; -} - -// int NSGetFileSize(string file) -ADD_SQFUNC("int", NSGetFileSize, "string file", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string fileName = g_pSquirrel<context>->getstring(sqvm, 1); - if (!IsPathSafe(fileName, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - fileName, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - try - { - // throws if file does not exist - // we don't want stuff such as "file does not exist, file is unavailable" to be lethal, so we just try/catch fs errors - g_pSquirrel<context>->pushinteger(sqvm, (int)(fs::file_size(dir / fileName) / 1024)); - } - catch (std::filesystem::filesystem_error const& ex) - { - spdlog::error("GET FILE SIZE FAILED! Is the path valid?"); - g_pSquirrel<context>->raiseerror(sqvm, ex.what()); - return SQRESULT_ERROR; - } - return SQRESULT_NOTNULL; -} - -// void NSDeleteFile(string file) -ADD_SQFUNC("void", NSDeleteFile, "string file", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string fileName = g_pSquirrel<context>->getstring(sqvm, 1); - if (!IsPathSafe(fileName, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - fileName, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSaveFileManager->DeleteFileAsync<context>(dir / fileName); - return SQRESULT_NOTNULL; -} - -// The param is not optional because that causes issues :) -ADD_SQFUNC("array<string>", NS_InternalGetAllFiles, "string path", "", ScriptContext::CLIENT | ScriptContext::UI | ScriptContext::SERVER) -{ - // depth 1 because this should always get called from Northstar.Custom - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm, 1); - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string pathStr = g_pSquirrel<context>->getstring(sqvm, 1); - fs::path path = dir; - if (pathStr != "") - path = dir / pathStr; - if (!IsPathSafe(pathStr, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - pathStr, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - try - { - g_pSquirrel<context>->newarray(sqvm, 0); - for (const auto& entry : fs::directory_iterator(path)) - { - g_pSquirrel<context>->pushstring(sqvm, entry.path().filename().string().c_str()); - g_pSquirrel<context>->arrayappend(sqvm, -2); - } - return SQRESULT_NOTNULL; - } - catch (std::exception ex) - { - spdlog::error("DIR ITERATE FAILED! Is the path valid?"); - g_pSquirrel<context>->raiseerror(sqvm, ex.what()); - return SQRESULT_ERROR; - } -} - -ADD_SQFUNC("bool", NSIsFolder, "string path", "", ScriptContext::CLIENT | ScriptContext::UI | ScriptContext::SERVER) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - std::string pathStr = g_pSquirrel<context>->getstring(sqvm, 1); - fs::path path = dir; - if (pathStr != "") - path = dir / pathStr; - if (!IsPathSafe(pathStr, dir)) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "File name invalid ({})! Make sure it does not contain any non-ASCII character, and results in a path inside your mod's " - "save folder.", - pathStr, - mod->Name) - .c_str()); - return SQRESULT_ERROR; - } - try - { - g_pSquirrel<context>->pushbool(sqvm, fs::is_directory(path)); - return SQRESULT_NOTNULL; - } - catch (std::exception ex) - { - spdlog::error("DIR READ FAILED! Is the path valid?"); - spdlog::info(path.string()); - g_pSquirrel<context>->raiseerror(sqvm, ex.what()); - return SQRESULT_ERROR; - } -} - -// side note, expensive. -ADD_SQFUNC("int", NSGetTotalSpaceRemaining, "", "", ScriptContext::CLIENT | ScriptContext::UI | ScriptContext::SERVER) -{ - Mod* mod = g_pSquirrel<context>->getcallingmod(sqvm); - fs::path dir = savePath / fs::path(mod->m_ModDirectory).filename(); - g_pSquirrel<context>->pushinteger(sqvm, (MAX_FOLDER_SIZE - GetSizeOfFolder(dir)) / 1024); - return SQRESULT_NOTNULL; -} - -// ok, I'm just gonna explain what the fuck is going on here because this -// is the pinnacle of my stupidity and I do not want to touch this ever -// again, yet someone will eventually have to maintain this. -template <ScriptContext context> std::string EncodeJSON(HSquirrelVM* sqvm) -{ - // new rapidjson - rapidjson_document doc; - doc.SetObject(); - - // get the SECOND param - SQTable* table = sqvm->_stackOfCurrentFunction[2]._VAL.asTable; - // take the table and copy it's contents over into the rapidjson_document - EncodeJSONTable<context>(table, &doc, doc.GetAllocator()); - - // convert JSON document to string - rapidjson::StringBuffer buffer; - rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); - doc.Accept(writer); - - // return the converted string - return buffer.GetString(); -} - -ON_DLL_LOAD("engine.dll", ModSaveFFiles_Init, (CModule module)) -{ - savePath = fs::path(GetNorthstarPrefix()) / "save_data"; - g_pSaveFileManager = new SaveFileManager; - int parm = CommandLine()->FindParm("-maxfoldersize"); - if (parm) - MAX_FOLDER_SIZE = std::stoi(CommandLine()->GetParm(parm)); -} - -int GetMaxSaveFolderSize() -{ - return MAX_FOLDER_SIZE; -} diff --git a/NorthstarDLL/mods/modsavefiles.h b/NorthstarDLL/mods/modsavefiles.h deleted file mode 100644 index f9d39723..00000000 --- a/NorthstarDLL/mods/modsavefiles.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -int GetMaxSaveFolderSize(); -bool ContainsInvalidChars(std::string str); - -class SaveFileManager -{ -public: - template <ScriptContext context> void SaveFileAsync(fs::path file, std::string content); - template <ScriptContext context> int LoadFileAsync(fs::path file); - template <ScriptContext context> void DeleteFileAsync(fs::path file); - // Future proofed in that if we ever get multi-threaded SSDs this code will take advantage of them. - std::mutex fileMutex; - -private: - int m_iLastRequestHandle = 0; -}; diff --git a/NorthstarDLL/ns_version.h b/NorthstarDLL/ns_version.h deleted file mode 100644 index d30594fb..00000000 --- a/NorthstarDLL/ns_version.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#ifndef NORTHSTAR_VERSION -// Turning off clang-format here so it doesn't mess with style as it needs to be this way for regex-ing with CI -// clang-format off -#define NORTHSTAR_VERSION 0,0,0,1 -// clang-format on -#endif diff --git a/NorthstarDLL/pch.h b/NorthstarDLL/pch.h deleted file mode 100644 index b9ba0e08..00000000 --- a/NorthstarDLL/pch.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef PCH_H -#define PCH_H - -#define WIN32_LEAN_AND_MEAN -#define _CRT_SECURE_NO_WARNINGS -#define RAPIDJSON_NOMEMBERITERATORCLASS // need this for rapidjson -#define NOMINMAX // this too -#define _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later -#define RAPIDJSON_HAS_STDSTRING 1 - -// add headers that you want to pre-compile here -#include "core/memalloc.h" - -#include <windows.h> -#include <psapi.h> -#include <set> -#include <map> -#include <filesystem> -#include <sstream> - -namespace fs = std::filesystem; - -#define EXPORT extern "C" __declspec(dllexport) - -typedef void (*callable)(); -typedef void (*callable_v)(void* v); - -// clang-format off -#define assert_msg(exp, msg) assert((exp, msg)) -//clang-format on - -#include "core/macros.h" - -#include "core/structs.h" -#include "core/math/color.h" - -#include "spdlog/spdlog.h" -#include "logging/logging.h" -#include "MinHook.h" -#include "curl/curl.h" -#include "core/hooks.h" -#include "core/memory.h" - -#endif diff --git a/NorthstarDLL/plugins/plugin_abi.h b/NorthstarDLL/plugins/plugin_abi.h deleted file mode 100644 index 16b26a1c..00000000 --- a/NorthstarDLL/plugins/plugin_abi.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once -#include "squirrel/squirrelclasstypes.h" - -#define ABI_VERSION 3 - -enum PluginLoadDLL -{ - ENGINE = 0, - CLIENT, - SERVER -}; - -enum ObjectType -{ - CONCOMMANDS = 0, - CONVAR = 1, -}; - -struct SquirrelFunctions -{ - RegisterSquirrelFuncType RegisterSquirrelFunc; - sq_defconstType __sq_defconst; - - sq_compilebufferType __sq_compilebuffer; - sq_callType __sq_call; - sq_raiseerrorType __sq_raiseerror; - sq_compilefileType __sq_compilefile; - - sq_newarrayType __sq_newarray; - sq_arrayappendType __sq_arrayappend; - - sq_newtableType __sq_newtable; - sq_newslotType __sq_newslot; - - sq_pushroottableType __sq_pushroottable; - sq_pushstringType __sq_pushstring; - sq_pushintegerType __sq_pushinteger; - sq_pushfloatType __sq_pushfloat; - sq_pushboolType __sq_pushbool; - sq_pushassetType __sq_pushasset; - sq_pushvectorType __sq_pushvector; - sq_pushobjectType __sq_pushobject; - - sq_getstringType __sq_getstring; - sq_getintegerType __sq_getinteger; - sq_getfloatType __sq_getfloat; - sq_getboolType __sq_getbool; - sq_getType __sq_get; - sq_getassetType __sq_getasset; - sq_getuserdataType __sq_getuserdata; - sq_getvectorType __sq_getvector; - sq_getthisentityType __sq_getthisentity; - sq_getobjectType __sq_getobject; - - sq_stackinfosType __sq_stackinfos; - - sq_createuserdataType __sq_createuserdata; - sq_setuserdatatypeidType __sq_setuserdatatypeid; - sq_getfunctionType __sq_getfunction; - - sq_schedule_call_externalType __sq_schedule_call_external; - - sq_getentityfrominstanceType __sq_getentityfrominstance; - sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; - - sq_pushnewstructinstanceType __sq_pushnewstructinstance; - sq_sealstructslotType __sq_sealstructslot; -}; - -struct MessageSource -{ - const char* file; - const char* func; - int line; -}; - -// This is a modified version of spdlog::details::log_msg -// This is so that we can make it cross DLL boundaries -struct LogMsg -{ - int level; - uint64_t timestamp; - const char* msg; - MessageSource source; - int pluginHandle; -}; - -extern "C" -{ - typedef void (*loggerfunc_t)(LogMsg* msg); - typedef void (*PLUGIN_RELAY_INVITE_TYPE)(const char* invite); - typedef void* (*CreateObjectFunc)(ObjectType type); - - typedef void (*PluginFnCommandCallback_t)(void* command); - typedef void (*PluginConCommandConstructorType)( - void* newCommand, const char* name, PluginFnCommandCallback_t callback, const char* helpString, int flags, void* parent); - typedef void (*PluginConVarRegisterType)( - void* pConVar, - const char* pszName, - const char* pszDefaultValue, - int nFlags, - const char* pszHelpString, - bool bMin, - float fMin, - bool bMax, - float fMax, - void* pCallback); - typedef void (*PluginConVarMallocType)(void* pConVarMaloc, int a2, int a3); -} - -struct PluginNorthstarData -{ - const char* version; - HMODULE northstarModule; - int pluginHandle; -}; - -struct PluginInitFuncs -{ - loggerfunc_t logger; - PLUGIN_RELAY_INVITE_TYPE relayInviteFunc; - CreateObjectFunc createObject; -}; - -struct PluginEngineData -{ - PluginConCommandConstructorType ConCommandConstructor; - PluginConVarMallocType conVarMalloc; - PluginConVarRegisterType conVarRegister; - void* ConVar_Vtable; - void* IConVar_Vtable; - void* g_pCVar; -}; - -/// <summary> Async communication within the plugin system -/// Due to the asynchronous nature of plugins, combined with the limitations of multi-compiler support -/// and the custom memory allocator used by r2, is it difficult to safely get data across DLL boundaries -/// from Northstar to plugin unless Northstar can own that memory. -/// This means that plugins should manage their own memory and can only receive data from northstar using one of the functions below. -/// These should be exports of the plugin DLL. If they are not exported, they will not be called. -/// Note that it is not required to have these exports if you do not use them. -/// </summary> - -// Northstar -> Plugin -typedef void (*PLUGIN_INIT_TYPE)(PluginInitFuncs* funcs, PluginNorthstarData* data); -typedef void (*PLUGIN_INIT_SQVM_TYPE)(SquirrelFunctions* funcs); -typedef void (*PLUGIN_INFORM_SQVM_CREATED_TYPE)(ScriptContext context, CSquirrelVM* sqvm); -typedef void (*PLUGIN_INFORM_SQVM_DESTROYED_TYPE)(ScriptContext context); - -typedef void (*PLUGIN_INFORM_DLL_LOAD_TYPE)(const char* dll, PluginEngineData* data, void* dllPtr); -typedef void (*PLUGIN_RUNFRAME)(); diff --git a/NorthstarDLL/plugins/pluginbackend.cpp b/NorthstarDLL/plugins/pluginbackend.cpp deleted file mode 100644 index 850394ac..00000000 --- a/NorthstarDLL/plugins/pluginbackend.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "pluginbackend.h" -#include "plugin_abi.h" -#include "server/serverpresence.h" -#include "masterserver/masterserver.h" -#include "squirrel/squirrel.h" -#include "plugins.h" - -#include "core/convar/concommand.h" - -#include <filesystem> - -#define EXPORT extern "C" __declspec(dllexport) - -AUTOHOOK_INIT() - -PluginCommunicationHandler* g_pPluginCommunicationhandler; - -static PluginDataRequest storedRequest {PluginDataRequestType::END, (PluginRespondDataCallable) nullptr}; - -void PluginCommunicationHandler::RunFrame() -{ - std::lock_guard<std::mutex> lock(requestMutex); - if (!requestQueue.empty()) - { - storedRequest = requestQueue.front(); - switch (storedRequest.type) - { - default: - spdlog::error("{} was called with invalid request type '{}'", __FUNCTION__, static_cast<int>(storedRequest.type)); - } - requestQueue.pop(); - } -} - -void PluginCommunicationHandler::PushRequest(PluginDataRequestType type, PluginRespondDataCallable func) -{ - std::lock_guard<std::mutex> lock(requestMutex); - requestQueue.push(PluginDataRequest {type, func}); -} - -void InformPluginsDLLLoad(fs::path dllPath, void* address) -{ - std::string dllName = dllPath.filename().string(); - - void* data = NULL; - if (strncmp(dllName.c_str(), "engine.dll", 10) == 0) - data = &g_pPluginCommunicationhandler->m_sEngineData; - - g_pPluginManager->InformDLLLoad(dllName.c_str(), data, address); -} diff --git a/NorthstarDLL/plugins/pluginbackend.h b/NorthstarDLL/plugins/pluginbackend.h deleted file mode 100644 index 45cd42f3..00000000 --- a/NorthstarDLL/plugins/pluginbackend.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include "plugin_abi.h" - -#include <queue> -#include <mutex> - -enum PluginDataRequestType -{ - END = 0, -}; - -union PluginRespondDataCallable -{ - // Empty for now - void* UNUSED; -}; - -class PluginDataRequest -{ -public: - PluginDataRequestType type; - PluginRespondDataCallable func; - PluginDataRequest(PluginDataRequestType type, PluginRespondDataCallable func) : type(type), func(func) {} -}; - -class PluginCommunicationHandler -{ -public: - void RunFrame(); - void PushRequest(PluginDataRequestType type, PluginRespondDataCallable func); - -public: - std::queue<PluginDataRequest> requestQueue = {}; - std::mutex requestMutex; - - PluginEngineData m_sEngineData {}; -}; - -void InformPluginsDLLLoad(fs::path dllPath, void* address); -extern PluginCommunicationHandler* g_pPluginCommunicationhandler; diff --git a/NorthstarDLL/plugins/plugins.cpp b/NorthstarDLL/plugins/plugins.cpp deleted file mode 100644 index 72b64566..00000000 --- a/NorthstarDLL/plugins/plugins.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "plugins.h" -#include "config/profile.h" - -#include "squirrel/squirrel.h" -#include "plugins.h" -#include "masterserver/masterserver.h" -#include "core/convar/convar.h" -#include "server/serverpresence.h" -#include <optional> -#include <regex> - -#include "util/version.h" -#include "pluginbackend.h" -#include "util/wininfo.h" -#include "logging/logging.h" -#include "dedicated/dedicated.h" - -PluginManager* g_pPluginManager; - -void freeLibrary(HMODULE hLib) -{ - if (!FreeLibrary(hLib)) - { - spdlog::error("There was an error while trying to free library"); - } -} - -EXPORT void PLUGIN_LOG(LogMsg* msg) -{ - spdlog::source_loc src {}; - src.filename = msg->source.file; - src.funcname = msg->source.func; - src.line = msg->source.line; - auto&& logger = g_pPluginManager->m_vLoadedPlugins[msg->pluginHandle].logger; - logger->log(src, (spdlog::level::level_enum)msg->level, msg->msg); -} - -EXPORT void* CreateObject(ObjectType type) -{ - switch (type) - { - case ObjectType::CONVAR: - return (void*)new ConVar; - case ObjectType::CONCOMMANDS: - return (void*)new ConCommand; - default: - return NULL; - } -} - -std::optional<Plugin> PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data) -{ - - Plugin plugin {}; - - std::string pathstring = path.string(); - std::wstring wpath = path.wstring(); - - LPCWSTR wpptr = wpath.c_str(); - HMODULE datafile = LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); // Load the DLL as a data file - if (datafile == NULL) - { - NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); - return std::nullopt; - } - HRSRC manifestResource = FindResourceW(datafile, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA); - - if (manifestResource == NULL) - { - NS::log::PLUGINSYS->info("Could not find manifest for library '{}'", pathstring); - freeLibrary(datafile); - return std::nullopt; - } - HGLOBAL myResourceData = LoadResource(datafile, manifestResource); - if (myResourceData == NULL) - { - NS::log::PLUGINSYS->error("Failed to load manifest from library '{}'", pathstring); - freeLibrary(datafile); - return std::nullopt; - } - int manifestSize = SizeofResource(datafile, manifestResource); - std::string manifest = std::string((const char*)LockResource(myResourceData), 0, manifestSize); - freeLibrary(datafile); - - rapidjson_document manifestJSON; - manifestJSON.Parse(manifest.c_str()); - - if (manifestJSON.HasParseError()) - { - NS::log::PLUGINSYS->error("Manifest for '{}' was invalid", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("name")) - { - NS::log::PLUGINSYS->error("'{}' is missing a name in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("displayname")) - { - NS::log::PLUGINSYS->error("'{}' is missing a displayname in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("description")) - { - NS::log::PLUGINSYS->error("'{}' is missing a description in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("api_version")) - { - NS::log::PLUGINSYS->error("'{}' is missing a api_version in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("version")) - { - NS::log::PLUGINSYS->error("'{}' is missing a version in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("run_on_server")) - { - NS::log::PLUGINSYS->error("'{}' is missing 'run_on_server' in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("run_on_client")) - { - NS::log::PLUGINSYS->error("'{}' is missing 'run_on_client' in its manifest", pathstring); - return std::nullopt; - } - auto test = manifestJSON["api_version"].GetString(); - if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str())) - { - NS::log::PLUGINSYS->error( - "'{}' has an incompatible API version number in its manifest. Current ABI version is '{}'", pathstring, ABI_VERSION); - return std::nullopt; - } - // Passed all checks, going to actually load it now - - HMODULE pluginLib = - LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); // Load the DLL with lib folders - if (pluginLib == NULL) - { - NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); - return std::nullopt; - } - plugin.init = (PLUGIN_INIT_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT"); - if (plugin.init == NULL) - { - NS::log::PLUGINSYS->info("Library '{}' has no function 'PLUGIN_INIT'", pathstring); - return std::nullopt; - } - NS::log::PLUGINSYS->info("Succesfully loaded {}", pathstring); - - plugin.name = manifestJSON["name"].GetString(); - plugin.displayName = manifestJSON["displayname"].GetString(); - plugin.description = manifestJSON["description"].GetString(); - plugin.api_version = manifestJSON["api_version"].GetString(); - plugin.version = manifestJSON["version"].GetString(); - - plugin.run_on_client = manifestJSON["run_on_client"].GetBool(); - plugin.run_on_server = manifestJSON["run_on_server"].GetBool(); - - if (!plugin.run_on_server && IsDedicatedServer()) - return std::nullopt; - - if (manifestJSON.HasMember("dependencyName")) - { - plugin.dependencyName = manifestJSON["dependencyName"].GetString(); - } - else - { - plugin.dependencyName = plugin.name; - } - - if (std::find_if( - plugin.dependencyName.begin(), - plugin.dependencyName.end(), - [&](char c) -> bool { return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'); }) != - plugin.dependencyName.end()) - { - NS::log::PLUGINSYS->warn("Dependency string \"{}\" in {} is not valid a squirrel constant!", plugin.dependencyName, plugin.name); - } - - plugin.init_sqvm_client = (PLUGIN_INIT_SQVM_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT_SQVM_CLIENT"); - plugin.init_sqvm_server = (PLUGIN_INIT_SQVM_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT_SQVM_SERVER"); - plugin.inform_sqvm_created = (PLUGIN_INFORM_SQVM_CREATED_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_SQVM_CREATED"); - plugin.inform_sqvm_destroyed = (PLUGIN_INFORM_SQVM_DESTROYED_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_SQVM_DESTROYED"); - - plugin.inform_dll_load = (PLUGIN_INFORM_DLL_LOAD_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_DLL_LOAD"); - - plugin.run_frame = (PLUGIN_RUNFRAME)GetProcAddress(pluginLib, "PLUGIN_RUNFRAME"); - - plugin.handle = m_vLoadedPlugins.size(); - plugin.logger = std::make_shared<ColoredLogger>(plugin.displayName.c_str(), NS::Colors::PLUGIN); - RegisterLogger(plugin.logger); - NS::log::PLUGINSYS->info("Loading plugin {} version {}", plugin.displayName, plugin.version); - m_vLoadedPlugins.push_back(plugin); - - plugin.init(funcs, data); - - return plugin; -} - -inline void FindPlugins(fs::path pluginPath, std::vector<fs::path>& paths) -{ - // ensure dirs exist - if (!fs::exists(pluginPath) || !fs::is_directory(pluginPath)) - { - return; - } - - for (const fs::directory_entry& entry : fs::directory_iterator(pluginPath)) - { - if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") - paths.emplace_back(entry.path()); - } -} - -bool PluginManager::LoadPlugins() -{ - if (strstr(GetCommandLineA(), "-noplugins") != NULL) - { - NS::log::PLUGINSYS->warn("-noplugins detected; skipping loading plugins"); - return false; - } - - fs::create_directories(GetThunderstoreModFolderPath()); - - std::vector<fs::path> paths; - - pluginPath = GetNorthstarPrefix() + "\\plugins"; - - PluginNorthstarData data {}; - std::string ns_version {version}; - - PluginInitFuncs funcs {}; - funcs.logger = PLUGIN_LOG; - funcs.relayInviteFunc = nullptr; - funcs.createObject = CreateObject; - - data.version = ns_version.c_str(); - data.northstarModule = g_NorthstarModule; - - fs::path libPath = fs::absolute(pluginPath + "\\lib"); - if (fs::exists(libPath) && fs::is_directory(libPath)) - AddDllDirectory(libPath.wstring().c_str()); - - FindPlugins(pluginPath, paths); - - // Special case for Thunderstore mods dir - std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreModFolderPath()); - // Set up regex for `AUTHOR-MOD-VERSION` pattern - std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))"); - for (fs::directory_entry dir : thunderstoreModsDir) - { - fs::path pluginsDir = dir.path() / "plugins"; - // Use regex to match `AUTHOR-MOD-VERSION` pattern - if (!std::regex_match(dir.path().string(), pattern)) - { - spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string()); - continue; // skip loading package that doesn't match - } - - fs::path libDir = fs::absolute(pluginsDir / "lib"); - if (fs::exists(libDir) && fs::is_directory(libDir)) - AddDllDirectory(libDir.wstring().c_str()); - - FindPlugins(pluginsDir, paths); - } - - if (paths.empty()) - { - NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins"); - return false; - } - - for (fs::path path : paths) - { - if (LoadPlugin(path, &funcs, &data)) - data.pluginHandle += 1; - } - return true; -} - -void PluginManager::InformSQVMLoad(ScriptContext context, SquirrelFunctions* s) -{ - for (auto plugin : m_vLoadedPlugins) - { - if (context == ScriptContext::CLIENT && plugin.init_sqvm_client != NULL) - { - plugin.init_sqvm_client(s); - } - else if (context == ScriptContext::SERVER && plugin.init_sqvm_server != NULL) - { - plugin.init_sqvm_server(s); - } - } -} - -void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm) -{ - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.inform_sqvm_created != NULL) - { - plugin.inform_sqvm_created(context, sqvm); - } - } -} - -void PluginManager::InformSQVMDestroyed(ScriptContext context) -{ - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.inform_sqvm_destroyed != NULL) - { - plugin.inform_sqvm_destroyed(context); - } - } -} - -void PluginManager::InformDLLLoad(const char* dll, void* data, void* dllPtr) -{ - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.inform_dll_load != NULL) - { - plugin.inform_dll_load(dll, (PluginEngineData*)data, dllPtr); - } - } -} - -void PluginManager::RunFrame() -{ - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.run_frame != NULL) - { - plugin.run_frame(); - } - } -} diff --git a/NorthstarDLL/plugins/plugins.h b/NorthstarDLL/plugins/plugins.h deleted file mode 100644 index 4e841f27..00000000 --- a/NorthstarDLL/plugins/plugins.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once -#include "plugin_abi.h" - -const int IDR_RCDATA1 = 101; - -class Plugin -{ -public: - std::string name; - std::string displayName; - std::string dependencyName; - std::string description; - - std::string api_version; - std::string version; - - // For now this is just implemented as the index into the plugins array - // Maybe a bit shit but it works - int handle; - - std::shared_ptr<ColoredLogger> logger; - - bool run_on_client = false; - bool run_on_server = false; - -public: - PLUGIN_INIT_TYPE init; - PLUGIN_INIT_SQVM_TYPE init_sqvm_client; - PLUGIN_INIT_SQVM_TYPE init_sqvm_server; - PLUGIN_INFORM_SQVM_CREATED_TYPE inform_sqvm_created; - PLUGIN_INFORM_SQVM_DESTROYED_TYPE inform_sqvm_destroyed; - - PLUGIN_INFORM_DLL_LOAD_TYPE inform_dll_load; - - PLUGIN_RUNFRAME run_frame; -}; - -class PluginManager -{ -public: - std::vector<Plugin> m_vLoadedPlugins; - -public: - bool LoadPlugins(); - std::optional<Plugin> LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data); - - void InformSQVMLoad(ScriptContext context, SquirrelFunctions* s); - void InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm); - void InformSQVMDestroyed(ScriptContext context); - - void InformDLLLoad(const char* dll, void* data, void* dllPtr); - - void RunFrame(); - -private: - std::string pluginPath; -}; - -extern PluginManager* g_pPluginManager; diff --git a/NorthstarDLL/resource1.h b/NorthstarDLL/resource1.h deleted file mode 100644 index bb584502..00000000 --- a/NorthstarDLL/resource1.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by resources.rc -// -#define IDI_ICON1 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/NorthstarDLL/resources.rc b/NorthstarDLL/resources.rc deleted file mode 100644 index 7e996617..00000000 --- a/NorthstarDLL/resources.rc +++ /dev/null @@ -1,79 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource1.h" -#include "../NorthstarDLL/ns_version.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource1.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION NORTHSTAR_VERSION - PRODUCTVERSION NORTHSTAR_VERSION - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "080904b0" - BEGIN - VALUE "CompanyName", "Northstar Developers" - VALUE "FileVersion", "DEV" - VALUE "InternalName", "Northstar.dll" - VALUE "LegalCopyright", "Copyright (C) 2021" - VALUE "OriginalFilename", "Northstar.dll" - VALUE "ProductVersion", "DEV" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x809, 1200 - END -END - -///////////////////////////////////////////////////////////////////////////// - - diff --git a/NorthstarDLL/scripts/client/clientchathooks.cpp b/NorthstarDLL/scripts/client/clientchathooks.cpp deleted file mode 100644 index e084f47e..00000000 --- a/NorthstarDLL/scripts/client/clientchathooks.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "squirrel/squirrel.h" -#include "util/utils.h" - -#include "server/serverchathooks.h" -#include "client/localchatwriter.h" - -#include <rapidjson/document.h> - -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(CHudChat__AddGameLine, client.dll + 0x22E580, -void, __fastcall, (void* self, const char* message, int inboxId, bool isTeam, bool isDead)) -// clang-format on -{ - // This hook is called for each HUD, but we only want our logic to run once. - if (self != *CHudChat::allHuds) - 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; - } - - RemoveAsciiControlSequences(const_cast<char*>(message), true); - - SQRESULT result = g_pSquirrel<ScriptContext::CLIENT>->Call( - "CHudChat_ProcessMessageStartThread", static_cast<int>(senderId) - 1, payload, isTeam, isDead, type); - if (result == SQRESULT_ERROR) - for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) - CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead); -} - -ADD_SQFUNC("void", NSChatWrite, "int context, string text", "", ScriptContext::CLIENT) -{ - int chatContext = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); - const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); - - LocalChatWriter((LocalChatWriter::Context)chatContext).Write(str); - return SQRESULT_NULL; -} - -ADD_SQFUNC("void", NSChatWriteRaw, "int context, string text", "", ScriptContext::CLIENT) -{ - int chatContext = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); - const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); - - LocalChatWriter((LocalChatWriter::Context)chatContext).InsertText(str); - return SQRESULT_NULL; -} - -ADD_SQFUNC("void", NSChatWriteLine, "int context, string text", "", ScriptContext::CLIENT) -{ - int chatContext = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); - const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); - - LocalChatWriter((LocalChatWriter::Context)chatContext).WriteLine(str); - return SQRESULT_NULL; -} - -ON_DLL_LOAD_CLIENT("client.dll", ClientChatHooks, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/scripts/client/cursorposition.cpp b/NorthstarDLL/scripts/client/cursorposition.cpp deleted file mode 100644 index c0e8623c..00000000 --- a/NorthstarDLL/scripts/client/cursorposition.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "squirrel/squirrel.h" -#include "util/wininfo.h" - -ADD_SQFUNC("vector ornull", NSGetCursorPosition, "", "", ScriptContext::UI) -{ - RECT rcClient; - POINT p; - if (GetCursorPos(&p) && ScreenToClient(*g_gameHWND, &p) && GetClientRect(*g_gameHWND, &rcClient)) - { - if (GetAncestor(GetForegroundWindow(), GA_ROOTOWNER) != *g_gameHWND) - return SQRESULT_NULL; - - g_pSquirrel<context>->pushvector( - sqvm, - {p.x > 0 ? p.x > rcClient.right ? rcClient.right : (float)p.x : 0, - p.y > 0 ? p.y > rcClient.bottom ? rcClient.bottom : (float)p.y : 0, - 0}); - return SQRESULT_NOTNULL; - } - g_pSquirrel<context>->raiseerror(sqvm, "Failed retrieving cursor position of game window"); - return SQRESULT_ERROR; -} diff --git a/NorthstarDLL/scripts/client/scriptbrowserhooks.cpp b/NorthstarDLL/scripts/client/scriptbrowserhooks.cpp deleted file mode 100644 index 86b4a356..00000000 --- a/NorthstarDLL/scripts/client/scriptbrowserhooks.cpp +++ /dev/null @@ -1,24 +0,0 @@ - -AUTOHOOK_INIT() - -bool* bIsOriginOverlayEnabled; - -// clang-format off -AUTOHOOK(OpenExternalWebBrowser, engine.dll + 0x184E40, -void, __fastcall, (char* pUrl, char flags)) -// clang-format on -{ - bool bIsOriginOverlayEnabledOriginal = *bIsOriginOverlayEnabled; - if (flags & 2 && !strncmp(pUrl, "http", 4)) // custom force external browser flag - *bIsOriginOverlayEnabled = false; // if this bool is false, game will use an external browser rather than the origin overlay one - - OpenExternalWebBrowser(pUrl, flags); - *bIsOriginOverlayEnabled = bIsOriginOverlayEnabledOriginal; -} - -ON_DLL_LOAD_CLIENT("engine.dll", ScriptExternalBrowserHooks, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - bIsOriginOverlayEnabled = module.Offset(0x13978255).RCast<bool*>(); -} diff --git a/NorthstarDLL/scripts/client/scriptmainmenupromos.cpp b/NorthstarDLL/scripts/client/scriptmainmenupromos.cpp deleted file mode 100644 index ecb47af7..00000000 --- a/NorthstarDLL/scripts/client/scriptmainmenupromos.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "squirrel/squirrel.h" -#include "masterserver/masterserver.h" - -// mirror this in script -enum eMainMenuPromoDataProperty -{ - newInfoTitle1, - newInfoTitle2, - newInfoTitle3, - - largeButtonTitle, - largeButtonText, - largeButtonUrl, - largeButtonImageIndex, - - smallButton1Title, - smallButton1Url, - smallButton1ImageIndex, - - smallButton2Title, - smallButton2Url, - smallButton2ImageIndex -}; -ADD_SQFUNC("void", NSRequestCustomMainMenuPromos, "", "", ScriptContext::UI) -{ - g_pMasterServerManager->RequestMainMenuPromos(); - return SQRESULT_NULL; -} - -ADD_SQFUNC("bool", NSHasCustomMainMenuPromoData, "", "", ScriptContext::UI) -{ - g_pSquirrel<ScriptContext::UI>->pushbool(sqvm, g_pMasterServerManager->m_bHasMainMenuPromoData); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("var", NSGetCustomMainMenuPromoData, "int promoDataKey", "", ScriptContext::UI) -{ - if (!g_pMasterServerManager->m_bHasMainMenuPromoData) - return SQRESULT_NULL; - - switch (g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 1)) - { - case eMainMenuPromoDataProperty::newInfoTitle1: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle1.c_str()); - break; - } - - case eMainMenuPromoDataProperty::newInfoTitle2: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle2.c_str()); - break; - } - - case eMainMenuPromoDataProperty::newInfoTitle3: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle3.c_str()); - break; - } - - case eMainMenuPromoDataProperty::largeButtonTitle: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonTitle.c_str()); - break; - } - - case eMainMenuPromoDataProperty::largeButtonText: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonText.c_str()); - break; - } - - case eMainMenuPromoDataProperty::largeButtonUrl: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonUrl.c_str()); - break; - } - - case eMainMenuPromoDataProperty::largeButtonImageIndex: - { - g_pSquirrel<ScriptContext::UI>->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonImageIndex); - break; - } - - case eMainMenuPromoDataProperty::smallButton1Title: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Title.c_str()); - break; - } - - case eMainMenuPromoDataProperty::smallButton1Url: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Url.c_str()); - break; - } - - case eMainMenuPromoDataProperty::smallButton1ImageIndex: - { - g_pSquirrel<ScriptContext::UI>->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1ImageIndex); - break; - } - - case eMainMenuPromoDataProperty::smallButton2Title: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Title.c_str()); - break; - } - - case eMainMenuPromoDataProperty::smallButton2Url: - { - g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Url.c_str()); - break; - } - - case eMainMenuPromoDataProperty::smallButton2ImageIndex: - { - g_pSquirrel<ScriptContext::UI>->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2ImageIndex); - break; - } - } - - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/scripts/client/scriptmodmenu.cpp b/NorthstarDLL/scripts/client/scriptmodmenu.cpp deleted file mode 100644 index a88478fb..00000000 --- a/NorthstarDLL/scripts/client/scriptmodmenu.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include "mods/modmanager.h" -#include "squirrel/squirrel.h" - -ADD_SQFUNC("array<string>", NSGetModNames, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - g_pSquirrel<context>->newarray(sqvm, 0); - - for (Mod& mod : g_pModManager->m_LoadedMods) - { - g_pSquirrel<context>->pushstring(sqvm, mod.Name.c_str()); - g_pSquirrel<context>->arrayappend(sqvm, -2); - } - - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("bool", NSIsModEnabled, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel<context>->pushbool(sqvm, mod.m_bEnabled); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - const SQBool enabled = g_pSquirrel<context>->getbool(sqvm, 2); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - mod.m_bEnabled = enabled; - return SQRESULT_NULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("string", NSGetModDescriptionByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel<context>->pushstring(sqvm, mod.Description.c_str()); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("string", NSGetModVersionByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel<context>->pushstring(sqvm, mod.Version.c_str()); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("string", NSGetModDownloadLinkByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel<context>->pushstring(sqvm, mod.DownloadLink.c_str()); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("int", NSGetModLoadPriority, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel<context>->pushinteger(sqvm, mod.LoadPriority); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("bool", NSIsModRequiredOnClient, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel<context>->pushbool(sqvm, mod.RequiredOnClient); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC( - "array<string>", NSGetModConvarsByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); - g_pSquirrel<context>->newarray(sqvm, 0); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - for (ModConVar* cvar : mod.ConVars) - { - g_pSquirrel<context>->pushstring(sqvm, cvar->Name.c_str()); - g_pSquirrel<context>->arrayappend(sqvm, -2); - } - - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NOTNULL; // return empty array -} - -ADD_SQFUNC("void", NSReloadMods, "", "", ScriptContext::UI) -{ - g_pModManager->LoadMods(); - return SQRESULT_NULL; -} diff --git a/NorthstarDLL/scripts/client/scriptoriginauth.cpp b/NorthstarDLL/scripts/client/scriptoriginauth.cpp deleted file mode 100644 index 420c4872..00000000 --- a/NorthstarDLL/scripts/client/scriptoriginauth.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "squirrel/squirrel.h" -#include "masterserver/masterserver.h" -#include "engine/r2engine.h" -#include "client/r2client.h" - -ADD_SQFUNC("bool", NSIsMasterServerAuthenticated, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerDone); - return SQRESULT_NOTNULL; -} - -/* -global struct MasterServerAuthResult -{ - bool success - string errorCode - string errorMessage -} -*/ - -ADD_SQFUNC("MasterServerAuthResult", NSGetMasterServerAuthResult, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushnewstructinstance(sqvm, 3); - - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerSuccessful); - g_pSquirrel<context>->sealstructslot(sqvm, 0); - - g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_sOriginAuthWithMasterServerErrorCode.c_str(), -1); - g_pSquirrel<context>->sealstructslot(sqvm, 1); - - g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_sOriginAuthWithMasterServerErrorMessage.c_str(), -1); - g_pSquirrel<context>->sealstructslot(sqvm, 2); - - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp deleted file mode 100644 index a142c3f4..00000000 --- a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "squirrel/squirrel.h" -#include "masterserver/masterserver.h" -#include "server/auth/serverauthentication.h" -#include "engine/r2engine.h" -#include "client/r2client.h" - -// functions for viewing server browser - -ADD_SQFUNC("void", NSRequestServerList, "", "", ScriptContext::UI) -{ - g_pMasterServerManager->RequestServerList(); - return SQRESULT_NULL; -} - -ADD_SQFUNC("bool", NSIsRequestingServerList, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bScriptRequestingServerList); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("bool", NSMasterServerConnectionSuccessful, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyConnected); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("int", NSGetServerCount, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers.size()); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("void", NSClearRecievedServerList, "", "", ScriptContext::UI) -{ - g_pMasterServerManager->ClearServerList(); - return SQRESULT_NULL; -} - -// functions for authenticating with servers - -ADD_SQFUNC("void", NSTryAuthWithServer, "int serverIndex, string password = ''", "", ScriptContext::UI) -{ - SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); - const SQChar* password = g_pSquirrel<context>->getstring(sqvm, 2); - - if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "Tried to auth with server index {} when only {} servers are available", - serverIndex, - g_pMasterServerManager->m_vRemoteServers.size()) - .c_str()); - return SQRESULT_ERROR; - } - - // send off persistent data first, don't worry about server/client stuff, since m_additionalPlayerData should only have entries when - // we're a local server note: this seems like it could create a race condition, test later - for (auto& pair : g_pServerAuthentication->m_PlayerAuthenticationData) - g_pServerAuthentication->WritePersistentData(pair.first); - - // do auth - g_pMasterServerManager->AuthenticateWithServer( - g_pLocalPlayerUserID, - g_pMasterServerManager->m_sOwnClientAuthToken, - g_pMasterServerManager->m_vRemoteServers[serverIndex], - (char*)password); - - return SQRESULT_NULL; -} - -ADD_SQFUNC("bool", NSIsAuthenticatingWithServer, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bScriptAuthenticatingWithGameServer); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("bool", NSWasAuthSuccessful, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyAuthenticatedWithGameServer); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("void", NSConnectToAuthedServer, "", "", ScriptContext::UI) -{ - if (!g_pMasterServerManager->m_bHasPendingConnectionInfo) - { - g_pSquirrel<context>->raiseerror( - sqvm, fmt::format("Tried to connect to authed server before any pending connection info was available").c_str()); - return SQRESULT_ERROR; - } - - RemoteServerConnectionInfo& info = g_pMasterServerManager->m_pendingConnectionInfo; - - // set auth token, then try to connect - // i'm honestly not entirely sure how silentconnect works regarding ports and encryption so using connect for now - g_pCVar->FindVar("serverfilter")->SetValue(info.authToken); - Cbuf_AddText( - Cbuf_GetCurrentPlayer(), - fmt::format( - "connect {}.{}.{}.{}:{}", - info.ip.S_un.S_un_b.s_b1, - info.ip.S_un.S_un_b.s_b2, - info.ip.S_un.S_un_b.s_b3, - info.ip.S_un.S_un_b.s_b4, - info.port) - .c_str(), - cmd_source_t::kCommandSrcCode); - - g_pMasterServerManager->m_bHasPendingConnectionInfo = false; - return SQRESULT_NULL; -} - -ADD_SQFUNC("void", NSTryAuthWithLocalServer, "", "", ScriptContext::UI) -{ - // do auth request - g_pMasterServerManager->AuthenticateWithOwnServer(g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken); - - return SQRESULT_NULL; -} - -ADD_SQFUNC("void", NSCompleteAuthWithLocalServer, "", "", ScriptContext::UI) -{ - // literally just set serverfilter - // note: this assumes we have no authdata other than our own - if (g_pServerAuthentication->m_RemoteAuthenticationData.size()) - g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str()); - - return SQRESULT_NULL; -} - -ADD_SQFUNC("string", NSGetAuthFailReason, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_sAuthFailureReason.c_str(), -1); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("array<ServerInfo>", NSGetGameServers, "", "", ScriptContext::UI) -{ - g_pSquirrel<context>->newarray(sqvm, 0); - for (size_t i = 0; i < g_pMasterServerManager->m_vRemoteServers.size(); i++) - { - const RemoteServerInfo& remoteServer = g_pMasterServerManager->m_vRemoteServers[i]; - - g_pSquirrel<context>->pushnewstructinstance(sqvm, 11); - - // index - g_pSquirrel<context>->pushinteger(sqvm, i); - g_pSquirrel<context>->sealstructslot(sqvm, 0); - - // id - g_pSquirrel<context>->pushstring(sqvm, remoteServer.id, -1); - g_pSquirrel<context>->sealstructslot(sqvm, 1); - - // name - g_pSquirrel<context>->pushstring(sqvm, remoteServer.name, -1); - g_pSquirrel<context>->sealstructslot(sqvm, 2); - - // description - g_pSquirrel<context>->pushstring(sqvm, remoteServer.description.c_str(), -1); - g_pSquirrel<context>->sealstructslot(sqvm, 3); - - // map - g_pSquirrel<context>->pushstring(sqvm, remoteServer.map, -1); - g_pSquirrel<context>->sealstructslot(sqvm, 4); - - // playlist - g_pSquirrel<context>->pushstring(sqvm, remoteServer.playlist, -1); - g_pSquirrel<context>->sealstructslot(sqvm, 5); - - // playerCount - g_pSquirrel<context>->pushinteger(sqvm, remoteServer.playerCount); - g_pSquirrel<context>->sealstructslot(sqvm, 6); - - // maxPlayerCount - g_pSquirrel<context>->pushinteger(sqvm, remoteServer.maxPlayers); - g_pSquirrel<context>->sealstructslot(sqvm, 7); - - // requiresPassword - g_pSquirrel<context>->pushbool(sqvm, remoteServer.requiresPassword); - g_pSquirrel<context>->sealstructslot(sqvm, 8); - - // region - g_pSquirrel<context>->pushstring(sqvm, remoteServer.region, -1); - g_pSquirrel<context>->sealstructslot(sqvm, 9); - - // requiredMods - g_pSquirrel<context>->newarray(sqvm); - for (const RemoteModInfo& mod : remoteServer.requiredMods) - { - g_pSquirrel<context>->pushnewstructinstance(sqvm, 2); - - // name - g_pSquirrel<context>->pushstring(sqvm, mod.Name.c_str(), -1); - g_pSquirrel<context>->sealstructslot(sqvm, 0); - - // version - g_pSquirrel<context>->pushstring(sqvm, mod.Version.c_str(), -1); - g_pSquirrel<context>->sealstructslot(sqvm, 1); - - g_pSquirrel<context>->arrayappend(sqvm, -2); - } - g_pSquirrel<context>->sealstructslot(sqvm, 10); - - g_pSquirrel<context>->arrayappend(sqvm, -2); - } - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp b/NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp deleted file mode 100644 index a3a81c8a..00000000 --- a/NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "squirrel/squirrel.h" -#include "core/convar/convar.h" -#include "core/convar/concommand.h" - -void ConCommand_ns_script_servertoclientstringcommand(const CCommand& arg) -{ - if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM) - g_pSquirrel<ScriptContext::CLIENT>->Call("NSClientCodeCallback_RecievedServerToClientStringCommand", arg.ArgS()); -} - -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ScriptServerToClientStringCommand, ClientSquirrel, (CModule module)) -{ - RegisterConCommand( - "ns_script_servertoclientstringcommand", - ConCommand_ns_script_servertoclientstringcommand, - "", - FCVAR_CLIENTDLL | FCVAR_SERVER_CAN_EXECUTE); -} diff --git a/NorthstarDLL/scripts/scriptdatatables.cpp b/NorthstarDLL/scripts/scriptdatatables.cpp deleted file mode 100644 index 87a26dca..00000000 --- a/NorthstarDLL/scripts/scriptdatatables.cpp +++ /dev/null @@ -1,909 +0,0 @@ -#include "squirrel/squirrel.h" -#include "core/filesystem/rpakfilesystem.h" -#include "core/convar/convar.h" -#include "dedicated/dedicated.h" -#include "core/filesystem/filesystem.h" -#include "core/math/vector.h" -#include "core/tier0.h" -#include "engine/r2engine.h" -#include <iostream> -#include <sstream> -#include <map> -#include <fstream> -#include <filesystem> - -const uint64_t USERDATA_TYPE_DATATABLE = 0xFFF7FFF700000004; -const uint64_t USERDATA_TYPE_DATATABLE_CUSTOM = 0xFFFCFFFC12345678; - -enum class DatatableType : int -{ - BOOL = 0, - INT, - FLOAT, - VECTOR, - STRING, - ASSET, - UNK_STRING // unknown but deffo a string type -}; - -struct ColumnInfo -{ - char* name; - DatatableType type; - int offset; -}; - -struct Datatable -{ - int numColumns; - int numRows; - ColumnInfo* columnInfo; - char* data; // actually data pointer - int rowInfo; -}; - -ConVar* Cvar_ns_prefer_datatable_from_disk; - -template <ScriptContext context> Datatable* (*SQ_GetDatatableInternal)(HSquirrelVM* sqvm); - -struct CSVData -{ - std::string m_sAssetName; - std::string m_sCSVString; - char* m_pDataBuf; - size_t m_nDataBufSize; - - std::vector<char*> columns; - std::vector<std::vector<char*>> dataPointers; -}; - -std::unordered_map<std::string, CSVData> CSVCache; - -Vector3 StringToVector(char* pString) -{ - Vector3 vRet; - - int length = 0; - while (pString[length]) - { - if ((pString[length] == '<') || (pString[length] == '>')) - pString[length] = '\0'; - length++; - } - - int startOfFloat = 1; - int currentIndex = 1; - - while (pString[currentIndex] && (pString[currentIndex] != ',')) - currentIndex++; - pString[currentIndex] = '\0'; - vRet.x = std::stof(&pString[startOfFloat]); - startOfFloat = ++currentIndex; - - while (pString[currentIndex] && (pString[currentIndex] != ',')) - currentIndex++; - pString[currentIndex] = '\0'; - vRet.y = std::stof(&pString[startOfFloat]); - startOfFloat = ++currentIndex; - - while (pString[currentIndex] && (pString[currentIndex] != ',')) - currentIndex++; - pString[currentIndex] = '\0'; - vRet.z = std::stof(&pString[startOfFloat]); - startOfFloat = ++currentIndex; - - return vRet; -} - -// var function GetDataTable( asset path ) -REPLACE_SQFUNC(GetDataTable, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - const char* pAssetName; - g_pSquirrel<context>->getasset(sqvm, 2, &pAssetName); - - if (strncmp(pAssetName, "datatable/", 10)) - { - g_pSquirrel<context>->raiseerror(sqvm, fmt::format("Asset \"{}\" doesn't start with \"datatable/\"", pAssetName).c_str()); - return SQRESULT_ERROR; - } - else if (!Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) - return g_pSquirrel<context>->m_funcOriginals["GetDataTable"](sqvm); - // either we prefer disk datatables, or we're loading a datatable that wasn't found in rpak - else - { - std::string sAssetPath(fmt::format("scripts/{}", pAssetName)); - - // first, check the cache - if (CSVCache.find(pAssetName) != CSVCache.end()) - { - CSVData** pUserdata = g_pSquirrel<context>->template createuserdata<CSVData*>(sqvm, sizeof(CSVData*)); - g_pSquirrel<context>->setuserdatatypeid(sqvm, -1, USERDATA_TYPE_DATATABLE_CUSTOM); - *pUserdata = &CSVCache[pAssetName]; - - return SQRESULT_NOTNULL; - } - - // check files on disk - // we don't use .rpak as the extension for on-disk datatables, so we need to replace .rpak with .csv in the filename we're reading - fs::path diskAssetPath("scripts"); - if (fs::path(pAssetName).extension() == ".rpak") - diskAssetPath /= fs::path(pAssetName).remove_filename() / (fs::path(pAssetName).stem().string() + ".csv"); - else - diskAssetPath /= fs::path(pAssetName); - - std::string sDiskAssetPath(diskAssetPath.string()); - if ((*g_pFilesystem)->m_vtable2->FileExists(&(*g_pFilesystem)->m_vtable2, sDiskAssetPath.c_str(), "GAME")) - { - std::string sTableCSV = ReadVPKFile(sDiskAssetPath.c_str()); - if (!sTableCSV.size()) - { - g_pSquirrel<context>->raiseerror(sqvm, fmt::format("Datatable \"{}\" is empty", pAssetName).c_str()); - return SQRESULT_ERROR; - } - - // somewhat shit, but ensure we end with a newline to make parsing easier - if (sTableCSV[sTableCSV.length() - 1] != '\n') - sTableCSV += '\n'; - - CSVData csv; - csv.m_sAssetName = pAssetName; - csv.m_sCSVString = sTableCSV; - csv.m_nDataBufSize = sTableCSV.size(); - csv.m_pDataBuf = new char[csv.m_nDataBufSize]; - memcpy(csv.m_pDataBuf, &sTableCSV[0], csv.m_nDataBufSize); - - // parse the csv - // csvs are essentially comma and newline-deliniated sets of strings for parsing, only thing we need to worry about is quoted - // entries when we parse an element of the csv, rather than allocating an entry for it, we just convert that element to a - // null-terminated string i.e., store the ptr to the first char of it, then make the comma that delinates it a nullchar - - bool bHasColumns = false; - bool bInQuotes = false; - - std::vector<char*> vCurrentRow; - char* pElemStart = csv.m_pDataBuf; - char* pElemEnd = nullptr; - - for (int i = 0; i < csv.m_nDataBufSize; i++) - { - if (csv.m_pDataBuf[i] == '\r' && csv.m_pDataBuf[i + 1] == '\n') - { - if (!pElemEnd) - pElemEnd = csv.m_pDataBuf + i; - - continue; // next iteration can handle the \n - } - - // newline, end of a row - if (csv.m_pDataBuf[i] == '\n') - { - // shouldn't have newline in string - if (bInQuotes) - { - g_pSquirrel<context>->raiseerror(sqvm, "Unexpected \\n in string"); - return SQRESULT_ERROR; - } - - // push last entry to current row - if (pElemEnd) - *pElemEnd = '\0'; - else - csv.m_pDataBuf[i] = '\0'; - - vCurrentRow.push_back(pElemStart); - - // newline, push last line to csv data and go from there - if (!bHasColumns) - { - bHasColumns = true; - csv.columns = vCurrentRow; - } - else - csv.dataPointers.push_back(vCurrentRow); - - vCurrentRow.clear(); - // put start of current element at char after newline - pElemStart = csv.m_pDataBuf + i + 1; - pElemEnd = nullptr; - } - // we're starting or ending a quoted string - else if (csv.m_pDataBuf[i] == '"') - { - // start quoted string - if (!bInQuotes) - { - // shouldn't have quoted strings in column names - if (!bHasColumns) - { - g_pSquirrel<context>->raiseerror(sqvm, "Unexpected \" in column name"); - return SQRESULT_ERROR; - } - - bInQuotes = true; - // put start of current element at char after string begin - pElemStart = csv.m_pDataBuf + i + 1; - } - // end quoted string - else - { - pElemEnd = csv.m_pDataBuf + i; - bInQuotes = false; - } - } - // don't parse commas in quotes - else if (bInQuotes) - { - continue; - } - // comma, push new entry to current row - else if (csv.m_pDataBuf[i] == ',') - { - if (pElemEnd) - *pElemEnd = '\0'; - else - csv.m_pDataBuf[i] = '\0'; - - vCurrentRow.push_back(pElemStart); - // put start of next element at char after comma - pElemStart = csv.m_pDataBuf + i + 1; - pElemEnd = nullptr; - } - } - - // add to cache and return - CSVData** pUserdata = g_pSquirrel<context>->template createuserdata<CSVData*>(sqvm, sizeof(CSVData*)); - g_pSquirrel<context>->setuserdatatypeid(sqvm, -1, USERDATA_TYPE_DATATABLE_CUSTOM); - CSVCache[pAssetName] = csv; - *pUserdata = &CSVCache[pAssetName]; - - return SQRESULT_NOTNULL; - } - // the file doesn't exist on disk, check rpak if we haven't already - else if (Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) - return g_pSquirrel<context>->m_funcOriginals["GetDataTable"](sqvm); - // the file doesn't exist at all, error - else - { - g_pSquirrel<context>->raiseerror(sqvm, fmt::format("Datatable {} not found", pAssetName).c_str()); - return SQRESULT_ERROR; - } - } -} - -// int function GetDataTableColumnByName( var datatable, string columnName ) -REPLACE_SQFUNC(GetDataTableColumnByName, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableColumnByName"](sqvm); - - CSVData* csv = *pData; - const char* pColumnName = g_pSquirrel<context>->getstring(sqvm, 2); - - for (int i = 0; i < csv->columns.size(); i++) - { - if (!strcmp(csv->columns[i], pColumnName)) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - // column not found - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowCount( var datatable ) -REPLACE_SQFUNC(GetDataTableRowCount, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDatatableRowCount"](sqvm); - - CSVData* csv = *pData; - g_pSquirrel<context>->pushinteger(sqvm, csv->dataPointers.size()); - return SQRESULT_NOTNULL; -} - -// string function GetDataTableString( var datatable, int row, int col ) -REPLACE_SQFUNC(GetDataTableString, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableString"](sqvm); - - CSVData* csv = *pData; - const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); - if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushstring(sqvm, csv->dataPointers[nRow][nCol], -1); - return SQRESULT_NOTNULL; -} - -// asset function GetDataTableAsset( var datatable, int row, int col ) -REPLACE_SQFUNC(GetDataTableAsset, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableAsset"](sqvm); - - CSVData* csv = *pData; - const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); - if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushasset(sqvm, csv->dataPointers[nRow][nCol], -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableInt( var datatable, int row, int col ) -REPLACE_SQFUNC(GetDataTableInt, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableInt"](sqvm); - - CSVData* csv = *pData; - const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); - if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushinteger(sqvm, std::stoi(csv->dataPointers[nRow][nCol])); - return SQRESULT_NOTNULL; -} - -// float function GetDataTableFloat( var datatable, int row, int col ) -REPLACE_SQFUNC(GetDataTableFloat, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableFloat"](sqvm); - - CSVData* csv = *pData; - const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); - if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushfloat(sqvm, std::stof(csv->dataPointers[nRow][nCol])); - return SQRESULT_NOTNULL; -} - -// bool function GetDataTableBool( var datatable, int row, int col ) -REPLACE_SQFUNC(GetDataTableBool, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableBool"](sqvm); - - CSVData* csv = *pData; - const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); - if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushbool(sqvm, std::stoi(csv->dataPointers[nRow][nCol])); - return SQRESULT_NOTNULL; -} - -// vector function GetDataTableVector( var datatable, int row, int col ) -REPLACE_SQFUNC(GetDataTableVector, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableVector"](sqvm); - - CSVData* csv = *pData; - const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); - if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) - { - g_pSquirrel<context>->raiseerror( - sqvm, - fmt::format( - "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) - .c_str()); - return SQRESULT_ERROR; - } - - g_pSquirrel<context>->pushvector(sqvm, StringToVector(csv->dataPointers[nRow][nCol])); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowMatchingStringValue( var datatable, int col, string value ) -REPLACE_SQFUNC(GetDataTableRowMatchingStringValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingStringValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const char* pStringVal = g_pSquirrel<context>->getstring(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (!strcmp(csv->dataPointers[i][nCol], pStringVal)) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowMatchingAssetValue( var datatable, int col, asset value ) -REPLACE_SQFUNC(GetDataTableMatchingAssetValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingAssetValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const char* pStringVal; - g_pSquirrel<context>->getasset(sqvm, 3, &pStringVal); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (!strcmp(csv->dataPointers[i][nCol], pStringVal)) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowMatchingFloatValue( var datatable, int col, float value ) -REPLACE_SQFUNC(GetDataTableRowMatchingFloatValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingFloatValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const float flFloatVal = g_pSquirrel<context>->getfloat(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (flFloatVal == std::stof(csv->dataPointers[i][nCol])) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowMatchingIntValue( var datatable, int col, int value ) -REPLACE_SQFUNC(GetDataTableRowMatchingIntValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingIntValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nIntVal = g_pSquirrel<context>->getinteger(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (nIntVal == std::stoi(csv->dataPointers[i][nCol])) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowMatchingVectorValue( var datatable, int col, vector value ) -REPLACE_SQFUNC(GetDataTableRowMatchingVectorValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingVectorValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const Vector3 vVectorVal = g_pSquirrel<context>->getvector(sqvm, 3); - - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (vVectorVal == StringToVector(csv->dataPointers[i][nCol])) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowGreaterThanOrEqualToIntValue( var datatable, int col, int value ) -REPLACE_SQFUNC(GetDataTableRowGreaterThanOrEqualToIntValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowGreaterThanOrEqualToIntValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nIntVal = g_pSquirrel<context>->getinteger(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (nIntVal >= std::stoi(csv->dataPointers[i][nCol])) - { - spdlog::info("datatable not loaded"); - g_pSquirrel<context>->pushinteger(sqvm, 1); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowLessThanOrEqualToIntValue( var datatable, int col, int value ) -REPLACE_SQFUNC(GetDataTableRowLessThanOrEqualToIntValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowLessThanOrEqualToIntValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const int nIntVal = g_pSquirrel<context>->getinteger(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (nIntVal <= std::stoi(csv->dataPointers[i][nCol])) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowGreaterThanOrEqualToFloatValue( var datatable, int col, float value ) -REPLACE_SQFUNC(GetDataTableRowGreaterThanOrEqualToFloatValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowGreaterThanOrEqualToFloatValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const float flFloatVal = g_pSquirrel<context>->getfloat(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (flFloatVal >= std::stof(csv->dataPointers[i][nCol])) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -// int function GetDataTableRowLessThanOrEqualToFloatValue( var datatable, int col, float value ) -REPLACE_SQFUNC(GetDataTableRowLessThanOrEqualToFloatValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) -{ - CSVData** pData; - uint64_t typeId; - g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); - - if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) - return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowLessThanOrEqualToFloatValue"](sqvm); - - CSVData* csv = *pData; - int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); - const float flFloatVal = g_pSquirrel<context>->getfloat(sqvm, 3); - for (int i = 0; i < csv->dataPointers.size(); i++) - { - if (flFloatVal <= std::stof(csv->dataPointers[i][nCol])) - { - g_pSquirrel<context>->pushinteger(sqvm, i); - return SQRESULT_NOTNULL; - } - } - - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; -} - -std::string DataTableToString(Datatable* datatable) -{ - std::string sCSVString; - - // write columns - bool bShouldComma = false; - for (int i = 0; i < datatable->numColumns; i++) - { - if (bShouldComma) - sCSVString += ','; - else - bShouldComma = true; - - sCSVString += datatable->columnInfo[i].name; - } - - // write rows - for (int row = 0; row < datatable->numRows; row++) - { - sCSVString += '\n'; - - bool bShouldComma = false; - for (int col = 0; col < datatable->numColumns; col++) - { - if (bShouldComma) - sCSVString += ','; - else - bShouldComma = true; - - // output typed data - ColumnInfo column = datatable->columnInfo[col]; - const void* pUntypedVal = datatable->data + column.offset + row * datatable->rowInfo; - switch (column.type) - { - case DatatableType::BOOL: - { - sCSVString += *(bool*)pUntypedVal ? '1' : '0'; - break; - } - - case DatatableType::INT: - { - sCSVString += std::to_string(*(int*)pUntypedVal); - break; - } - - case DatatableType::FLOAT: - { - sCSVString += std::to_string(*(float*)pUntypedVal); - break; - } - - case DatatableType::VECTOR: - { - Vector3* pVector = (Vector3*)(pUntypedVal); - sCSVString += fmt::format("<{},{},{}>", pVector->x, pVector->y, pVector->z); - break; - } - - case DatatableType::STRING: - case DatatableType::ASSET: - case DatatableType::UNK_STRING: - { - sCSVString += fmt::format("\"{}\"", *(char**)pUntypedVal); - break; - } - } - } - } - - return sCSVString; -} - -void DumpDatatable(const char* pDatatablePath) -{ - Datatable* pDatatable = (Datatable*)g_pPakLoadManager->LoadFile(pDatatablePath); - if (!pDatatable) - { - spdlog::error("couldn't load datatable {} (rpak containing it may not be loaded?)", pDatatablePath); - return; - } - - std::string sOutputPath(fmt::format("{}/scripts/datatable/{}.csv", g_pModName, fs::path(pDatatablePath).stem().string())); - std::string sDatatableContents(DataTableToString(pDatatable)); - - fs::create_directories(fs::path(sOutputPath).remove_filename()); - std::ofstream outputStream(sOutputPath); - outputStream.write(sDatatableContents.c_str(), sDatatableContents.size()); - outputStream.close(); - - spdlog::info("dumped datatable {} {} to {}", pDatatablePath, (void*)pDatatable, sOutputPath); -} - -void ConCommand_dump_datatable(const CCommand& args) -{ - if (args.ArgC() < 2) - { - spdlog::info("usage: dump_datatable datatable/tablename.rpak"); - return; - } - - DumpDatatable(args.Arg(1)); -} - -void ConCommand_dump_datatables(const CCommand& args) -{ - // likely not a comprehensive list, might be missing a couple? - static const std::vector<const char*> VANILLA_DATATABLE_PATHS = { - "datatable/burn_meter_rewards.rpak", - "datatable/burn_meter_store.rpak", - "datatable/calling_cards.rpak", - "datatable/callsign_icons.rpak", - "datatable/camo_skins.rpak", - "datatable/default_pilot_loadouts.rpak", - "datatable/default_titan_loadouts.rpak", - "datatable/faction_leaders.rpak", - "datatable/fd_awards.rpak", - "datatable/features_mp.rpak", - "datatable/non_loadout_weapons.rpak", - "datatable/pilot_abilities.rpak", - "datatable/pilot_executions.rpak", - "datatable/pilot_passives.rpak", - "datatable/pilot_properties.rpak", - "datatable/pilot_weapons.rpak", - "datatable/pilot_weapon_features.rpak", - "datatable/pilot_weapon_mods.rpak", - "datatable/pilot_weapon_mods_common.rpak", - "datatable/playlist_items.rpak", - "datatable/titans_mp.rpak", - "datatable/titan_abilities.rpak", - "datatable/titan_executions.rpak", - "datatable/titan_fd_upgrades.rpak", - "datatable/titan_nose_art.rpak", - "datatable/titan_passives.rpak", - "datatable/titan_primary_mods.rpak", - "datatable/titan_primary_mods_common.rpak", - "datatable/titan_primary_weapons.rpak", - "datatable/titan_properties.rpak", - "datatable/titan_skins.rpak", - "datatable/titan_voices.rpak", - "datatable/unlocks_faction_level.rpak", - "datatable/unlocks_fd_titan_level.rpak", - "datatable/unlocks_player_level.rpak", - "datatable/unlocks_random.rpak", - "datatable/unlocks_titan_level.rpak", - "datatable/unlocks_weapon_level_pilot.rpak", - "datatable/weapon_skins.rpak", - "datatable/xp_per_faction_level.rpak", - "datatable/xp_per_fd_titan_level.rpak", - "datatable/xp_per_player_level.rpak", - "datatable/xp_per_titan_level.rpak", - "datatable/xp_per_weapon_level.rpak", - "datatable/faction_leaders_dropship_anims.rpak", - "datatable/score_events.rpak", - "datatable/startpoints.rpak", - "datatable/sp_levels.rpak", - "datatable/community_entries.rpak", - "datatable/spotlight_images.rpak", - "datatable/death_hints_mp.rpak", - "datatable/flightpath_assets.rpak", - "datatable/earn_meter_mp.rpak", - "datatable/battle_chatter_voices.rpak", - "datatable/battle_chatter.rpak", - "datatable/titan_os_conversations.rpak", - "datatable/faction_dialogue.rpak", - "datatable/grunt_chatter_mp.rpak", - "datatable/spectre_chatter_mp.rpak", - "datatable/pain_death_sounds.rpak", - "datatable/caller_ids_mp.rpak"}; - - for (const char* datatable : VANILLA_DATATABLE_PATHS) - DumpDatatable(datatable); -} - -ON_DLL_LOAD_RELIESON("server.dll", ServerScriptDatatables, ServerSquirrel, (CModule module)) -{ - SQ_GetDatatableInternal<ScriptContext::SERVER> = module.Offset(0x1250f0).RCast<Datatable* (*)(HSquirrelVM*)>(); -} - -ON_DLL_LOAD_RELIESON("client.dll", ClientScriptDatatables, ClientSquirrel, (CModule module)) -{ - SQ_GetDatatableInternal<ScriptContext::CLIENT> = module.Offset(0x1C9070).RCast<Datatable* (*)(HSquirrelVM*)>(); - SQ_GetDatatableInternal<ScriptContext::UI> = SQ_GetDatatableInternal<ScriptContext::CLIENT>; -} - -ON_DLL_LOAD_RELIESON("engine.dll", SharedScriptDataTables, ConVar, (CModule module)) -{ - Cvar_ns_prefer_datatable_from_disk = new ConVar( - "ns_prefer_datatable_from_disk", - IsDedicatedServer() && CommandLine()->CheckParm("-nopakdedi") ? "1" : "0", - FCVAR_NONE, - "whether to prefer loading datatables from disk, rather than rpak"); - - RegisterConCommand("dump_datatables", ConCommand_dump_datatables, "dumps all datatables from a hardcoded list", FCVAR_NONE); - RegisterConCommand("dump_datatable", ConCommand_dump_datatable, "dump a datatable", FCVAR_NONE); -} diff --git a/NorthstarDLL/scripts/scripthttprequesthandler.cpp b/NorthstarDLL/scripts/scripthttprequesthandler.cpp deleted file mode 100644 index aa75127a..00000000 --- a/NorthstarDLL/scripts/scripthttprequesthandler.cpp +++ /dev/null @@ -1,585 +0,0 @@ -#include "scripthttprequesthandler.h" -#include "util/version.h" -#include "squirrel/squirrel.h" -#include "core/tier0.h" - -HttpRequestHandler* g_httpRequestHandler; - -bool IsHttpDisabled() -{ - const static bool bIsHttpDisabled = CommandLine()->FindParm("-disablehttprequests"); - return bIsHttpDisabled; -} - -bool IsLocalHttpAllowed() -{ - const static bool bIsLocalHttpAllowed = CommandLine()->FindParm("-allowlocalhttp"); - return bIsLocalHttpAllowed; -} - -bool DisableHttpSsl() -{ - const static bool bDisableHttpSsl = CommandLine()->FindParm("-disablehttpssl"); - return bDisableHttpSsl; -} - -HttpRequestHandler::HttpRequestHandler() -{ - // Cache the launch parameters as early as possible in order to avoid possible exploits that change them at runtime. - IsHttpDisabled(); - IsLocalHttpAllowed(); - DisableHttpSsl(); -} - -void HttpRequestHandler::StartHttpRequestHandler() -{ - if (IsRunning()) - { - spdlog::warn("%s was called while IsRunning() is true!", __FUNCTION__); - return; - } - - m_bIsHttpRequestHandlerRunning = true; - spdlog::info("HttpRequestHandler started."); -} - -void HttpRequestHandler::StopHttpRequestHandler() -{ - if (!IsRunning()) - { - spdlog::warn("%s was called while IsRunning() is false", __FUNCTION__); - return; - } - - m_bIsHttpRequestHandlerRunning = false; - spdlog::info("HttpRequestHandler stopped."); -} - -bool IsHttpDestinationHostAllowed(const std::string& host, std::string& outHostname, std::string& outAddress, std::string& outPort) -{ - CURLU* url = curl_url(); - if (!url) - { - spdlog::error("Failed to call curl_url() for http request."); - return false; - } - - if (curl_url_set(url, CURLUPART_URL, host.c_str(), CURLU_DEFAULT_SCHEME) != CURLUE_OK) - { - spdlog::error("Failed to parse destination URL for http request."); - - curl_url_cleanup(url); - return false; - } - - char* urlHostname = nullptr; - if (curl_url_get(url, CURLUPART_HOST, &urlHostname, 0) != CURLUE_OK) - { - spdlog::error("Failed to parse hostname from destination URL for http request."); - - curl_url_cleanup(url); - return false; - } - - char* urlScheme = nullptr; - if (curl_url_get(url, CURLUPART_SCHEME, &urlScheme, CURLU_DEFAULT_SCHEME) != CURLUE_OK) - { - spdlog::error("Failed to parse scheme from destination URL for http request."); - - curl_url_cleanup(url); - curl_free(urlHostname); - return false; - } - - char* urlPort = nullptr; - if (curl_url_get(url, CURLUPART_PORT, &urlPort, CURLU_DEFAULT_PORT) != CURLUE_OK) - { - spdlog::error("Failed to parse port from destination URL for http request."); - - curl_url_cleanup(url); - curl_free(urlHostname); - curl_free(urlScheme); - return false; - } - - // Resolve the hostname into an address. - addrinfo* result; - addrinfo hints; - std::memset(&hints, 0, sizeof(addrinfo)); - hints.ai_family = AF_UNSPEC; - - if (getaddrinfo(urlHostname, urlScheme, &hints, &result) != 0) - { - spdlog::error("Failed to resolve http request destination {} using getaddrinfo().", urlHostname); - - curl_url_cleanup(url); - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - return false; - } - - bool bFoundIPv6 = false; - sockaddr_in* sockaddr_ipv4 = nullptr; - for (addrinfo* info = result; info; info = info->ai_next) - { - if (info->ai_family == AF_INET) - { - sockaddr_ipv4 = (sockaddr_in*)info->ai_addr; - break; - } - - bFoundIPv6 = bFoundIPv6 || info->ai_family == AF_INET6; - } - - if (sockaddr_ipv4 == nullptr) - { - if (bFoundIPv6) - { - spdlog::error("Only IPv4 destinations are supported for HTTP requests. To allow IPv6, launch the game using -allowlocalhttp."); - } - else - { - spdlog::error("Failed to resolve http request destination {} into a valid IPv4 address.", urlHostname); - } - - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - curl_url_cleanup(url); - - return false; - } - - // Fast checks for private ranges of IPv4. - // clang-format off - { - auto addrBytes = sockaddr_ipv4->sin_addr.S_un.S_un_b; - - if (addrBytes.s_b1 == 10 // 10.0.0.0 - 10.255.255.255 (Class A Private) - || addrBytes.s_b1 == 172 && addrBytes.s_b2 >= 16 && addrBytes.s_b2 <= 31 // 172.16.0.0 - 172.31.255.255 (Class B Private) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 168 // 192.168.0.0 - 192.168.255.255 (Class C Private) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 0 && addrBytes.s_b3 == 0 // 192.0.0.0 - 192.0.0.255 (IETF Assignment) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 0 && addrBytes.s_b3 == 2 // 192.0.2.0 - 192.0.2.255 (TEST-NET-1) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 88 && addrBytes.s_b3 == 99 // 192.88.99.0 - 192.88.99.255 (IPv4-IPv6 Relay) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 >= 18 && addrBytes.s_b2 <= 19 // 192.18.0.0 - 192.19.255.255 (Internet Benchmark) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 51 && addrBytes.s_b3 == 100 // 192.51.100.0 - 192.51.100.255 (TEST-NET-2) - || addrBytes.s_b1 == 203 && addrBytes.s_b2 == 0 && addrBytes.s_b3 == 113 // 203.0.113.0 - 203.0.113.255 (TEST-NET-3) - || addrBytes.s_b1 == 169 && addrBytes.s_b2 == 254 // 169.254.00 - 169.254.255.255 (Link-local/APIPA) - || addrBytes.s_b1 == 127 // 127.0.0.0 - 127.255.255.255 (Loopback) - || addrBytes.s_b1 == 0 // 0.0.0.0 - 0.255.255.255 (Current network) - || addrBytes.s_b1 == 100 && addrBytes.s_b2 >= 64 && addrBytes.s_b2 <= 127 // 100.64.0.0 - 100.127.255.255 (Shared address space) - || sockaddr_ipv4->sin_addr.S_un.S_addr == 0xFFFFFFFF // 255.255.255.255 (Broadcast) - || addrBytes.s_b1 >= 224 && addrBytes.s_b2 <= 239 // 224.0.0.0 - 239.255.255.255 (Multicast) - || addrBytes.s_b1 == 233 && addrBytes.s_b2 == 252 && addrBytes.s_b3 == 0 // 233.252.0.0 - 233.252.0.255 (MCAST-TEST-NET) - || addrBytes.s_b1 >= 240 && addrBytes.s_b4 <= 254) // 240.0.0.0 - 255.255.255.254 (Future Use Class E) - { - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - curl_url_cleanup(url); - - return false; - } - } - - // clang-format on - - char resolvedStr[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &sockaddr_ipv4->sin_addr, resolvedStr, INET_ADDRSTRLEN); - - // Use the resolved address as the new request host. - outHostname = urlHostname; - outAddress = resolvedStr; - outPort = urlPort; - - freeaddrinfo(result); - - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - curl_url_cleanup(url); - - return true; -} - -size_t HttpCurlWriteToStringBufferCallback(char* contents, size_t size, size_t nmemb, void* userp) -{ - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; -} - -template <ScriptContext context> int HttpRequestHandler::MakeHttpRequest(const HttpRequest& requestParameters) -{ - if (!IsRunning()) - { - spdlog::warn("%s was called while IsRunning() is false!", __FUNCTION__); - return -1; - } - - if (IsHttpDisabled()) - { - spdlog::warn("NS_InternalMakeHttpRequest called while the game is running with -disablehttprequests." - " Please check if requests are allowed using NSIsHttpEnabled() first."); - return -1; - } - - bool bAllowLocalHttp = IsLocalHttpAllowed(); - - // This handle will be returned to Squirrel so it can wait for the response and assign a callback for it. - int handle = ++m_iLastRequestHandle; - - std::thread requestThread( - [this, handle, requestParameters, bAllowLocalHttp]() - { - std::string hostname, resolvedAddress, resolvedPort; - - if (!bAllowLocalHttp) - { - if (!IsHttpDestinationHostAllowed(requestParameters.baseUrl, hostname, resolvedAddress, resolvedPort)) - { - spdlog::warn( - "HttpRequestHandler::MakeHttpRequest attempted to make a request to a private network. This is only allowed when " - "running the game with -allowlocalhttp."); - g_pSquirrel<context>->AsyncCall( - "NSHandleFailedHttpRequest", - handle, - (int)0, - "Cannot make HTTP requests to private network hosts without -allowlocalhttp. Check your console for more " - "information."); - return; - } - } - - CURL* curl = curl_easy_init(); - if (!curl) - { - spdlog::error("HttpRequestHandler::MakeHttpRequest failed to init libcurl for request."); - g_pSquirrel<context>->AsyncCall( - "NSHandleFailedHttpRequest", handle, static_cast<int>(CURLE_FAILED_INIT), curl_easy_strerror(CURLE_FAILED_INIT)); - return; - } - - // HEAD has no body. - if (requestParameters.method == HttpRequestMethod::HRM_HEAD) - { - curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); - } - - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, HttpRequestMethod::ToString(requestParameters.method).c_str()); - - // Only resolve to IPv4 if we don't allow private network requests. - curl_slist* host = nullptr; - if (!bAllowLocalHttp) - { - curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - host = curl_slist_append(host, fmt::format("{}:{}:{}", hostname, resolvedPort, resolvedAddress).c_str()); - curl_easy_setopt(curl, CURLOPT_RESOLVE, host); - } - - // Ensure we only allow HTTP or HTTPS. - curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - - // Allow redirects - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L); - - // Check if the url already contains a query. - // If so, we'll know to append with & instead of start with ? - std::string queryUrl = requestParameters.baseUrl; - bool bUrlContainsQuery = false; - - // If this fails, just ignore the parsing and trust what the user wants to query. - // Probably will fail but handling it here would be annoying. - CURLU* curlUrl = curl_url(); - if (curlUrl) - { - if (curl_url_set(curlUrl, CURLUPART_URL, queryUrl.c_str(), CURLU_DEFAULT_SCHEME) == CURLUE_OK) - { - char* currentQuery; - if (curl_url_get(curlUrl, CURLUPART_QUERY, ¤tQuery, 0) == CURLUE_OK) - { - if (currentQuery && std::strlen(currentQuery) != 0) - { - bUrlContainsQuery = true; - } - } - - curl_free(currentQuery); - } - - curl_url_cleanup(curlUrl); - } - - // GET requests, or POST-like requests with an empty body, can have query parameters. - // Append them to the base url. - if (HttpRequestMethod::CanHaveQueryParameters(requestParameters.method) && - !HttpRequestMethod::UsesCurlPostOptions(requestParameters.method) || - requestParameters.body.empty()) - { - bool isFirstValue = true; - for (const auto& kv : requestParameters.queryParameters) - { - char* key = curl_easy_escape(curl, kv.first.c_str(), kv.first.length()); - - for (const std::string& queryValue : kv.second) - { - char* value = curl_easy_escape(curl, queryValue.c_str(), queryValue.length()); - - if (isFirstValue && !bUrlContainsQuery) - { - queryUrl.append(fmt::format("?{}={}", key, value)); - isFirstValue = false; - } - else - { - queryUrl.append(fmt::format("&{}={}", key, value)); - } - - curl_free(value); - } - - curl_free(key); - } - } - - // If this method uses POST-like curl options, set those and set the body. - // The body won't be sent if it's empty anyway, meaning the query parameters above, if any, would be. - if (HttpRequestMethod::UsesCurlPostOptions(requestParameters.method)) - { - // Grab the body and set it as a POST field - curl_easy_setopt(curl, CURLOPT_POST, 1L); - - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, requestParameters.body.length()); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, requestParameters.body.c_str()); - } - - // Set the full URL for this http request. - curl_easy_setopt(curl, CURLOPT_URL, queryUrl.c_str()); - - std::string bodyBuffer; - std::string headerBuffer; - - // Set up buffers to write the response headers and body. - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpCurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &bodyBuffer); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HttpCurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headerBuffer); - - // Add all the headers for the request. - curl_slist* headers = nullptr; - - // Content-Type header for POST-like requests. - if (HttpRequestMethod::UsesCurlPostOptions(requestParameters.method) && !requestParameters.body.empty()) - { - headers = curl_slist_append(headers, fmt::format("Content-Type: {}", requestParameters.contentType).c_str()); - } - - for (const auto& kv : requestParameters.headers) - { - for (const std::string& headerValue : kv.second) - { - headers = curl_slist_append(headers, fmt::format("{}: {}", kv.first, headerValue).c_str()); - } - } - - if (headers != nullptr) - { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - } - - // Disable SSL checks if requested by the user. - if (DisableHttpSsl()) - { - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 0L); - } - - // Enforce the Northstar user agent, unless an override was specified. - if (requestParameters.userAgent.empty()) - { - curl_easy_setopt(curl, CURLOPT_USERAGENT, &NSUserAgent); - } - else - { - curl_easy_setopt(curl, CURLOPT_USERAGENT, requestParameters.userAgent.c_str()); - } - - // Set the timeout for this request. Max 60 seconds so mods can't just spin up native threads all the time. - curl_easy_setopt(curl, CURLOPT_TIMEOUT, std::clamp<long>(requestParameters.timeout, 1, 60)); - - CURLcode result = curl_easy_perform(curl); - if (IsRunning()) - { - if (result == CURLE_OK) - { - // While the curl request is OK, it could return a non success code. - // Squirrel side will handle firing the correct callback. - long httpCode = 0; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); - g_pSquirrel<context>->AsyncCall( - "NSHandleSuccessfulHttpRequest", handle, static_cast<int>(httpCode), bodyBuffer, headerBuffer); - } - else - { - // Pass CURL result code & error. - spdlog::error( - "curl_easy_perform() failed with code {}, error: {}", static_cast<int>(result), curl_easy_strerror(result)); - - // If it's an SSL issue, tell the user they may disable SSL checks using -disablehttpssl. - if (result == CURLE_PEER_FAILED_VERIFICATION || result == CURLE_SSL_CERTPROBLEM || - result == CURLE_SSL_INVALIDCERTSTATUS) - { - spdlog::error("You can try disabling SSL verifications for this issue using the -disablehttpssl launch argument. " - "Keep in mind this is potentially dangerous!"); - } - - g_pSquirrel<context>->AsyncCall( - "NSHandleFailedHttpRequest", handle, static_cast<int>(result), curl_easy_strerror(result)); - } - } - - curl_easy_cleanup(curl); - curl_slist_free_all(headers); - curl_slist_free_all(host); - }); - - requestThread.detach(); - return handle; -} - -// int NS_InternalMakeHttpRequest(int method, string baseUrl, table<string, string> headers, table<string, string> queryParams, -// string contentType, string body, int timeout, string userAgent) -template <ScriptContext context> SQRESULT SQ_InternalMakeHttpRequest(HSquirrelVM* sqvm) -{ - if (!g_httpRequestHandler || !g_httpRequestHandler->IsRunning()) - { - spdlog::warn("NS_InternalMakeHttpRequest called while the http request handler isn't running."); - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; - } - - if (IsHttpDisabled()) - { - spdlog::warn("NS_InternalMakeHttpRequest called while the game is running with -disablehttprequests." - " Please check if requests are allowed using NSIsHttpEnabled() first."); - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; - } - - HttpRequest request; - request.method = static_cast<HttpRequestMethod::Type>(g_pSquirrel<context>->getinteger(sqvm, 1)); - request.baseUrl = g_pSquirrel<context>->getstring(sqvm, 2); - - // Read the tables for headers and query parameters. - SQTable* headerTable = sqvm->_stackOfCurrentFunction[3]._VAL.asTable; - for (int idx = 0; idx < headerTable->_numOfNodes; ++idx) - { - tableNode* node = &headerTable->_nodes[idx]; - - if (node->key._Type == OT_STRING && node->val._Type == OT_ARRAY) - { - SQArray* valueArray = node->val._VAL.asArray; - std::vector<std::string> headerValues; - - for (int vIdx = 0; vIdx < valueArray->_usedSlots; ++vIdx) - { - if (valueArray->_values[vIdx]._Type == OT_STRING) - { - headerValues.push_back(valueArray->_values[vIdx]._VAL.asString->_val); - } - } - - request.headers[node->key._VAL.asString->_val] = headerValues; - } - } - - SQTable* queryTable = sqvm->_stackOfCurrentFunction[4]._VAL.asTable; - for (int idx = 0; idx < queryTable->_numOfNodes; ++idx) - { - tableNode* node = &queryTable->_nodes[idx]; - - if (node->key._Type == OT_STRING && node->val._Type == OT_ARRAY) - { - SQArray* valueArray = node->val._VAL.asArray; - std::vector<std::string> queryValues; - - for (int vIdx = 0; vIdx < valueArray->_usedSlots; ++vIdx) - { - if (valueArray->_values[vIdx]._Type == OT_STRING) - { - queryValues.push_back(valueArray->_values[vIdx]._VAL.asString->_val); - } - } - - request.queryParameters[node->key._VAL.asString->_val] = queryValues; - } - } - - request.contentType = g_pSquirrel<context>->getstring(sqvm, 5); - request.body = g_pSquirrel<context>->getstring(sqvm, 6); - request.timeout = g_pSquirrel<context>->getinteger(sqvm, 7); - request.userAgent = g_pSquirrel<context>->getstring(sqvm, 8); - - int handle = g_httpRequestHandler->MakeHttpRequest<context>(request); - g_pSquirrel<context>->pushinteger(sqvm, handle); - return SQRESULT_NOTNULL; -} - -// bool NSIsHttpEnabled() -template <ScriptContext context> SQRESULT SQ_IsHttpEnabled(HSquirrelVM* sqvm) -{ - g_pSquirrel<context>->pushbool(sqvm, !IsHttpDisabled()); - return SQRESULT_NOTNULL; -} - -// bool NSIsLocalHttpAllowed() -template <ScriptContext context> SQRESULT SQ_IsLocalHttpAllowed(HSquirrelVM* sqvm) -{ - g_pSquirrel<context>->pushbool(sqvm, IsLocalHttpAllowed()); - return SQRESULT_NOTNULL; -} - -template <ScriptContext context> void HttpRequestHandler::RegisterSQFuncs() -{ - g_pSquirrel<context>->AddFuncRegistration( - "int", - "NS_InternalMakeHttpRequest", - "int method, string baseUrl, table<string, array<string> > headers, table<string, array<string> > queryParams, string contentType, " - "string body, " - "int timeout, string userAgent", - "[Internal use only] Passes the HttpRequest struct fields to be reconstructed in native and used for an http request", - SQ_InternalMakeHttpRequest<context>); - - g_pSquirrel<context>->AddFuncRegistration( - "bool", - "NSIsHttpEnabled", - "", - "Whether or not HTTP requests are enabled. You can opt-out by starting the game with -disablehttprequests.", - SQ_IsHttpEnabled<context>); - - g_pSquirrel<context>->AddFuncRegistration( - "bool", - "NSIsLocalHttpAllowed", - "", - "Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with " - "-allowlocalhttp.", - SQ_IsLocalHttpAllowed<context>); -} - -ON_DLL_LOAD_RELIESON("client.dll", HttpRequestHandler_ClientInit, ClientSquirrel, (CModule module)) -{ - g_httpRequestHandler->RegisterSQFuncs<ScriptContext::CLIENT>(); - g_httpRequestHandler->RegisterSQFuncs<ScriptContext::UI>(); -} - -ON_DLL_LOAD_RELIESON("server.dll", HttpRequestHandler_ServerInit, ServerSquirrel, (CModule module)) -{ - g_httpRequestHandler->RegisterSQFuncs<ScriptContext::SERVER>(); -} - -ON_DLL_LOAD("engine.dll", HttpRequestHandler_Init, (CModule module)) -{ - g_httpRequestHandler = new HttpRequestHandler; - g_httpRequestHandler->StartHttpRequestHandler(); -} diff --git a/NorthstarDLL/scripts/scripthttprequesthandler.h b/NorthstarDLL/scripts/scripthttprequesthandler.h deleted file mode 100644 index f3921f4e..00000000 --- a/NorthstarDLL/scripts/scripthttprequesthandler.h +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -enum class ScriptContext; - -// These definitions below should match on the Squirrel side so we can easily pass them along through a function. - -/** - * Allowed methods for an HttpRequest. - */ -namespace HttpRequestMethod -{ - enum Type - { - HRM_GET = 0, - HRM_POST = 1, - HRM_HEAD = 2, - HRM_PUT = 3, - HRM_DELETE = 4, - HRM_PATCH = 5, - HRM_OPTIONS = 6, - }; - - /** Returns the HTTP string representation of the given method. */ - inline std::string ToString(HttpRequestMethod::Type method) - { - switch (method) - { - case HttpRequestMethod::HRM_GET: - return "GET"; - case HttpRequestMethod::HRM_POST: - return "POST"; - case HttpRequestMethod::HRM_HEAD: - return "HEAD"; - case HttpRequestMethod::HRM_PUT: - return "PUT"; - case HttpRequestMethod::HRM_DELETE: - return "DELETE"; - case HttpRequestMethod::HRM_PATCH: - return "PATCH"; - case HttpRequestMethod::HRM_OPTIONS: - return "OPTIONS"; - default: - return "INVALID"; - } - } - - /** Whether or not the given method should be treated like a POST for curlopts. */ - bool UsesCurlPostOptions(HttpRequestMethod::Type method) - { - switch (method) - { - case HttpRequestMethod::HRM_POST: - case HttpRequestMethod::HRM_PUT: - case HttpRequestMethod::HRM_DELETE: - case HttpRequestMethod::HRM_PATCH: - return true; - default: - return false; - } - } - - /** Whether or not the given http request method can have query parameters in the URL. */ - bool CanHaveQueryParameters(HttpRequestMethod::Type method) - { - return method == HttpRequestMethod::HRM_GET || UsesCurlPostOptions(method); - } -}; // namespace HttpRequestMethod - -/** Contains data about an http request that has been queued. */ -struct HttpRequest -{ - /** Method used for this http request. */ - HttpRequestMethod::Type method; - - /** Base URL of this http request. */ - std::string baseUrl; - - /** Headers used for this http request. Some may get overridden or ignored. */ - std::unordered_map<std::string, std::vector<std::string>> headers; - - /** Query parameters for this http request. */ - std::unordered_map<std::string, std::vector<std::string>> queryParameters; - - /** The content type of this http request. Defaults to text/plain & UTF-8 charset. */ - std::string contentType = "text/plain; charset=utf-8"; - - /** The body of this http request. If set, will override queryParameters.*/ - std::string body; - - /** The timeout for the http request, in seconds. Must be between 1 and 60. */ - int timeout; - - /** If set, the override to use for the User-Agent header. */ - std::string userAgent; -}; - -/** - * Handles making HTTP requests and sending the responses back to Squirrel. - */ -class HttpRequestHandler -{ -public: - HttpRequestHandler(); - - // Start/Stop the HTTP request handler. Right now this doesn't do much. - void StartHttpRequestHandler(); - void StopHttpRequestHandler(); - - // Whether or not this http request handler is currently running. - bool IsRunning() const - { - return m_bIsHttpRequestHandlerRunning; - } - - /** - * Creates a new thread to execute an HTTP request. - * @param requestParameters The parameters to use for this http request. - * @returns The handle for the http request being sent, or -1 if the request failed. - */ - template <ScriptContext context> int MakeHttpRequest(const HttpRequest& requestParameters); - - /** Registers the HTTP request Squirrel functions for the given script context. */ - template <ScriptContext context> void RegisterSQFuncs(); - -private: - int m_iLastRequestHandle = 0; - std::atomic_bool m_bIsHttpRequestHandlerRunning = false; -}; - -extern HttpRequestHandler* g_httpRequestHandler; diff --git a/NorthstarDLL/scripts/scriptjson.cpp b/NorthstarDLL/scripts/scriptjson.cpp deleted file mode 100644 index 06bda6f4..00000000 --- a/NorthstarDLL/scripts/scriptjson.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "squirrel/squirrel.h" - -#include "rapidjson/error/en.h" -#include "rapidjson/document.h" -#include "rapidjson/writer.h" -#include "rapidjson/stringbuffer.h" - -#ifdef _MSC_VER -#undef GetObject // fuck microsoft developers -#endif - -template <ScriptContext context> void -DecodeJsonArray(HSquirrelVM* sqvm, rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* arr) -{ - g_pSquirrel<context>->newarray(sqvm, 0); - - for (auto& itr : arr->GetArray()) - { - switch (itr.GetType()) - { - case rapidjson::kObjectType: - DecodeJsonTable<context>(sqvm, &itr); - g_pSquirrel<context>->arrayappend(sqvm, -2); - break; - case rapidjson::kArrayType: - DecodeJsonArray<context>(sqvm, &itr); - g_pSquirrel<context>->arrayappend(sqvm, -2); - break; - case rapidjson::kStringType: - g_pSquirrel<context>->pushstring(sqvm, itr.GetString(), -1); - g_pSquirrel<context>->arrayappend(sqvm, -2); - break; - case rapidjson::kTrueType: - case rapidjson::kFalseType: - g_pSquirrel<context>->pushbool(sqvm, itr.GetBool()); - g_pSquirrel<context>->arrayappend(sqvm, -2); - break; - case rapidjson::kNumberType: - if (itr.IsDouble() || itr.IsFloat()) - g_pSquirrel<context>->pushfloat(sqvm, itr.GetFloat()); - else - g_pSquirrel<context>->pushinteger(sqvm, itr.GetInt()); - g_pSquirrel<context>->arrayappend(sqvm, -2); - break; - } - } -} - -template <ScriptContext context> void -DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj) -{ - g_pSquirrel<context>->newtable(sqvm); - - for (auto itr = obj->MemberBegin(); itr != obj->MemberEnd(); itr++) - { - switch (itr->value.GetType()) - { - case rapidjson::kObjectType: - g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); - DecodeJsonTable<context>( - sqvm, (rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>*)&itr->value); - g_pSquirrel<context>->newslot(sqvm, -3, false); - break; - case rapidjson::kArrayType: - g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); - DecodeJsonArray<context>( - sqvm, (rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>*)&itr->value); - g_pSquirrel<context>->newslot(sqvm, -3, false); - break; - case rapidjson::kStringType: - g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); - g_pSquirrel<context>->pushstring(sqvm, itr->value.GetString(), -1); - - g_pSquirrel<context>->newslot(sqvm, -3, false); - break; - case rapidjson::kTrueType: - case rapidjson::kFalseType: - g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); - g_pSquirrel<context>->pushbool(sqvm, itr->value.GetBool()); - g_pSquirrel<context>->newslot(sqvm, -3, false); - break; - case rapidjson::kNumberType: - if (itr->value.IsDouble() || itr->value.IsFloat()) - { - g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); - g_pSquirrel<context>->pushfloat(sqvm, itr->value.GetFloat()); - } - else - { - g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); - g_pSquirrel<context>->pushinteger(sqvm, itr->value.GetInt()); - } - g_pSquirrel<context>->newslot(sqvm, -3, false); - break; - } - } -} - -template <ScriptContext context> void EncodeJSONTable( - SQTable* table, - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj, - rapidjson::MemoryPoolAllocator<SourceAllocator>& allocator) -{ - for (int i = 0; i < table->_numOfNodes; i++) - { - tableNode* node = &table->_nodes[i]; - if (node->key._Type == OT_STRING) - { - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newObj(rapidjson::kObjectType); - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newArray(rapidjson::kArrayType); - - switch (node->val._Type) - { - case OT_STRING: - obj->AddMember( - rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::StringRef(node->val._VAL.asString->_val), allocator); - break; - case OT_INTEGER: - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), node->val._VAL.asInteger, allocator); - break; - case OT_FLOAT: - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), node->val._VAL.asFloat, allocator); - break; - case OT_BOOL: - if (node->val._VAL.asInteger) - { - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), true, allocator); - } - else - { - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), false, allocator); - } - break; - case OT_TABLE: - EncodeJSONTable<context>(node->val._VAL.asTable, &newObj, allocator); - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newObj, allocator); - break; - case OT_ARRAY: - EncodeJSONArray<context>(node->val._VAL.asArray, &newArray, allocator); - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newArray, allocator); - break; - default: - spdlog::warn("SQ_EncodeJSON: squirrel type {} not supported", SQTypeNameFromID(node->val._Type)); - break; - } - } - } -} - -template <ScriptContext context> void EncodeJSONArray( - SQArray* arr, - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj, - rapidjson::MemoryPoolAllocator<SourceAllocator>& allocator) -{ - for (int i = 0; i < arr->_usedSlots; i++) - { - SQObject* node = &arr->_values[i]; - - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newObj(rapidjson::kObjectType); - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newArray(rapidjson::kArrayType); - - switch (node->_Type) - { - case OT_STRING: - obj->PushBack(rapidjson::StringRef(node->_VAL.asString->_val), allocator); - break; - case OT_INTEGER: - obj->PushBack(node->_VAL.asInteger, allocator); - break; - case OT_FLOAT: - obj->PushBack(node->_VAL.asFloat, allocator); - break; - case OT_BOOL: - if (node->_VAL.asInteger) - obj->PushBack(rapidjson::StringRef("true"), allocator); - else - obj->PushBack(rapidjson::StringRef("false"), allocator); - break; - case OT_TABLE: - EncodeJSONTable<context>(node->_VAL.asTable, &newObj, allocator); - obj->PushBack(newObj, allocator); - break; - case OT_ARRAY: - EncodeJSONArray<context>(node->_VAL.asArray, &newArray, allocator); - obj->PushBack(newArray, allocator); - break; - default: - spdlog::info("SQ encode Json type {} not supported", SQTypeNameFromID(node->_Type)); - } - } -} - -ADD_SQFUNC( - "table", - DecodeJSON, - "string json, bool fatalParseErrors = false", - "converts a json string to a squirrel table", - ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - const char* pJson = g_pSquirrel<context>->getstring(sqvm, 1); - const bool bFatalParseErrors = g_pSquirrel<context>->getbool(sqvm, 2); - - rapidjson_document doc; - doc.Parse(pJson); - if (doc.HasParseError()) - { - g_pSquirrel<context>->newtable(sqvm); - - std::string sErrorString = fmt::format( - "Failed parsing json file: encountered parse error \"{}\" at offset {}", - GetParseError_En(doc.GetParseError()), - doc.GetErrorOffset()); - - if (bFatalParseErrors) - { - g_pSquirrel<context>->raiseerror(sqvm, sErrorString.c_str()); - return SQRESULT_ERROR; - } - - spdlog::warn(sErrorString); - return SQRESULT_NOTNULL; - } - - DecodeJsonTable<context>(sqvm, (rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>*)&doc); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC( - "string", - EncodeJSON, - "table data", - "converts a squirrel table to a json string", - ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - rapidjson_document doc; - doc.SetObject(); - - // temp until this is just the func parameter type - HSquirrelVM* vm = (HSquirrelVM*)sqvm; - SQTable* table = vm->_stackOfCurrentFunction[1]._VAL.asTable; - EncodeJSONTable<context>(table, &doc, doc.GetAllocator()); - - rapidjson::StringBuffer buffer; - rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); - doc.Accept(writer); - const char* pJsonString = buffer.GetString(); - - g_pSquirrel<context>->pushstring(sqvm, pJsonString, -1); - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/scripts/scriptjson.h b/NorthstarDLL/scripts/scriptjson.h deleted file mode 100644 index b747106b..00000000 --- a/NorthstarDLL/scripts/scriptjson.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "rapidjson/document.h" -#include "rapidjson/writer.h" -#include "rapidjson/stringbuffer.h" - -template <ScriptContext context> void EncodeJSONTable( - SQTable* table, - rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj, - rapidjson::MemoryPoolAllocator<SourceAllocator>& allocator); - -template <ScriptContext context> void -DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj); diff --git a/NorthstarDLL/scripts/scriptutility.cpp b/NorthstarDLL/scripts/scriptutility.cpp deleted file mode 100644 index 4b92fa02..00000000 --- a/NorthstarDLL/scripts/scriptutility.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "squirrel/squirrel.h" -#include "client/r2client.h" -#include "engine/r2engine.h" - -// asset function StringToAsset( string assetName ) -ADD_SQFUNC( - "asset", - StringToAsset, - "string assetName", - "converts a given string to an asset", - ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - g_pSquirrel<context>->pushasset(sqvm, g_pSquirrel<context>->getstring(sqvm, 1), -1); - return SQRESULT_NOTNULL; -} - -// string function NSGetLocalPlayerUID() -ADD_SQFUNC( - "string", NSGetLocalPlayerUID, "", "Returns the local player's uid.", ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - if (g_pLocalPlayerUserID) - { - g_pSquirrel<context>->pushstring(sqvm, g_pLocalPlayerUserID); - return SQRESULT_NOTNULL; - } - - return SQRESULT_NULL; -} diff --git a/NorthstarDLL/scripts/server/miscserverfixes.cpp b/NorthstarDLL/scripts/server/miscserverfixes.cpp deleted file mode 100644 index 48c2c111..00000000 --- a/NorthstarDLL/scripts/server/miscserverfixes.cpp +++ /dev/null @@ -1,6 +0,0 @@ - -ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module)) -{ - // nop out call to VGUI shutdown since it crashes the game when quitting from the console - module.Offset(0x154A96).NOP(5); -} diff --git a/NorthstarDLL/scripts/server/miscserverscript.cpp b/NorthstarDLL/scripts/server/miscserverscript.cpp deleted file mode 100644 index ed6e4800..00000000 --- a/NorthstarDLL/scripts/server/miscserverscript.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "squirrel/squirrel.h" -#include "masterserver/masterserver.h" -#include "server/auth/serverauthentication.h" -#include "dedicated/dedicated.h" -#include "client/r2client.h" -#include "server/r2server.h" - -#include <filesystem> - -ADD_SQFUNC("void", NSEarlyWritePlayerPersistenceForLeave, "entity player", "", ScriptContext::SERVER) -{ - const CBasePlayer* pPlayer = g_pSquirrel<context>->template getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player"); - - g_pSquirrel<context>->pushbool(sqvm, false); - return SQRESULT_NOTNULL; - } - - CBaseClient* pClient = &g_pClientArray[pPlayer->m_nPlayerIndex - 1]; - if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end()) - { - g_pSquirrel<context>->pushbool(sqvm, false); - return SQRESULT_NOTNULL; - } - - g_pServerAuthentication->m_PlayerAuthenticationData[pClient].needPersistenceWriteOnLeave = false; - g_pServerAuthentication->WritePersistentData(pClient); - return SQRESULT_NULL; -} - -ADD_SQFUNC("bool", NSIsWritingPlayerPersistence, "", "", ScriptContext::SERVER) -{ - g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("bool", NSIsPlayerLocalPlayer, "entity player", "", ScriptContext::SERVER) -{ - const CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->template getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - spdlog::warn("NSIsPlayerLocalPlayer got null player"); - - g_pSquirrel<context>->pushbool(sqvm, false); - return SQRESULT_NOTNULL; - } - - CBaseClient* pClient = &g_pClientArray[pPlayer->m_nPlayerIndex - 1]; - g_pSquirrel<context>->pushbool(sqvm, !strcmp(g_pLocalPlayerUserID, pClient->m_UID)); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC("bool", NSIsDedicated, "", "", ScriptContext::SERVER) -{ - g_pSquirrel<context>->pushbool(sqvm, IsDedicatedServer()); - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC( - "bool", - NSDisconnectPlayer, - "entity player, string reason", - "Disconnects the player from the server with the given reason", - ScriptContext::SERVER) -{ - const CBasePlayer* pPlayer = g_pSquirrel<context>->template getentity<CBasePlayer>(sqvm, 1); - const char* reason = g_pSquirrel<context>->getstring(sqvm, 2); - - if (!pPlayer) - { - spdlog::warn("Attempted to call NSDisconnectPlayer() with null player."); - - g_pSquirrel<context>->pushbool(sqvm, false); - return SQRESULT_NOTNULL; - } - - // Shouldn't happen but I like sanity checks. - CBaseClient* pClient = &g_pClientArray[pPlayer->m_nPlayerIndex - 1]; - if (!pClient) - { - spdlog::warn("NSDisconnectPlayer(): player entity has null CBaseClient!"); - - g_pSquirrel<context>->pushbool(sqvm, false); - return SQRESULT_NOTNULL; - } - - if (reason) - { - CBaseClient__Disconnect(pClient, 1, reason); - } - else - { - CBaseClient__Disconnect(pClient, 1, "Disconnected by the server."); - } - - g_pSquirrel<context>->pushbool(sqvm, true); - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/scripts/server/scriptuserinfo.cpp b/NorthstarDLL/scripts/server/scriptuserinfo.cpp deleted file mode 100644 index c53a9d22..00000000 --- a/NorthstarDLL/scripts/server/scriptuserinfo.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "squirrel/squirrel.h" -#include "engine/r2engine.h" -#include "server/r2server.h" - -// clang-format off -ADD_SQFUNC("string", GetUserInfoKVString_Internal, "entity player, string key, string defaultValue = \"\"", - "Gets the string value of a given player's userinfo convar by name", ScriptContext::SERVER) -// clang-format on -{ - const CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->template getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); - return SQRESULT_ERROR; - } - - const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); - const char* pDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 3); - - const char* pResult = g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetString(pKey, pDefaultValue); - g_pSquirrel<ScriptContext::SERVER>->pushstring(sqvm, pResult); - return SQRESULT_NOTNULL; -} - -// clang-format off -ADD_SQFUNC("asset", GetUserInfoKVAsset_Internal, "entity player, string key, asset defaultValue = $\"\"", - "Gets the asset value of a given player's userinfo convar by name", ScriptContext::SERVER) -// clang-format on -{ - const CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->template getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); - return SQRESULT_ERROR; - } - - const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); - const char* pDefaultValue; - g_pSquirrel<ScriptContext::SERVER>->getasset(sqvm, 3, &pDefaultValue); - - const char* pResult = g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetString(pKey, pDefaultValue); - g_pSquirrel<ScriptContext::SERVER>->pushasset(sqvm, pResult); - return SQRESULT_NOTNULL; -} - -// clang-format off -ADD_SQFUNC("int", GetUserInfoKVInt_Internal, "entity player, string key, int defaultValue = 0", - "Gets the int value of a given player's userinfo convar by name", ScriptContext::SERVER) -// clang-format on -{ - const CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->template getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); - return SQRESULT_ERROR; - } - - const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); - const int iDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getinteger(sqvm, 3); - - const int iResult = g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetInt(pKey, iDefaultValue); - g_pSquirrel<ScriptContext::SERVER>->pushinteger(sqvm, iResult); - return SQRESULT_NOTNULL; -} - -// clang-format off -ADD_SQFUNC("float", GetUserInfoKVFloat_Internal, "entity player, string key, float defaultValue = 0", - "Gets the float value of a given player's userinfo convar by name", ScriptContext::SERVER) -// clang-format on -{ - const CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); - return SQRESULT_ERROR; - } - - const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); - const float flDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getfloat(sqvm, 3); - - const float flResult = g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetFloat(pKey, flDefaultValue); - g_pSquirrel<ScriptContext::SERVER>->pushfloat(sqvm, flResult); - return SQRESULT_NOTNULL; -} - -// clang-format off -ADD_SQFUNC("bool", GetUserInfoKVBool_Internal, "entity player, string key, bool defaultValue = false", - "Gets the bool value of a given player's userinfo convar by name", ScriptContext::SERVER) -// clang-format on -{ - const CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<CBasePlayer>(sqvm, 1); - if (!pPlayer) - { - g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); - return SQRESULT_ERROR; - } - - const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); - const bool bDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getbool(sqvm, 3); - - const bool bResult = g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetInt(pKey, bDefaultValue); - g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, bResult); - return SQRESULT_NOTNULL; -} diff --git a/NorthstarDLL/server/alltalk.cpp b/NorthstarDLL/server/alltalk.cpp deleted file mode 100644 index 74119309..00000000 --- a/NorthstarDLL/server/alltalk.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "core/convar/convar.h" -#include "engine/r2engine.h" - -size_t __fastcall ShouldAllowAlltalk() -{ - // this needs to return a 64 bit integer where 0 = true and 1 = false - static ConVar* Cvar_sv_alltalk = g_pCVar->FindVar("sv_alltalk"); - if (Cvar_sv_alltalk->GetBool()) - return 0; - - // lobby should default to alltalk, otherwise don't allow it - return strcmp(g_pGlobals->m_pMapName, "mp_lobby"); -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerAllTalk, ConVar, (CModule module)) -{ - // replace strcmp function called in CClient::ProcessVoiceData with our own code that calls ShouldAllowAllTalk - CMemoryAddress base = module.Offset(0x1085FA); - - base.Patch("48 B8"); // mov rax, 64 bit int - // (uint8_t*)&ShouldAllowAlltalk doesn't work for some reason? need to make it a uint64 first - uint64_t pShouldAllowAllTalk = reinterpret_cast<uint64_t>(ShouldAllowAlltalk); - base.Offset(0x2).Patch((uint8_t*)&pShouldAllowAllTalk, 8); - base.Offset(0xA).Patch("FF D0"); // call rax - - // nop until compare (test eax, eax) - base.Offset(0xC).NOP(0x7); -} diff --git a/NorthstarDLL/server/auth/bansystem.cpp b/NorthstarDLL/server/auth/bansystem.cpp deleted file mode 100644 index a45cde93..00000000 --- a/NorthstarDLL/server/auth/bansystem.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#include "bansystem.h" -#include "serverauthentication.h" -#include "core/convar/concommand.h" -#include "server/r2server.h" -#include "engine/r2engine.h" -#include "client/r2client.h" -#include "config/profile.h" - -#include <filesystem> - -const char* BANLIST_PATH_SUFFIX = "/banlist.txt"; -const char BANLIST_COMMENT_CHAR = '#'; - -ServerBanSystem* g_pBanSystem; - -void ServerBanSystem::OpenBanlist() -{ - std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt"); - - if (!banlistStream.fail()) - { - std::string line; - while (std::getline(banlistStream, line)) - { - // ignore line if first char is # or line is empty - if (line == "" || line.front() == BANLIST_COMMENT_CHAR) - continue; - - // remove tabs which shouldnt be there but maybe someone did the funny - line.erase(std::remove(line.begin(), line.end(), '\t'), line.end()); - // remove spaces to allow for spaces before uids - line.erase(std::remove(line.begin(), line.end(), ' '), line.end()); - - // check if line is empty to allow for newlines in the file - if (line == "") - continue; - - // for inline comments like: 123123123 #banned for unfunny - std::string uid = line.substr(0, line.find(BANLIST_COMMENT_CHAR)); - - m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10)); - } - - banlistStream.close(); - } - - // open write stream for banlist // dont do this to allow for all time access - // m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app); -} - -void ServerBanSystem::ReloadBanlist() -{ - std::ifstream fsBanlist(GetNorthstarPrefix() + "/banlist.txt"); - - if (!fsBanlist.fail()) - { - std::string line; - // since we wanna use this as the reload func we need to clear the list - m_vBannedUids.clear(); - while (std::getline(fsBanlist, line)) - m_vBannedUids.push_back(strtoull(line.c_str(), nullptr, 10)); - - fsBanlist.close(); - } -} - -void ServerBanSystem::ClearBanlist() -{ - m_vBannedUids.clear(); - - // reopen the file, don't provide std::ofstream::app so it clears on open - m_sBanlistStream.close(); - m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary); - m_sBanlistStream.close(); -} - -void ServerBanSystem::BanUID(uint64_t uid) -{ - // checking if last char is \n to make sure uids arent getting fucked - std::ifstream fsBanlist(GetNorthstarPrefix() + "/banlist.txt"); - std::string content((std::istreambuf_iterator<char>(fsBanlist)), (std::istreambuf_iterator<char>())); - fsBanlist.close(); - - m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app); - if (content.back() != '\n') - m_sBanlistStream << std::endl; - - m_vBannedUids.push_back(uid); - m_sBanlistStream << std::to_string(uid) << std::endl; - m_sBanlistStream.close(); - spdlog::info("{} was banned", uid); -} - -void ServerBanSystem::UnbanUID(uint64_t uid) -{ - auto findResult = std::find(m_vBannedUids.begin(), m_vBannedUids.end(), uid); - if (findResult == m_vBannedUids.end()) - return; - - m_vBannedUids.erase(findResult); - - std::vector<std::string> banlistText; - std::ifstream fs_readBanlist(GetNorthstarPrefix() + "/banlist.txt"); - - if (!fs_readBanlist.fail()) - { - std::string line; - while (std::getline(fs_readBanlist, line)) - { - // support for comments and newlines added in https://github.com/R2Northstar/NorthstarLauncher/pull/227 - - std::string modLine = line; // copy the line into a free var that we can fuck with, line will be the original - - // remove tabs which shouldnt be there but maybe someone did the funny - modLine.erase(std::remove(modLine.begin(), modLine.end(), '\t'), modLine.end()); - // remove spaces to allow for spaces before uids - modLine.erase(std::remove(modLine.begin(), modLine.end(), ' '), modLine.end()); - - // ignore line if first char is # or empty line, just add it - if (line.front() == BANLIST_COMMENT_CHAR || modLine == "") - { - banlistText.push_back(line); - continue; - } - - // for inline comments like: 123123123 #banned for unfunny - std::string lineUid = line.substr(0, line.find(BANLIST_COMMENT_CHAR)); - // have to erase spaces or else inline comments will fuck up the uid finding - lineUid.erase(std::remove(lineUid.begin(), lineUid.end(), '\t'), lineUid.end()); - lineUid.erase(std::remove(lineUid.begin(), lineUid.end(), ' '), lineUid.end()); - - // if the uid in the line is the uid we wanna unban - if (std::to_string(uid) == lineUid) - { - // comment the uid out - line.insert(0, "# "); - - // add a comment with unban date - // not necessary but i feel like this makes it better - std::time_t t = std::time(0); - std::tm* now = std::localtime(&t); - - std::ostringstream unbanComment; - - //{y}/{m}/{d} {h}:{m} - unbanComment << " # unban date: "; - unbanComment << now->tm_year + 1900 << "-"; // this lib is so fucking awful - unbanComment << std::setw(2) << std::setfill('0') << now->tm_mon + 1 << "-"; - unbanComment << std::setw(2) << std::setfill('0') << now->tm_mday << " "; - unbanComment << std::setw(2) << std::setfill('0') << now->tm_hour << ":"; - unbanComment << std::setw(2) << std::setfill('0') << now->tm_min; - - line.append(unbanComment.str()); - } - - banlistText.push_back(line); - } - - fs_readBanlist.close(); - } - - // open write stream for banlist // without append so we clear the file - if (m_sBanlistStream.is_open()) - m_sBanlistStream.close(); - m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary); - - for (std::string updatedLine : banlistText) - m_sBanlistStream << updatedLine << std::endl; - - m_sBanlistStream.close(); - spdlog::info("{} was unbanned", uid); -} - -bool ServerBanSystem::IsUIDAllowed(uint64_t uid) -{ - uint64_t localPlayerUserID = strtoull(g_pLocalPlayerUserID, nullptr, 10); - if (localPlayerUserID == uid) - return true; - - ReloadBanlist(); // Reload to have up to date list on join - return std::find(m_vBannedUids.begin(), m_vBannedUids.end(), uid) == m_vBannedUids.end(); -} - -void ConCommand_ban(const CCommand& args) -{ - if (args.ArgC() < 2) - return; - - for (int i = 0; i < g_pGlobals->m_nMaxClients; i++) - { - CBaseClient* player = &g_pClientArray[i]; - - if (!strcmp(player->m_Name, args.Arg(1)) || !strcmp(player->m_UID, args.Arg(1))) - { - g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10)); - CBaseClient__Disconnect(player, 1, "Banned from server"); - break; - } - } -} - -void ConCommand_unban(const CCommand& args) -{ - if (args.ArgC() < 2) - return; - - // assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything - g_pBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10)); -} - -void ConCommand_clearbanlist(const CCommand& args) -{ - g_pBanSystem->ClearBanlist(); -} - -ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (CModule module)) -{ - g_pBanSystem = new ServerBanSystem; - g_pBanSystem->OpenBanlist(); - - RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL); - RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL); - RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_GAMEDLL); -} diff --git a/NorthstarDLL/server/auth/bansystem.h b/NorthstarDLL/server/auth/bansystem.h deleted file mode 100644 index d6ac5a4f..00000000 --- a/NorthstarDLL/server/auth/bansystem.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include <fstream> - -class ServerBanSystem -{ -private: - std::ofstream m_sBanlistStream; - std::vector<uint64_t> m_vBannedUids; - -public: - void OpenBanlist(); - void ReloadBanlist(); - void ClearBanlist(); - void BanUID(uint64_t uid); - void UnbanUID(uint64_t uid); - bool IsUIDAllowed(uint64_t uid); -}; - -extern ServerBanSystem* g_pBanSystem; diff --git a/NorthstarDLL/server/auth/serverauthentication.cpp b/NorthstarDLL/server/auth/serverauthentication.cpp deleted file mode 100644 index 0d46426f..00000000 --- a/NorthstarDLL/server/auth/serverauthentication.cpp +++ /dev/null @@ -1,380 +0,0 @@ -#include "serverauthentication.h" -#include "shared/exploit_fixes/ns_limits.h" -#include "core/convar/cvar.h" -#include "core/convar/convar.h" -#include "masterserver/masterserver.h" -#include "server/serverpresence.h" -#include "engine/hoststate.h" -#include "bansystem.h" -#include "core/convar/concommand.h" -#include "dedicated/dedicated.h" -#include "config/profile.h" -#include "core/tier0.h" -#include "engine/r2engine.h" -#include "client/r2client.h" -#include "server/r2server.h" - -#include <fstream> -#include <filesystem> -#include <string> -#include <thread> - -AUTOHOOK_INIT() - -// global vars -ServerAuthenticationManager* g_pServerAuthentication; -CBaseServer__RejectConnectionType CBaseServer__RejectConnection; - -void ServerAuthenticationManager::AddRemotePlayer(std::string token, uint64_t uid, std::string username, std::string pdata) -{ - std::string uidS = std::to_string(uid); - - RemoteAuthData newAuthData {}; - strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), uidS.c_str(), uidS.length()); - strncpy_s(newAuthData.username, sizeof(newAuthData.username), username.c_str(), username.length()); - newAuthData.pdata = new char[pdata.length()]; - newAuthData.pdataSize = pdata.length(); - memcpy(newAuthData.pdata, pdata.c_str(), newAuthData.pdataSize); - - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - m_RemoteAuthenticationData[token] = newAuthData; -} - -void ServerAuthenticationManager::AddPlayer(CBaseClient* pPlayer, const char* pToken) -{ - PlayerAuthenticationData additionalData; - - auto remoteAuthData = m_RemoteAuthenticationData.find(pToken); - if (remoteAuthData != m_RemoteAuthenticationData.end()) - additionalData.pdataSize = remoteAuthData->second.pdataSize; - else - additionalData.pdataSize = PERSISTENCE_MAX_SIZE; - - additionalData.usingLocalPdata = pPlayer->m_iPersistenceReady == ePersistenceReady::READY_INSECURE; - - m_PlayerAuthenticationData.insert(std::make_pair(pPlayer, additionalData)); -} - -void ServerAuthenticationManager::RemovePlayer(CBaseClient* pPlayer) -{ - if (m_PlayerAuthenticationData.count(pPlayer)) - m_PlayerAuthenticationData.erase(pPlayer); -} - -bool ServerAuthenticationManager::VerifyPlayerName(const char* pAuthToken, const char* pName, char pOutVerifiedName[64]) -{ - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - - // always use name from masterserver if available - // use of strncpy_s here should verify that this is always nullterminated within valid buffer size - auto authData = m_RemoteAuthenticationData.find(pAuthToken); - if (authData != m_RemoteAuthenticationData.end() && *authData->second.username) - strncpy_s(pOutVerifiedName, 64, authData->second.username, 63); - else - strncpy_s(pOutVerifiedName, 64, pName, 63); - - // now, check that whatever name we have is actually valid - // first, make sure it's >1 char - if (!*pOutVerifiedName) - return false; - - // next, make sure it's within a valid range of ascii characters - for (int i = 0; pOutVerifiedName[i]; i++) - { - if (pOutVerifiedName[i] < 32 || pOutVerifiedName[i] > 126) - return false; - } - - return true; -} - -bool ServerAuthenticationManager::IsDuplicateAccount(CBaseClient* pPlayer, const char* pPlayerUid) -{ - if (m_bAllowDuplicateAccounts) - return false; - - bool bHasUidPlayer = false; - for (int i = 0; i < g_pGlobals->m_nMaxClients; i++) - if (&g_pClientArray[i] != pPlayer && !strcmp(pPlayerUid, g_pClientArray[i].m_UID)) - return true; - - return false; -} - -bool ServerAuthenticationManager::CheckAuthentication(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken) -{ - std::string sUid = std::to_string(iUid); - - // check whether this player's authentication is valid but don't actually write anything to the player, we'll do that later - // if we don't need auth this is valid - if (Cvar_ns_auth_allow_insecure->GetBool()) - return true; - - // local server that doesn't need auth (probably sp) and local player - if (m_bStartingLocalSPGame && !strcmp(sUid.c_str(), g_pLocalPlayerUserID)) - return true; - - // don't allow duplicate accounts - if (IsDuplicateAccount(pPlayer, sUid.c_str())) - return false; - - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - auto authData = m_RemoteAuthenticationData.find(pAuthToken); - if (authData != m_RemoteAuthenticationData.end() && !strcmp(sUid.c_str(), authData->second.uid)) - return true; - - return false; -} - -void ServerAuthenticationManager::AuthenticatePlayer(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken) -{ - // for bot players, generate a new uid - if (pPlayer->m_bFakePlayer) - iUid = 0; // is this a good way of doing things :clueless: - - std::string sUid = std::to_string(iUid); - - // copy uuid - strcpy(pPlayer->m_UID, sUid.c_str()); - - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - auto authData = m_RemoteAuthenticationData.find(pAuthToken); - if (authData != m_RemoteAuthenticationData.end()) - { - // if we're resetting let script handle the reset with InitPersistentData() on connect - if (!m_bForceResetLocalPlayerPersistence || strcmp(sUid.c_str(), g_pLocalPlayerUserID)) - { - // copy pdata into buffer - memcpy(pPlayer->m_PersistenceBuffer, authData->second.pdata, authData->second.pdataSize); - } - - // set persistent data as ready - pPlayer->m_iPersistenceReady = ePersistenceReady::READY_REMOTE; - } - // we probably allow insecure at this point, but make sure not to write anyway if not insecure - else if (Cvar_ns_auth_allow_insecure->GetBool() || pPlayer->m_bFakePlayer) - { - // set persistent data as ready - // note: actual placeholder persistent data is populated in script with InitPersistentData() - pPlayer->m_iPersistenceReady = ePersistenceReady::READY_INSECURE; - } -} - -bool ServerAuthenticationManager::RemovePlayerAuthData(CBaseClient* pPlayer) -{ - if (!Cvar_ns_erase_auth_info->GetBool()) // keep auth data forever - return false; - - // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect - if (m_bNeedLocalAuthForNewgame && !strcmp(pPlayer->m_UID, g_pLocalPlayerUserID)) - return false; - - // we don't have our auth token at this point, so lookup authdata by uid - for (auto& auth : m_RemoteAuthenticationData) - { - if (!strcmp(pPlayer->m_UID, auth.second.uid)) - { - // pretty sure this is fine, since we don't iterate after the erase - // i think if we iterated after it'd be undefined behaviour tho - std::lock_guard<std::mutex> guard(m_AuthDataMutex); - - delete[] auth.second.pdata; - m_RemoteAuthenticationData.erase(auth.first); - return true; - } - } - - return false; -} - -void ServerAuthenticationManager::WritePersistentData(CBaseClient* pPlayer) -{ - if (pPlayer->m_iPersistenceReady == ePersistenceReady::READY_REMOTE) - { - g_pMasterServerManager->WritePlayerPersistentData( - pPlayer->m_UID, (const char*)pPlayer->m_PersistenceBuffer, m_PlayerAuthenticationData[pPlayer].pdataSize); - } - else if (Cvar_ns_auth_allow_insecure_write->GetBool()) - { - // todo: write pdata to disk here - } -} - -// auth hooks - -// store these in vars so we can use them in CBaseClient::Connect -// this is fine because ptrs won't decay by the time we use this, just don't use it outside of calls from cbaseclient::connectclient -char* pNextPlayerToken; -uint64_t iNextPlayerUid; - -// clang-format off -AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430, -void*,, ( - void* self, - void* addr, - void* a3, - uint32_t a4, - uint32_t a5, - int32_t a6, - void* a7, - char* playerName, - char* serverFilter, - void* a10, - char a11, - void* a12, - char a13, - char a14, - int64_t uid, - uint32_t a16, - uint32_t a17)) -// clang-format on -{ - // auth tokens are sent with serverfilter, can't be accessed from player struct to my knowledge, so have to do this here - pNextPlayerToken = serverFilter; - iNextPlayerUid = uid; - - return CBaseServer__ConnectClient(self, addr, a3, a4, a5, a6, a7, playerName, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17); -} - -ConVar* Cvar_ns_allowuserclantags; - -// clang-format off -AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, -bool,, (CBaseClient* self, char* pName, void* pNetChannel, char bFakePlayer, void* a5, char pDisconnectReason[256], void* a7)) -// clang-format on -{ - const char* pAuthenticationFailure = nullptr; - char pVerifiedName[64]; - - if (!bFakePlayer) - { - if (!g_pServerAuthentication->VerifyPlayerName(pNextPlayerToken, pName, pVerifiedName)) - pAuthenticationFailure = "Invalid Name."; - else if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) - pAuthenticationFailure = "Banned From server."; - else if (!g_pServerAuthentication->CheckAuthentication(self, iNextPlayerUid, pNextPlayerToken)) - pAuthenticationFailure = "Authentication Failed."; - } - else // need to copy name for bots still - strncpy_s(pVerifiedName, pName, 63); - - if (pAuthenticationFailure) - { - spdlog::info("{}'s (uid {}) connection was rejected: \"{}\"", pName, iNextPlayerUid, pAuthenticationFailure); - - strncpy_s(pDisconnectReason, 256, pAuthenticationFailure, 255); - return false; - } - - // try to actually connect the player - if (!CBaseClient__Connect(self, pVerifiedName, pNetChannel, bFakePlayer, a5, pDisconnectReason, a7)) - return false; - - // we already know this player's authentication data is legit, actually write it to them now - g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken); - - g_pServerAuthentication->AddPlayer(self, pNextPlayerToken); - g_pServerLimits->AddPlayer(self); - - return true; -} - -// clang-format off -AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, -void,, (CBaseClient* self)) -// clang-format on -{ - // if we're authed, write our persistent data - // RemovePlayerAuthData returns true if it removed successfully, i.e. on first call only, and we only want to write on >= second call - // (since this func is called on map loads) - if (self->m_iPersistenceReady >= ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) - { - g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = false; - g_pServerAuthentication->WritePersistentData(self); - g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - } - - CBaseClient__ActivatePlayer(self); -} - -// clang-format off -AUTOHOOK(_CBaseClient__Disconnect, engine.dll + 0x1012C0, -void,, (CBaseClient* self, uint32_t unknownButAlways1, const char* pReason, ...)) -// clang-format on -{ - // have to manually format message because can't pass varargs to original func - char buf[1024]; - - va_list va; - va_start(va, pReason); - vsprintf(buf, pReason, va); - va_end(va); - - // this reason is used while connecting to a local server, hacky, but just ignore it - if (strcmp(pReason, "Connection closing")) - { - spdlog::info("Player {} disconnected: \"{}\"", self->m_Name, buf); - - // dcing, write persistent data - if (g_pServerAuthentication->m_PlayerAuthenticationData[self].needPersistenceWriteOnLeave) - g_pServerAuthentication->WritePersistentData(self); - - memset(self->m_PersistenceBuffer, 0, g_pServerAuthentication->m_PlayerAuthenticationData[self].pdataSize); - g_pServerAuthentication->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case - - g_pServerAuthentication->RemovePlayer(self); - g_pServerLimits->RemovePlayer(self); - } - - g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - - _CBaseClient__Disconnect(self, unknownButAlways1, buf); -} - -void ConCommand_ns_resetpersistence(const CCommand& args) -{ - if (*g_pServerState == server_state_t::ss_active) - { - spdlog::error("ns_resetpersistence must be entered from the main menu"); - return; - } - - spdlog::info("resetting persistence on next lobby load..."); - g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = true; -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, (ConCommand, ConVar), (CModule module)) -{ - AUTOHOOK_DISPATCH() - - g_pServerAuthentication = new ServerAuthenticationManager; - - g_pServerAuthentication->Cvar_ns_erase_auth_info = - new ConVar("ns_erase_auth_info", "1", FCVAR_GAMEDLL, "Whether auth info should be erased from this server on disconnect or crash"); - g_pServerAuthentication->Cvar_ns_auth_allow_insecure = - new ConVar("ns_auth_allow_insecure", "0", FCVAR_GAMEDLL, "Whether this server will allow unauthenicated players to connect"); - g_pServerAuthentication->Cvar_ns_auth_allow_insecure_write = new ConVar( - "ns_auth_allow_insecure_write", - "0", - FCVAR_GAMEDLL, - "Whether the pdata of unauthenticated clients will be written to disk when changed"); - - RegisterConCommand( - "ns_resetpersistence", ConCommand_ns_resetpersistence, "resets your pdata when you next enter the lobby", FCVAR_NONE); - - // patch to disable kicking based on incorrect serverfilter in connectclient, since we repurpose it for use as an auth token - module.Offset(0x114655).Patch("EB"); - - // patch to disable fairfight marking players as cheaters and kicking them - module.Offset(0x101012).Patch("E9 90 00"); - - CBaseServer__RejectConnection = module.Offset(0x1182E0).RCast<CBaseServer__RejectConnectionType>(); - - if (CommandLine()->CheckParm("-allowdupeaccounts")) - { - // patch to allow same of multiple account - module.Offset(0x114510).Patch("EB"); - - g_pServerAuthentication->m_bAllowDuplicateAccounts = true; - } -} diff --git a/NorthstarDLL/server/auth/serverauthentication.h b/NorthstarDLL/server/auth/serverauthentication.h deleted file mode 100644 index 996d20e1..00000000 --- a/NorthstarDLL/server/auth/serverauthentication.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include "core/convar/convar.h" -#include "engine/r2engine.h" -#include <unordered_map> -#include <string> - -struct RemoteAuthData -{ - char uid[33]; - char username[64]; - - // pdata - char* pdata; - size_t pdataSize; -}; - -struct PlayerAuthenticationData -{ - bool usingLocalPdata; - size_t pdataSize; - bool needPersistenceWriteOnLeave = true; -}; - -typedef int64_t (*CBaseServer__RejectConnectionType)(void* a1, unsigned int a2, void* a3, const char* a4, ...); -extern CBaseServer__RejectConnectionType CBaseServer__RejectConnection; - -class ServerAuthenticationManager -{ -public: - ConVar* Cvar_ns_erase_auth_info; - ConVar* Cvar_ns_auth_allow_insecure; - ConVar* Cvar_ns_auth_allow_insecure_write; - - std::mutex m_AuthDataMutex; - std::unordered_map<std::string, RemoteAuthData> m_RemoteAuthenticationData; - std::unordered_map<CBaseClient*, PlayerAuthenticationData> m_PlayerAuthenticationData; - - bool m_bAllowDuplicateAccounts = false; - bool m_bNeedLocalAuthForNewgame = false; - bool m_bForceResetLocalPlayerPersistence = false; - bool m_bStartingLocalSPGame = false; - -public: - void AddRemotePlayer(std::string token, uint64_t uid, std::string username, std::string pdata); - - void AddPlayer(CBaseClient* pPlayer, const char* pAuthToken); - void RemovePlayer(CBaseClient* pPlayer); - - bool VerifyPlayerName(const char* pAuthToken, const char* pName, char pOutVerifiedName[64]); - bool IsDuplicateAccount(CBaseClient* pPlayer, const char* pUid); - bool CheckAuthentication(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken); - - void AuthenticatePlayer(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken); - bool RemovePlayerAuthData(CBaseClient* pPlayer); - void WritePersistentData(CBaseClient* pPlayer); -}; - -extern ServerAuthenticationManager* g_pServerAuthentication; diff --git a/NorthstarDLL/server/buildainfile.cpp b/NorthstarDLL/server/buildainfile.cpp deleted file mode 100644 index a7f59961..00000000 --- a/NorthstarDLL/server/buildainfile.cpp +++ /dev/null @@ -1,395 +0,0 @@ -#include "core/convar/convar.h" -#include "engine/hoststate.h" -#include "engine/r2engine.h" - -#include <fstream> -#include <filesystem> - -AUTOHOOK_INIT() - -const int AINET_VERSION_NUMBER = 57; -const int AINET_SCRIPT_VERSION_NUMBER = 21; -const int PLACEHOLDER_CRC = 0; -const int MAX_HULLS = 5; - -#pragma pack(push, 1) -struct CAI_NodeLink -{ - short srcId; - short destId; - bool hulls[MAX_HULLS]; - char unk0; - char unk1; // maps => unk0 on disk - char unk2[5]; - int64_t flags; -}; -#pragma pack(pop) - -#pragma pack(push, 1) -struct CAI_NodeLinkDisk -{ - short srcId; - short destId; - char unk0; - bool hulls[MAX_HULLS]; -}; -#pragma pack(pop) - -#pragma pack(push, 1) -struct CAI_Node -{ - int index; // not present on disk - float x; - float y; - float z; - float hulls[MAX_HULLS]; - float yaw; - - int unk0; // always 2 in buildainfile, maps directly to unk0 in disk struct - int unk1; // maps directly to unk1 in disk struct - int unk2[MAX_HULLS]; // maps directly to unk2 in disk struct, despite being ints rather than shorts - - // view server.dll+393672 for context and death wish - char unk3[MAX_HULLS]; // hell on earth, should map to unk3 on disk - char pad[3]; // aligns next bytes - float unk4[MAX_HULLS]; // i have no fucking clue, calculated using some kind of demon hell function float magic - - CAI_NodeLink** links; - char unk5[16]; - int linkcount; - int unk11; // bad name lmao - short unk6; // should match up to unk4 on disk - char unk7[16]; // padding until next bit - short unk8; // should match up to unk5 on disk - char unk9[8]; // padding until next bit - char unk10[8]; // should match up to unk6 on disk -}; -#pragma pack(pop) - -// the way CAI_Nodes are represented in on-disk ain files -#pragma pack(push, 1) -struct CAI_NodeDisk -{ - float x; - float y; - float z; - float yaw; - float hulls[MAX_HULLS]; - - char unk0; - int unk1; - short unk2[MAX_HULLS]; - char unk3[MAX_HULLS]; - short unk4; - short unk5; - char unk6[8]; -}; // total size of 68 bytes -#pragma pack(pop) - -#pragma pack(push, 1) -struct UnkNodeStruct0 -{ - int index; - char unk0; - char unk1; // maps to unk1 on disk - char pad0[2]; // padding to +8 - - float x; - float y; - float z; - - char pad5[4]; - int* unk2; // maps to unk5 on disk; - char pad1[16]; // pad to +48 - int unkcount0; // maps to unkcount0 on disk - - char pad2[4]; // pad to +56 - int* unk3; - char pad3[16]; // pad to +80 - int unkcount1; - - char pad4[132]; - char unk5; -}; -#pragma pack(pop) - -int* pUnkStruct0Count; -UnkNodeStruct0*** pppUnkNodeStruct0s; - -#pragma pack(push, 1) -struct UnkLinkStruct1 -{ - short unk0; - short unk1; - int unk2; - char unk3; - char unk4; - char unk5; -}; -#pragma pack(pop) - -int* pUnkLinkStruct1Count; -UnkLinkStruct1*** pppUnkStruct1s; - -#pragma pack(push, 1) -struct CAI_ScriptNode -{ - float x; - float y; - float z; - uint64_t scriptdata; -}; -#pragma pack(pop) - -#pragma pack(push, 1) -struct CAI_Network -{ - // +0 - char unk0[8]; - // +8 - int linkcount; // this is uninitialised and never set on ain build, fun! - // +12 - char unk1[124]; - // +136 - int zonecount; - // +140 - char unk2[16]; - // +156 - int unk5; // unk8 on disk - // +160 - char unk6[4]; - // +164 - int hintcount; - // +168 - short hints[2000]; // these probably aren't actually hints, but there's 1 of them per hint so idk - // +4168 - int scriptnodecount; - // +4172 - CAI_ScriptNode scriptnodes[4000]; - // +84172 - int nodecount; - // +84176 - CAI_Node** nodes; -}; -#pragma pack(pop) - -ConVar* Cvar_ns_ai_dumpAINfileFromLoad; - -void DumpAINInfo(CAI_Network* aiNetwork) -{ - fs::path writePath(fmt::format("{}/maps/graphs", g_pModName)); - writePath /= g_pGlobals->m_pMapName; - writePath += ".ain"; - - // dump from memory - spdlog::info("writing ain file {}", writePath.string()); - spdlog::info(""); - spdlog::info(""); - spdlog::info(""); - spdlog::info(""); - spdlog::info(""); - - std::ofstream writeStream(writePath, std::ofstream::binary); - spdlog::info("writing ainet version: {}", AINET_VERSION_NUMBER); - writeStream.write((char*)&AINET_VERSION_NUMBER, sizeof(int)); - - int mapVersion = g_pGlobals->m_nMapVersion; - spdlog::info("writing map version: {}", mapVersion); - writeStream.write((char*)&mapVersion, sizeof(int)); - spdlog::info("writing placeholder crc: {}", PLACEHOLDER_CRC); - writeStream.write((char*)&PLACEHOLDER_CRC, sizeof(int)); - - int calculatedLinkcount = 0; - - // path nodes - spdlog::info("writing nodecount: {}", aiNetwork->nodecount); - writeStream.write((char*)&aiNetwork->nodecount, sizeof(int)); - - for (int i = 0; i < aiNetwork->nodecount; i++) - { - // construct on-disk node struct - CAI_NodeDisk diskNode; - diskNode.x = aiNetwork->nodes[i]->x; - diskNode.y = aiNetwork->nodes[i]->y; - diskNode.z = aiNetwork->nodes[i]->z; - diskNode.yaw = aiNetwork->nodes[i]->yaw; - memcpy(diskNode.hulls, aiNetwork->nodes[i]->hulls, sizeof(diskNode.hulls)); - diskNode.unk0 = (char)aiNetwork->nodes[i]->unk0; - diskNode.unk1 = aiNetwork->nodes[i]->unk1; - - for (int j = 0; j < MAX_HULLS; j++) - { - diskNode.unk2[j] = (short)aiNetwork->nodes[i]->unk2[j]; - spdlog::info((short)aiNetwork->nodes[i]->unk2[j]); - } - - memcpy(diskNode.unk3, aiNetwork->nodes[i]->unk3, sizeof(diskNode.unk3)); - diskNode.unk4 = aiNetwork->nodes[i]->unk6; - diskNode.unk5 = - -1; // aiNetwork->nodes[i]->unk8; // this field is wrong, however, it's always -1 in vanilla navmeshes anyway, so no biggie - memcpy(diskNode.unk6, aiNetwork->nodes[i]->unk10, sizeof(diskNode.unk6)); - - spdlog::info("writing node {} from {} to {:x}", aiNetwork->nodes[i]->index, (void*)aiNetwork->nodes[i], writeStream.tellp()); - writeStream.write((char*)&diskNode, sizeof(CAI_NodeDisk)); - - calculatedLinkcount += aiNetwork->nodes[i]->linkcount; - } - - // links - spdlog::info("linkcount: {}", aiNetwork->linkcount); - spdlog::info("calculated total linkcount: {}", calculatedLinkcount); - - calculatedLinkcount /= 2; - if (Cvar_ns_ai_dumpAINfileFromLoad->GetBool()) - { - if (aiNetwork->linkcount == calculatedLinkcount) - spdlog::info("caculated linkcount is normal!"); - else - spdlog::warn("calculated linkcount has weird value! this is expected on build!"); - } - - spdlog::info("writing linkcount: {}", calculatedLinkcount); - writeStream.write((char*)&calculatedLinkcount, sizeof(int)); - - for (int i = 0; i < aiNetwork->nodecount; i++) - { - for (int j = 0; j < aiNetwork->nodes[i]->linkcount; j++) - { - // skip links that don't originate from current node - if (aiNetwork->nodes[i]->links[j]->srcId != aiNetwork->nodes[i]->index) - continue; - - CAI_NodeLinkDisk diskLink; - diskLink.srcId = aiNetwork->nodes[i]->links[j]->srcId; - diskLink.destId = aiNetwork->nodes[i]->links[j]->destId; - diskLink.unk0 = aiNetwork->nodes[i]->links[j]->unk1; - memcpy(diskLink.hulls, aiNetwork->nodes[i]->links[j]->hulls, sizeof(diskLink.hulls)); - - spdlog::info("writing link {} => {} to {:x}", diskLink.srcId, diskLink.destId, writeStream.tellp()); - writeStream.write((char*)&diskLink, sizeof(CAI_NodeLinkDisk)); - } - } - - // don't know what this is, it's likely a block from tf1 that got deprecated? should just be 1 int per node - spdlog::info("writing {:x} bytes for unknown block at {:x}", aiNetwork->nodecount * sizeof(uint32_t), writeStream.tellp()); - uint32_t* unkNodeBlock = new uint32_t[aiNetwork->nodecount]; - memset(unkNodeBlock, 0, aiNetwork->nodecount * sizeof(uint32_t)); - writeStream.write((char*)unkNodeBlock, aiNetwork->nodecount * sizeof(uint32_t)); - delete[] unkNodeBlock; - - // TODO: this is traverse nodes i think? these aren't used in tf2 ains so we can get away with just writing count=0 and skipping - // but ideally should actually dump these - spdlog::info("writing {} traversal nodes at {:x}...", 0, writeStream.tellp()); - short traverseNodeCount = 0; - writeStream.write((char*)&traverseNodeCount, sizeof(short)); - // only write count since count=0 means we don't have to actually do anything here - - // TODO: ideally these should be actually dumped, but they're always 0 in tf2 from what i can tell - spdlog::info("writing {} bytes for unknown hull block at {:x}", MAX_HULLS * 8, writeStream.tellp()); - char* unkHullBlock = new char[MAX_HULLS * 8]; - memset(unkHullBlock, 0, MAX_HULLS * 8); - writeStream.write(unkHullBlock, MAX_HULLS * 8); - delete[] unkHullBlock; - - // unknown struct that's seemingly node-related - spdlog::info("writing {} unknown node structs at {:x}", *pUnkStruct0Count, writeStream.tellp()); - writeStream.write((char*)pUnkStruct0Count, sizeof(*pUnkStruct0Count)); - for (int i = 0; i < *pUnkStruct0Count; i++) - { - spdlog::info("writing unknown node struct {} at {:x}", i, writeStream.tellp()); - UnkNodeStruct0* nodeStruct = (*pppUnkNodeStruct0s)[i]; - - writeStream.write((char*)&nodeStruct->index, sizeof(nodeStruct->index)); - writeStream.write((char*)&nodeStruct->unk1, sizeof(nodeStruct->unk1)); - - writeStream.write((char*)&nodeStruct->x, sizeof(nodeStruct->x)); - writeStream.write((char*)&nodeStruct->y, sizeof(nodeStruct->y)); - writeStream.write((char*)&nodeStruct->z, sizeof(nodeStruct->z)); - - writeStream.write((char*)&nodeStruct->unkcount0, sizeof(nodeStruct->unkcount0)); - for (int j = 0; j < nodeStruct->unkcount0; j++) - { - short unk2Short = (short)nodeStruct->unk2[j]; - writeStream.write((char*)&unk2Short, sizeof(unk2Short)); - } - - writeStream.write((char*)&nodeStruct->unkcount1, sizeof(nodeStruct->unkcount1)); - for (int j = 0; j < nodeStruct->unkcount1; j++) - { - short unk3Short = (short)nodeStruct->unk3[j]; - writeStream.write((char*)&unk3Short, sizeof(unk3Short)); - } - - writeStream.write((char*)&nodeStruct->unk5, sizeof(nodeStruct->unk5)); - } - - // unknown struct that's seemingly link-related - spdlog::info("writing {} unknown link structs at {:x}", *pUnkLinkStruct1Count, writeStream.tellp()); - writeStream.write((char*)pUnkLinkStruct1Count, sizeof(*pUnkLinkStruct1Count)); - for (int i = 0; i < *pUnkLinkStruct1Count; i++) - { - // disk and memory structs are literally identical here so just directly write - spdlog::info("writing unknown link struct {} at {:x}", i, writeStream.tellp()); - writeStream.write((char*)(*pppUnkStruct1s)[i], sizeof(*(*pppUnkStruct1s)[i])); - } - - // some weird int idk what this is used for - writeStream.write((char*)&aiNetwork->unk5, sizeof(aiNetwork->unk5)); - - // tf2-exclusive stuff past this point, i.e. ain v57 only - spdlog::info("writing {} script nodes at {:x}", aiNetwork->scriptnodecount, writeStream.tellp()); - writeStream.write((char*)&aiNetwork->scriptnodecount, sizeof(aiNetwork->scriptnodecount)); - for (int i = 0; i < aiNetwork->scriptnodecount; i++) - { - // disk and memory structs are literally identical here so just directly write - spdlog::info("writing script node {} at {:x}", i, writeStream.tellp()); - writeStream.write((char*)&aiNetwork->scriptnodes[i], sizeof(aiNetwork->scriptnodes[i])); - } - - spdlog::info("writing {} hints at {:x}", aiNetwork->hintcount, writeStream.tellp()); - writeStream.write((char*)&aiNetwork->hintcount, sizeof(aiNetwork->hintcount)); - for (int i = 0; i < aiNetwork->hintcount; i++) - { - spdlog::info("writing hint data {} at {:x}", i, writeStream.tellp()); - writeStream.write((char*)&aiNetwork->hints[i], sizeof(aiNetwork->hints[i])); - } - - writeStream.close(); -} - -// clang-format off -AUTOHOOK(CAI_NetworkBuilder__Build, server.dll + 0x385E20, -void, __fastcall, (void* builder, CAI_Network* aiNetwork, void* unknown)) -// clang-format on -{ - CAI_NetworkBuilder__Build(builder, aiNetwork, unknown); - - DumpAINInfo(aiNetwork); -} - -// clang-format off -AUTOHOOK(LoadAINFile, server.dll + 0x3933A0, -void, __fastcall, (void* aimanager, void* buf, const char* filename)) -// clang-format on -{ - LoadAINFile(aimanager, buf, filename); - - if (Cvar_ns_ai_dumpAINfileFromLoad->GetBool()) - { - spdlog::info("running DumpAINInfo for loaded file {}", filename); - DumpAINInfo(*(CAI_Network**)((char*)aimanager + 2536)); - } -} - -ON_DLL_LOAD("server.dll", BuildAINFile, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - Cvar_ns_ai_dumpAINfileFromLoad = new ConVar( - "ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk"); - - pUnkStruct0Count = module.Offset(0x1063BF8).RCast<int*>(); - pppUnkNodeStruct0s = module.Offset(0x1063BE0).RCast<UnkNodeStruct0***>(); - pUnkLinkStruct1Count = module.Offset(0x1063AA8).RCast<int*>(); - pppUnkStruct1s = module.Offset(0x1063A90).RCast<UnkLinkStruct1***>(); -} diff --git a/NorthstarDLL/server/r2server.cpp b/NorthstarDLL/server/r2server.cpp deleted file mode 100644 index c52f396e..00000000 --- a/NorthstarDLL/server/r2server.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "r2server.h" - -CBaseEntity* (*Server_GetEntityByIndex)(int index); -CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); - -ON_DLL_LOAD("server.dll", R2GameServer, (CModule module)) -{ - Server_GetEntityByIndex = module.Offset(0xFB820).RCast<CBaseEntity* (*)(int)>(); - UTIL_PlayerByIndex = module.Offset(0x26AA10).RCast<CBasePlayer*(__fastcall*)(int)>(); -} diff --git a/NorthstarDLL/server/r2server.h b/NorthstarDLL/server/r2server.h deleted file mode 100644 index c40cdc1f..00000000 --- a/NorthstarDLL/server/r2server.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include "core/math/vector.h" - -// server entity stuff -class CBaseEntity; -extern CBaseEntity* (*Server_GetEntityByIndex)(int index); - -// clang-format off -OFFSET_STRUCT(CBasePlayer) -{ - FIELD(0x58, uint32_t m_nPlayerIndex) - - FIELD(0x23E8, bool m_grappleActive) - FIELD(0x1D08, uint32_t m_platformUserId) - FIELD(0x1D10, int32_t m_classModsActive) - FIELD(0x1D8C, int32_t m_posClassModsActive) - FIELD(0x1DCC, bool m_passives) - FIELD(0x4948, int32_t m_selectedOffhand) - FIELD(0x1358, int32_t m_selectedOffhandPendingHybridAction) - FIELD(0x1E88, int32_t m_playerFlags) - FIELD(0x26A8, int32_t m_lastUCmdSimulationTicks) - FIELD(0x26AC, float m_lastUCmdSimulationRemainderTime) - FIELD(0x1F04, int32_t m_remoteTurret) - FIELD(0x414, int32_t m_hGroundEntity) - FIELD(0x13B8, int32_t m_titanSoul) - FIELD(0x2054, int32_t m_petTitan) - FIELD(0x4D4, int32_t m_iHealth) - FIELD(0x4D0, int32_t m_iMaxHealth) - FIELD(0x4F1, int32_t m_lifeState) - FIELD(0x50C, float m_flMaxspeed) - FIELD(0x298, int32_t m_fFlags) - FIELD(0x1F64, int32_t m_iObserverMode) - FIELD(0x1F6C, int32_t m_hObserverTarget) - FIELD(0x2098, int32_t m_hViewModel) - FIELD(0x27E4, int32_t m_ubEFNointerpParity) - FIELD(0x1FA4, int32_t m_activeBurnCardIndex) - FIELD(0x1B68, int32_t m_hColorCorrectionCtrl) - FIELD(0x19E0, int32_t m_PlayerFog__m_hCtrl) - FIELD(0x26BC, bool m_bShouldDrawPlayerWhileUsingViewEntity) - FIELD(0x2848, char m_title[32]) - FIELD(0x2964, bool m_useCredit) - FIELD(0x1F40, float m_damageImpulseNoDecelEndTime) - FIELD(0x1E8C, bool m_hasMic) - FIELD(0x1E8D, bool m_inPartyChat) - FIELD(0x1E90, float m_playerMoveSpeedScale) - FIELD(0x1F58, float m_flDeathTime) - FIELD(0x25A8, bool m_iSpawnParity) - FIELD(0x102284, Vector3 m_upDir) - FIELD(0x259C, float m_lastDodgeTime) - FIELD(0x22E0, bool m_wallHanging) - FIELD(0x22EC, int32_t m_traversalType) - FIELD(0x22F0, int32_t m_traversalState) - FIELD(0x2328, Vector3 m_traversalRefPos) - FIELD(0x231C, Vector3 m_traversalForwardDir) - FIELD(0x2354, float m_traversalYawDelta) - FIELD(0x2358, int32_t m_traversalYawPoseParameter) - FIELD(0x2050, int32_t m_grappleHook) - FIELD(0x27C0, int32_t m_autoSprintForced) - FIELD(0x27C4, bool m_fIsSprinting) - FIELD(0x27CC, float m_sprintStartedTime) - FIELD(0x27D0, float m_sprintStartedFrac) - FIELD(0x27D4, float m_sprintEndedTime) - FIELD(0x27D8, float m_sprintEndedFrac) - FIELD(0x27DC, float m_stickySprintStartTime) - FIELD(0x2998, float m_smartAmmoPreviousHighestLockOnMeFractionValue) - FIELD(0x23FC, int32_t m_activeZipline) - FIELD(0x2400, bool m_ziplineReverse) - FIELD(0x2410, int32_t m_ziplineState) - FIELD(0x2250, int32_t m_duckState) - FIELD(0x2254, Vector3 m_StandHullMin) - FIELD(0x2260, Vector3 m_StandHullMax) - FIELD(0x226C, Vector3 m_DuckHullMin) - FIELD(0x2278, Vector3 m_DuckHullMax) - FIELD(0x205C, int32_t m_xp) - FIELD(0x2060, int32_t m_generation) - FIELD(0x2064, int32_t m_rank) - FIELD(0x2068, int32_t m_serverForceIncreasePlayerListGenerationParity) - FIELD(0x206C, bool m_isPlayingRanked) - FIELD(0x2070, float m_skill_mu) - FIELD(0x1E80, int32_t m_titanSoulBeingRodeoed) - FIELD(0x1E84, int32_t m_entitySyncingWithMe) - FIELD(0x2078, float m_nextTitanRespawnAvailable) - FIELD(0x1C90, bool m_hasBadReputation) - FIELD(0x1C91, char m_communityName[64]) - FIELD(0x1CD1, char m_communityClanTag[16]) - FIELD(0x1CE1, char m_factionName[16]) - FIELD(0x1CF1, char m_hardwareIcon[16]) - FIELD(0x1D01, bool m_happyHourActive) - FIELD(0x1EF4, int32_t m_gestureAutoKillBitfield) - FIELD(0x2EA8, int32_t m_pilotClassIndex) - FIELD(0x100490, Vector3 m_vecAbsOrigin) - FIELD(0x25BE, bool m_isPerformingBoostAction) - FIELD(0x240C, bool m_ziplineValid3pWeaponLayerAnim) - FIELD(0x345C, int32_t m_playerScriptNetDataGlobal) - FIELD(0x1598, int32_t m_bZooming) - FIELD(0x1599, bool m_zoomToggleOn) - FIELD(0x159C, float m_zoomBaseFrac) - FIELD(0x15A0, float m_zoomBaseTime) - FIELD(0x15A4, float m_zoomFullStartTime) - FIELD(0xA04, int32_t m_camoIndex) - FIELD(0xA08, int32_t m_decalIndex) -}; -// clang-format on - -extern CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); diff --git a/NorthstarDLL/server/serverchathooks.cpp b/NorthstarDLL/server/serverchathooks.cpp deleted file mode 100644 index d3ac4776..00000000 --- a/NorthstarDLL/server/serverchathooks.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "serverchathooks.h" -#include "shared/exploit_fixes/ns_limits.h" -#include "squirrel/squirrel.h" -#include "server/r2server.h" -#include "util/utils.h" - -#include <rapidjson/document.h> -#include <rapidjson/stringbuffer.h> -#include <rapidjson/writer.h> - -AUTOHOOK_INIT() - -class CServerGameDLL; - -class CRecipientFilter -{ - char unknown[58]; -}; - -CServerGameDLL* g_pServerGameDLL; - -void(__fastcall* CServerGameDLL__OnReceivedSayTextMessage)( - CServerGameDLL* self, unsigned int senderPlayerId, const char* text, int channelId); - -void(__fastcall* CRecipientFilter__Construct)(CRecipientFilter* self); -void(__fastcall* CRecipientFilter__Destruct)(CRecipientFilter* self); -void(__fastcall* CRecipientFilter__AddAllPlayers)(CRecipientFilter* self); -void(__fastcall* CRecipientFilter__AddRecipient)(CRecipientFilter* self, const CBasePlayer* player); -void(__fastcall* CRecipientFilter__MakeReliable)(CRecipientFilter* self); - -void(__fastcall* UserMessageBegin)(CRecipientFilter* filter, const char* messagename); -void(__fastcall* MessageEnd)(); -void(__fastcall* MessageWriteByte)(int iValue); -void(__fastcall* MessageWriteString)(const char* sz); -void(__fastcall* MessageWriteBool)(bool bValue); - -bool bShouldCallSayTextHook = false; -// clang-format off -AUTOHOOK(_CServerGameDLL__OnReceivedSayTextMessage, server.dll + 0x1595C0, -void, __fastcall, (CServerGameDLL* self, unsigned int senderPlayerId, const char* text, bool isTeam)) -// clang-format on -{ - RemoveAsciiControlSequences(const_cast<char*>(text), true); - - // 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 (bShouldCallSayTextHook) - { - bShouldCallSayTextHook = false; - _CServerGameDLL__OnReceivedSayTextMessage(self, senderPlayerId, text, isTeam); - return; - } - - // check chat ratelimits - if (!g_pServerLimits->CheckChatLimits(&g_pClientArray[senderPlayerId - 1])) - return; - - SQRESULT result = g_pSquirrel<ScriptContext::SERVER>->Call( - "CServerGameDLL_ProcessMessageStartThread", static_cast<int>(senderPlayerId) - 1, text, isTeam); - - if (result == SQRESULT_ERROR) - _CServerGameDLL__OnReceivedSayTextMessage(self, senderPlayerId, text, isTeam); -} - -void ChatSendMessage(unsigned int playerIndex, const char* text, bool isTeam) -{ - bShouldCallSayTextHook = true; - CServerGameDLL__OnReceivedSayTextMessage( - g_pServerGameDLL, - // 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_s(sendText + 1, 255, text, 254); - - // 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); -} - -ADD_SQFUNC("void", NSSendMessage, "int playerIndex, string text, bool isTeam", "", ScriptContext::SERVER) -{ - int playerIndex = g_pSquirrel<ScriptContext::SERVER>->getinteger(sqvm, 1); - const char* text = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); - bool isTeam = g_pSquirrel<ScriptContext::SERVER>->getbool(sqvm, 3); - - ChatSendMessage(playerIndex, text, isTeam); - - return SQRESULT_NULL; -} - -ADD_SQFUNC( - "void", - NSBroadcastMessage, - "int fromPlayerIndex, int toPlayerIndex, string text, bool isTeam, bool isDead, int messageType", - "", - ScriptContext::SERVER) -{ - int fromPlayerIndex = g_pSquirrel<ScriptContext::SERVER>->getinteger(sqvm, 1); - int toPlayerIndex = g_pSquirrel<ScriptContext::SERVER>->getinteger(sqvm, 2); - const char* text = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 3); - bool isTeam = g_pSquirrel<ScriptContext::SERVER>->getbool(sqvm, 4); - bool isDead = g_pSquirrel<ScriptContext::SERVER>->getbool(sqvm, 5); - int messageType = g_pSquirrel<ScriptContext::SERVER>->getinteger(sqvm, 6); - - if (messageType < 1) - { - g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, fmt::format("Invalid message type {}", messageType).c_str()); - return SQRESULT_ERROR; - } - - ChatBroadcastMessage(fromPlayerIndex, toPlayerIndex, text, isTeam, isDead, (CustomMessageType)messageType); - - return SQRESULT_NULL; -} - -ON_DLL_LOAD("engine.dll", EngineServerChatHooks, (CModule module)) -{ - g_pServerGameDLL = module.Offset(0x13F0AA98).RCast<CServerGameDLL*>(); -} - -ON_DLL_LOAD_RELIESON("server.dll", ServerChatHooks, ServerSquirrel, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(server.dll) - - CServerGameDLL__OnReceivedSayTextMessage = - module.Offset(0x1595C0).RCast<void(__fastcall*)(CServerGameDLL*, unsigned int, const char*, int)>(); - CRecipientFilter__Construct = module.Offset(0x1E9440).RCast<void(__fastcall*)(CRecipientFilter*)>(); - CRecipientFilter__Destruct = module.Offset(0x1E9700).RCast<void(__fastcall*)(CRecipientFilter*)>(); - CRecipientFilter__AddAllPlayers = module.Offset(0x1E9940).RCast<void(__fastcall*)(CRecipientFilter*)>(); - CRecipientFilter__AddRecipient = module.Offset(0x1E9B30).RCast<void(__fastcall*)(CRecipientFilter*, const CBasePlayer*)>(); - CRecipientFilter__MakeReliable = module.Offset(0x1EA4E0).RCast<void(__fastcall*)(CRecipientFilter*)>(); - - UserMessageBegin = module.Offset(0x15C520).RCast<void(__fastcall*)(CRecipientFilter*, const char*)>(); - MessageEnd = module.Offset(0x158880).RCast<void(__fastcall*)()>(); - MessageWriteByte = module.Offset(0x158A90).RCast<void(__fastcall*)(int)>(); - MessageWriteString = module.Offset(0x158D00).RCast<void(__fastcall*)(const char*)>(); - MessageWriteBool = module.Offset(0x158A00).RCast<void(__fastcall*)(bool)>(); -} diff --git a/NorthstarDLL/server/serverchathooks.h b/NorthstarDLL/server/serverchathooks.h deleted file mode 100644 index d033e769..00000000 --- a/NorthstarDLL/server/serverchathooks.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#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 = (unsigned char)~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); diff --git a/NorthstarDLL/server/servernethooks.cpp b/NorthstarDLL/server/servernethooks.cpp deleted file mode 100644 index 148b735f..00000000 --- a/NorthstarDLL/server/servernethooks.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "core/convar/convar.h" -#include "engine/r2engine.h" -#include "shared/exploit_fixes/ns_limits.h" -#include "masterserver/masterserver.h" - -#include <string> -#include <thread> -#include <bcrypt.h> - -AUTOHOOK_INIT() - -static ConVar* Cvar_net_debug_atlas_packet; -static ConVar* Cvar_net_debug_atlas_packet_insecure; - -static BCRYPT_ALG_HANDLE HMACSHA256; -constexpr size_t HMACSHA256_LEN = 256 / 8; - -static bool InitHMACSHA256() -{ - NTSTATUS status; - DWORD hashLength = 0; - ULONG hashLengthSz = 0; - - if ((status = BCryptOpenAlgorithmProvider(&HMACSHA256, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG))) - { - spdlog::error("failed to initialize HMAC-SHA256: BCryptOpenAlgorithmProvider: error 0x{:08X}", (ULONG)status); - return false; - } - - if ((status = BCryptGetProperty(HMACSHA256, BCRYPT_HASH_LENGTH, (PUCHAR)&hashLength, sizeof(hashLength), &hashLengthSz, 0))) - { - spdlog::error("failed to initialize HMAC-SHA256: BCryptGetProperty(BCRYPT_HASH_LENGTH): error 0x{:08X}", (ULONG)status); - return false; - } - - if (hashLength != HMACSHA256_LEN) - { - spdlog::error("failed to initialize HMAC-SHA256: BCryptGetProperty(BCRYPT_HASH_LENGTH): unexpected value {}", hashLength); - return false; - } - - return true; -} - -// compare the HMAC-SHA256(data, key) against sig (note: all strings are treated as raw binary data) -static bool VerifyHMACSHA256(std::string key, std::string sig, std::string data) -{ - uint8_t invalid = 1; - char hash[HMACSHA256_LEN]; - - NTSTATUS status; - BCRYPT_HASH_HANDLE h = NULL; - - if ((status = BCryptCreateHash(HMACSHA256, &h, NULL, 0, (PUCHAR)key.c_str(), (ULONG)key.length(), 0))) - { - spdlog::error("failed to verify HMAC-SHA256: BCryptCreateHash: error 0x{:08X}", (ULONG)status); - goto cleanup; - } - - if ((status = BCryptHashData(h, (PUCHAR)data.c_str(), (ULONG)data.length(), 0))) - { - spdlog::error("failed to verify HMAC-SHA256: BCryptHashData: error 0x{:08X}", (ULONG)status); - goto cleanup; - } - - if ((status = BCryptFinishHash(h, (PUCHAR)&hash, (ULONG)sizeof(hash), 0))) - { - spdlog::error("failed to verify HMAC-SHA256: BCryptFinishHash: error 0x{:08X}", (ULONG)status); - goto cleanup; - } - - // constant-time compare - if (sig.length() == sizeof(hash)) - { - invalid = 0; - for (size_t i = 0; i < sizeof(hash); i++) - invalid |= (uint8_t)(sig[i]) ^ (uint8_t)(hash[i]); - } - -cleanup: - if (h) - BCryptDestroyHash(h); - return !invalid; -} - -// v1 HMACSHA256-signed masterserver request (HMAC-SHA256(JSONData, MasterServerToken) + JSONData) -static void ProcessAtlasConnectionlessPacketSigreq1(netpacket_t* packet, bool dbg, std::string pType, std::string pData) -{ - if (pData.length() < HMACSHA256_LEN) - { - if (dbg) - spdlog::warn("ignoring Atlas connectionless packet (size={} type={}): invalid: too short for signature", packet->size, pType); - return; - } - - std::string pSig; // is binary data, not actually an ASCII string - pSig = pData.substr(0, HMACSHA256_LEN); - pData = pData.substr(HMACSHA256_LEN); - - if (!g_pMasterServerManager || !g_pMasterServerManager->m_sOwnServerAuthToken[0]) - { - if (dbg) - spdlog::warn( - "ignoring Atlas connectionless packet (size={} type={}): invalid (data={}): no masterserver token yet", - packet->size, - pType, - pData); - return; - } - - if (!VerifyHMACSHA256(std::string(g_pMasterServerManager->m_sOwnServerAuthToken), pSig, pData)) - { - if (!Cvar_net_debug_atlas_packet_insecure->GetBool()) - { - if (dbg) - spdlog::warn( - "ignoring Atlas connectionless packet (size={} type={}): invalid: invalid signature (key={})", - packet->size, - pType, - std::string(g_pMasterServerManager->m_sOwnServerAuthToken)); - return; - } - spdlog::warn( - "processing Atlas connectionless packet (size={} type={}) with invalid signature due to net_debug_atlas_packet_insecure", - packet->size, - pType); - } - - if (dbg) - spdlog::info("got Atlas connectionless packet (size={} type={} data={})", packet->size, pType, pData); - - std::thread t(&MasterServerManager::ProcessConnectionlessPacketSigreq1, g_pMasterServerManager, pData); - t.detach(); - - return; -} - -static void ProcessAtlasConnectionlessPacket(netpacket_t* packet) -{ - bool dbg = Cvar_net_debug_atlas_packet->GetBool(); - - // extract kind, null-terminated type, data - std::string pType, pData; - for (int i = 5; i < packet->size; i++) - { - if (packet->data[i] == '\x00') - { - pType.assign((char*)(&packet->data[5]), (size_t)(i - 5)); - if (i + 1 < packet->size) - pData.assign((char*)(&packet->data[i + 1]), (size_t)(packet->size - i - 1)); - break; - } - } - - // note: all Atlas connectionless packets should be idempotent so multiple attempts can be made to mitigate packet loss - // note: all long-running Atlas connectionless packet handlers should be started in a new thread (with copies of the data) to avoid - // blocking networking - - // v1 HMACSHA256-signed masterserver request - if (pType == "sigreq1") - { - ProcessAtlasConnectionlessPacketSigreq1(packet, dbg, pType, pData); - return; - } - - if (dbg) - spdlog::warn("ignoring Atlas connectionless packet (size={} type={}): unknown type", packet->size, pType); - return; -} - -AUTOHOOK(ProcessConnectionlessPacket, engine.dll + 0x117800, bool, , (void* a1, netpacket_t* packet)) -{ - // packet->data consists of 0xFFFFFFFF (int32 -1) to indicate packets aren't split, followed by a header consisting of a single - // character, which is used to uniquely identify the packet kind. Most kinds follow this with a null-terminated string payload - // then an arbitrary amoount of data. - - // T (no rate limits since we authenticate packets before doing anything expensive) - if (4 < packet->size && packet->data[4] == 'T') - { - ProcessAtlasConnectionlessPacket(packet); - return false; - } - - // check rate limits for the original unconnected packets - if (!g_pServerLimits->CheckConnectionlessPacketLimits(packet)) - return false; - - // A, H, I, N - return ProcessConnectionlessPacket(a1, packet); -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerNetHooks, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - if (!InitHMACSHA256()) - throw std::runtime_error("failed to initialize bcrypt"); - - if (!VerifyHMACSHA256( - "test", - "\x88\xcd\x21\x08\xb5\x34\x7d\x97\x3c\xf3\x9c\xdf\x90\x53\xd7\xdd\x42\x70\x48\x76\xd8\xc9\xa9\xbd\x8e\x2d\x16\x82\x59\xd3\xdd" - "\xf7", - "test")) - throw std::runtime_error("bcrypt HMAC-SHA256 is broken"); - - Cvar_net_debug_atlas_packet = new ConVar( - "net_debug_atlas_packet", - "0", - FCVAR_NONE, - "Whether to log detailed debugging information for Atlas connectionless packets (warning: this allows unlimited amounts of " - "arbitrary data to be logged)"); - - Cvar_net_debug_atlas_packet_insecure = new ConVar( - "net_debug_atlas_packet_insecure", - "0", - FCVAR_NONE, - "Whether to disable signature verification for Atlas connectionless packets (DANGEROUS: this allows anyone to impersonate Atlas)"); -} diff --git a/NorthstarDLL/server/serverpresence.cpp b/NorthstarDLL/server/serverpresence.cpp deleted file mode 100644 index 159b9f30..00000000 --- a/NorthstarDLL/server/serverpresence.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "serverpresence.h" -#include "shared/playlist.h" -#include "core/tier0.h" -#include "core/convar/convar.h" - -#include <regex> - -ServerPresenceManager* g_pServerPresence; - -ConVar* Cvar_hostname; - -// Convert a hex digit char to integer. -inline int hctod(char c) -{ - if (c >= 'A' && c <= 'F') - { - return c - 'A' + 10; - } - else if (c >= 'a' && c <= 'f') - { - return c - 'a' + 10; - } - else - { - return c - '0'; - } -} - -// This function interprets all 4-hexadecimal-digit unicode codepoint characters like \u4E2D to UTF-8 encoding. -std::string UnescapeUnicode(const std::string& str) -{ - std::string result; - - std::regex r("\\\\u([a-f\\d]{4})", std::regex::icase); - auto matches_begin = std::sregex_iterator(str.begin(), str.end(), r); - auto matches_end = std::sregex_iterator(); - std::smatch last_match; - - for (std::sregex_iterator i = matches_begin; i != matches_end; ++i) - { - last_match = *i; - result.append(last_match.prefix()); - unsigned int cp = 0; - for (int i = 2; i <= 5; ++i) - { - cp *= 16; - cp += hctod(last_match.str()[i]); - } - if (cp <= 0x7F) - { - result.push_back(cp); - } - else if (cp <= 0x7FF) - { - result.push_back((cp >> 6) | 0b11000000 & (~(1 << 5))); - result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - } - else if (cp <= 0xFFFF) - { - result.push_back((cp >> 12) | 0b11100000 & (~(1 << 4))); - result.push_back((cp >> 6) & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - } - } - - if (!last_match.ready()) - return str; - else - result.append(last_match.suffix()); - - return result; -} - -void ServerPresenceManager::CreateConVars() -{ - // clang-format off - // register convars - Cvar_ns_server_presence_update_rate = new ConVar( - "ns_server_presence_update_rate", "5000", FCVAR_GAMEDLL, "How often we update our server's presence on server lists in ms"); - - Cvar_ns_server_name = new ConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL, "This server's name", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { - g_pServerPresence->SetName(UnescapeUnicode(g_pServerPresence->Cvar_ns_server_name->GetString())); - - // update engine hostname cvar - Cvar_hostname->SetValue(g_pServerPresence->Cvar_ns_server_name->GetString()); - }); - - Cvar_ns_server_desc = new ConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL, "This server's description", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { - g_pServerPresence->SetDescription(UnescapeUnicode(g_pServerPresence->Cvar_ns_server_desc->GetString())); - }); - - Cvar_ns_server_password = new ConVar("ns_server_password", "", FCVAR_GAMEDLL, "This server's password", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { - g_pServerPresence->SetPassword(g_pServerPresence->Cvar_ns_server_password->GetString()); - }); - - Cvar_ns_report_server_to_masterserver = new ConVar("ns_report_server_to_masterserver", "1", FCVAR_GAMEDLL, "Whether we should report this server to the masterserver"); - Cvar_ns_report_sp_server_to_masterserver = new ConVar("ns_report_sp_server_to_masterserver", "0", FCVAR_GAMEDLL, "Whether we should report this server to the masterserver, when started in singleplayer"); - // clang-format on -} - -void ServerPresenceManager::AddPresenceReporter(ServerPresenceReporter* reporter) -{ - m_vPresenceReporters.push_back(reporter); -} - -void ServerPresenceManager::CreatePresence() -{ - // reset presence fields that rely on runtime server state - // these being: port, map/playlist name, and playercount/maxplayers - m_ServerPresence.m_iPort = 0; - - m_ServerPresence.m_iPlayerCount = 0; // this should actually be 0 at this point, so shouldn't need updating later - m_ServerPresence.m_iMaxPlayers = 0; - - memset(m_ServerPresence.m_MapName, 0, sizeof(m_ServerPresence.m_MapName)); - memset(m_ServerPresence.m_PlaylistName, 0, sizeof(m_ServerPresence.m_PlaylistName)); - m_ServerPresence.m_bIsSingleplayerServer = false; - - m_bHasPresence = true; - m_bFirstPresenceUpdate = true; - - // code that's calling this should set up the reset fields at this point -} - -void ServerPresenceManager::DestroyPresence() -{ - m_bHasPresence = false; - - for (ServerPresenceReporter* reporter : m_vPresenceReporters) - reporter->DestroyPresence(&m_ServerPresence); -} - -void ServerPresenceManager::RunFrame(double flCurrentTime) -{ - if (!m_bHasPresence || !Cvar_ns_report_server_to_masterserver->GetBool()) // don't run until we actually have server presence - return; - - // don't run if we're sp and don't want to report sp - if (m_ServerPresence.m_bIsSingleplayerServer && !Cvar_ns_report_sp_server_to_masterserver->GetBool()) - return; - - // Call RunFrame() so that reporters can, for example, handle std::future results as soon as they arrive. - for (ServerPresenceReporter* reporter : m_vPresenceReporters) - reporter->RunFrame(flCurrentTime, &m_ServerPresence); - - // run on a specified delay - if ((flCurrentTime - m_flLastPresenceUpdate) * 1000 < Cvar_ns_server_presence_update_rate->GetFloat()) - return; - - // is this the first frame we're updating this presence? - if (m_bFirstPresenceUpdate) - { - // let reporters setup/clear any state - for (ServerPresenceReporter* reporter : m_vPresenceReporters) - reporter->CreatePresence(&m_ServerPresence); - - m_bFirstPresenceUpdate = false; - } - - m_flLastPresenceUpdate = flCurrentTime; - - for (ServerPresenceReporter* reporter : m_vPresenceReporters) - reporter->ReportPresence(&m_ServerPresence); -} - -void ServerPresenceManager::SetPort(const int iPort) -{ - // update port - m_ServerPresence.m_iPort = iPort; -} - -void ServerPresenceManager::SetName(const std::string sServerNameUnicode) -{ - // update name - m_ServerPresence.m_sServerName = sServerNameUnicode; -} - -void ServerPresenceManager::SetDescription(const std::string sServerDescUnicode) -{ - // update desc - m_ServerPresence.m_sServerDesc = sServerDescUnicode; -} - -void ServerPresenceManager::SetPassword(const char* pPassword) -{ - // update password - strncpy_s(m_ServerPresence.m_Password, sizeof(m_ServerPresence.m_Password), pPassword, sizeof(m_ServerPresence.m_Password) - 1); -} - -void ServerPresenceManager::SetMap(const char* pMapName, bool isInitialising) -{ - // if the server is initialising (i.e. this is first map) on sp, set the server to sp - if (isInitialising) - m_ServerPresence.m_bIsSingleplayerServer = !strncmp(pMapName, "sp_", 3); - - // update map - strncpy_s(m_ServerPresence.m_MapName, sizeof(m_ServerPresence.m_MapName), pMapName, sizeof(m_ServerPresence.m_MapName) - 1); -} - -void ServerPresenceManager::SetPlaylist(const char* pPlaylistName) -{ - // update playlist - strncpy_s( - m_ServerPresence.m_PlaylistName, - sizeof(m_ServerPresence.m_PlaylistName), - pPlaylistName, - sizeof(m_ServerPresence.m_PlaylistName) - 1); - - // update maxplayers - const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", true); - - // can be null in some situations, so default 6 - if (pMaxPlayers) - m_ServerPresence.m_iMaxPlayers = std::stoi(pMaxPlayers); - else - m_ServerPresence.m_iMaxPlayers = 6; -} - -void ServerPresenceManager::SetPlayerCount(const int iPlayerCount) -{ - m_ServerPresence.m_iPlayerCount = iPlayerCount; -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerPresence, ConVar, (CModule module)) -{ - g_pServerPresence->CreateConVars(); - Cvar_hostname = module.Offset(0x1315BAE8).Deref().RCast<ConVar*>(); -} diff --git a/NorthstarDLL/server/serverpresence.h b/NorthstarDLL/server/serverpresence.h deleted file mode 100644 index c644cc37..00000000 --- a/NorthstarDLL/server/serverpresence.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once -#include "core/convar/convar.h" - -struct ServerPresence -{ -public: - int m_iPort; - - std::string m_sServerId; - - std::string m_sServerName; - std::string m_sServerDesc; - char m_Password[256]; // probably bigger than will ever be used in practice, lol - - char m_MapName[32]; - char m_PlaylistName[64]; - bool m_bIsSingleplayerServer; // whether the server started in sp - - int m_iPlayerCount; - int m_iMaxPlayers; - - ServerPresence() - { - memset(this, 0, sizeof(this)); - } - - ServerPresence(const ServerPresence* obj) - { - m_iPort = obj->m_iPort; - - m_sServerId = obj->m_sServerId; - - m_sServerName = obj->m_sServerName; - m_sServerDesc = obj->m_sServerDesc; - memcpy(m_Password, obj->m_Password, sizeof(m_Password)); - - memcpy(m_MapName, obj->m_MapName, sizeof(m_MapName)); - memcpy(m_PlaylistName, obj->m_PlaylistName, sizeof(m_PlaylistName)); - - m_iPlayerCount = obj->m_iPlayerCount; - m_iMaxPlayers = obj->m_iMaxPlayers; - } -}; - -class ServerPresenceReporter -{ -public: - virtual void CreatePresence(const ServerPresence* pServerPresence) {} - virtual void ReportPresence(const ServerPresence* pServerPresence) {} - virtual void DestroyPresence(const ServerPresence* pServerPresence) {} - virtual void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) {} -}; - -class ServerPresenceManager -{ -private: - ServerPresence m_ServerPresence; - - bool m_bHasPresence = false; - bool m_bFirstPresenceUpdate = false; - - std::vector<ServerPresenceReporter*> m_vPresenceReporters; - - double m_flLastPresenceUpdate = 0; - ConVar* Cvar_ns_server_presence_update_rate; - - ConVar* Cvar_ns_server_name; - ConVar* Cvar_ns_server_desc; - ConVar* Cvar_ns_server_password; - - ConVar* Cvar_ns_report_server_to_masterserver; - ConVar* Cvar_ns_report_sp_server_to_masterserver; - -public: - void AddPresenceReporter(ServerPresenceReporter* reporter); - - void CreateConVars(); - - void CreatePresence(); - void DestroyPresence(); - void RunFrame(double flCurrentTime); - - void SetPort(const int iPort); - - void SetName(const std::string sServerNameUnicode); - void SetDescription(const std::string sServerDescUnicode); - void SetPassword(const char* pPassword); - - void SetMap(const char* pMapName, bool isInitialising = false); - void SetPlaylist(const char* pPlaylistName); - void SetPlayerCount(const int iPlayerCount); -}; - -extern ServerPresenceManager* g_pServerPresence; diff --git a/NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp b/NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp deleted file mode 100644 index 8064d5ac..00000000 --- a/NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp +++ /dev/null @@ -1,461 +0,0 @@ -#include "core/convar/cvar.h" -#include "ns_limits.h" -#include "dedicated/dedicated.h" -#include "core/tier0.h" -#include "engine/r2engine.h" -#include "client/r2client.h" -#include "core/math/vector.h" -#include "core/vanilla.h" - -AUTOHOOK_INIT() - -ConVar* Cvar_ns_exploitfixes_log; -ConVar* Cvar_ns_should_log_all_clientcommands; - -ConVar* Cvar_sv_cheats; - -#define BLOCKED_INFO(s) \ - ( \ - [=]() -> bool \ - { \ - if (Cvar_ns_exploitfixes_log->GetBool()) \ - { \ - std::stringstream stream; \ - stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \ - spdlog::error(stream.str()); \ - } \ - return false; \ - }()) - -// block bad netmessages -// Servers can literally request a screenshot from any client, yeah no -// clang-format off -AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20, -bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10 -// clang-format on -{ - if (g_pVanillaCompatibility->GetVanillaCompatibility()) - return CLC_Screenshot_WriteToBuffer(thisptr, buffer); - return false; -} - -// clang-format off -AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00, -bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38 -// clang-format on -{ - if (g_pVanillaCompatibility->GetVanillaCompatibility()) - return CLC_Screenshot_ReadFromBuffer(thisptr, buffer); - return false; -} - -// This is unused ingame and a big client=>server=>client exploit vector -// clang-format off -AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040, -bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70 -// clang-format on -{ - return false; -} - -// clang-format off -AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0, -bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10 -// clang-format on -{ - - constexpr int ENTRY_STR_LEN = 260; - struct SetConVarEntry - { - char name[ENTRY_STR_LEN]; - char val[ENTRY_STR_LEN]; - }; - - struct NET_SetConVar - { - void* vtable; - void* unk1; - void* unk2; - void* m_pMessageHandler; - SetConVarEntry* m_ConVars; // convar entry array - void* unk5; // these 2 unks are just vector capacity or whatever - void* unk6; - int m_ConVars_count; // amount of cvar entries in array (this will not be out of bounds) - }; - - auto msg = (NET_SetConVar*)pMsg; - bool bIsServerFrame = ThreadInServerFrameThread(); - - std::string BLOCK_PREFIX = - std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: "; - - if (bIsServerFrame) - { - constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69; - if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT) - { - return BLOCKED_INFO("Invalid m_ConVars_count (" << msg->m_ConVars_count << ")"); - } - } - - for (int i = 0; i < msg->m_ConVars_count; i++) - { - auto entry = msg->m_ConVars + i; - - // Safety check for memory access - if (CMemoryAddress(entry).IsMemoryReadable(sizeof(*entry))) - { - // Find null terminators - bool nameValid = false, valValid = false; - for (int i = 0; i < ENTRY_STR_LEN; i++) - { - if (!entry->name[i]) - nameValid = true; - if (!entry->val[i]) - valValid = true; - } - - if (!nameValid || !valValid) - return BLOCKED_INFO("Missing null terminators"); - - ConVar* pVar = g_pCVar->FindVar(entry->name); - - if (pVar) - { - memcpy( - entry->name, - pVar->m_ConCommandBase.m_pszName, - strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case - - int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED; - if (!pVar->IsFlagSet(iFlags)) - return BLOCKED_INFO( - "Invalid flags (" << std::hex << "0x" << pVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name); - } - } - else - { - return BLOCKED_INFO("Unreadable memory at " << (void*)entry); // Not risking that one, they all gotta be readable - } - } - - return CClient_ProcessSetConVar(msg); -} - -// prevent invalid user CMDs -// clang-format off -AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0, -bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58 -// clang-format on -{ - struct CLC_Move - { - BYTE gap0[24]; - void* m_pMessageHandler; - int m_nBackupCommands; - int m_nNewCommands; - int m_nLength; - // bf_read m_DataIn; - // bf_write m_DataOut; - }; - - auto msg = (CLC_Move*)pMsg; - - const char* BLOCK_PREFIX = "ProcessUserCmds: "; - - if (msg->m_nBackupCommands < 0) - { - return BLOCKED_INFO("Invalid m_nBackupCommands (" << msg->m_nBackupCommands << ")"); - } - - if (msg->m_nNewCommands < 0) - { - return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")"); - } - - if (msg->m_nLength <= 0) - return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")"); - - return CClient_ProcessUsercmds(thisptr, pMsg); -} - -// clang-format off -AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0, -void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57 -// clang-format on -{ - // Let normal usercmd read happen first, it's safe - ReadUsercmd(buf, pCmd_move, pCmd_from); - - // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing) - struct alignas(4) SV_CUserCmd - { - DWORD command_number; - DWORD tick_count; - float command_time; - Vector3 worldViewAngles; - BYTE gap18[4]; - Vector3 localViewAngles; - Vector3 attackangles; - Vector3 move; - DWORD buttons; - BYTE impulse; - short weaponselect; - DWORD meleetarget; - BYTE gap4C[24]; - char headoffset; - BYTE gap65[11]; - Vector3 cameraPos; - Vector3 cameraAngles; - BYTE gap88[4]; - int tickSomething; - DWORD dword90; - DWORD predictedServerEventAck; - DWORD dword98; - float frameTime; - }; - - auto cmd = (SV_CUserCmd*)pCmd_move; - auto fromCmd = (SV_CUserCmd*)pCmd_from; - - std::string BLOCK_PREFIX = - "ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): "; - - // fix invalid player angles - cmd->worldViewAngles.MakeValid(); - cmd->attackangles.MakeValid(); - cmd->localViewAngles.MakeValid(); - - // Fix invalid camera angles - cmd->cameraPos.MakeValid(); - cmd->cameraAngles.MakeValid(); - - // Fix invaid movement vector - cmd->move.MakeValid(); - - if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0) - { - BLOCKED_INFO( - "Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime - << ", commandTime : " << cmd->command_time << ")"); - goto INVALID_CMD; // No simulation of bogus-timed cmds - } - - return; - -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 = {0, 0, 0}; - cmd->tick_count = cmd->frameTime = 0; - cmd->move = cmd->cameraPos = {0, 0, 0}; - cmd->buttons = 0; - cmd->meleetarget = 0; -} - -// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls -// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one -// clang-format off -AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360, -bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63 -// clang-format on -{ - // somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn - int iSize = strlen(pModName); - g_pModName = new char[iSize + 1]; - strcpy(g_pModName, pModName); - - if (g_pVanillaCompatibility->GetVanillaCompatibility()) - return false; - - return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !CommandLine()->CheckParm("-norestrictservercommands"); -} - -// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't -// clang-format off -AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0, -bool, __fastcall, (CBaseClient* self, uint32_t unknown, const char* pCommandString)) -// clang-format on -{ - if (Cvar_ns_should_log_all_clientcommands->GetBool()) - spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString); - - if (!g_pServerLimits->CheckStringCommandLimits(self)) - { - CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); - return false; - } - - // verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand - char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor - memset(commandBuf, 0, sizeof(commandBuf)); - CCommand tempCommand = *(CCommand*)&commandBuf; - - if (!CCommand__Tokenize(tempCommand, pCommandString, cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC()) - return false; - - ConCommand* command = g_pCVar->FindCommand(tempCommand.Arg(0)); - - // if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff - if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS)) - { - // ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients - if (IsDedicatedServer()) - return false; - - if (strcmp(self->m_UID, g_pLocalPlayerUserID)) - return false; - } - - // check for and block abusable legacy portal 2 commands - // these aren't actually concommands weirdly enough, they seem to just be hardcoded - if (!Cvar_sv_cheats->GetBool()) - { - 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() - }; - - int iCmdLength = strlen(tempCommand.Arg(0)); - - bool bIsBadCommand = false; - for (auto& blockedCommand : blockedCommands) - { - if (iCmdLength != strlen(blockedCommand)) - continue; - - for (int i = 0; tempCommand.Arg(0)[i]; i++) - if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i]) - goto NEXT_COMMAND; // break out of this loop, then go to next command - - // this is a command we need to block - return false; - NEXT_COMMAND:; - } - } - - return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); -} - -// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines -bool bWasWritingStringTableSuccessful; - -// clang-format off -AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0, -void, __fastcall, (void* self)) -// clang-format on -{ - bWasWritingStringTableSuccessful = true; - CBaseClient__SendServerInfo(self); - if (!bWasWritingStringTableSuccessful) - CBaseClient__Disconnect( - self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting"); -} - -// return null when GetEntByIndex is passed an index >= 0x4000 -// this is called from exactly 1 script clientcommand that can be given an arbitrary index, and going above 0x4000 crashes -// clang-format off -AUTOHOOK(GetEntByIndex, server.dll + 0x2A8A50, -void*, __fastcall, (int i)) -// clang-format on -{ - const int MAX_ENT_IDX = 0x4000; - - if (i >= MAX_ENT_IDX) - { - spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX); - return nullptr; - } - - return GetEntByIndex(i); -} -// clang-format off -AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940, -bool, __fastcall, (void* a1)) -// clang-format on -{ - struct CEntityReadInfo - { - BYTE gap[40]; - int nNewEntity; - }; - - CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1; - if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0) - { - // Value isn't sanitized in release builds for - // every game powered by the Source Engine 1 - // causing read/write outside of array bounds. - // This defect has let to the achievement of a - // full-chain RCE exploit. We hook and perform - // sanity checks for the value of m_nNewEntity - // here to prevent this behavior from happening. - return false; - } - - return CL_CopyExistingEntity(a1); -} - -ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - // allow client/ui to run clientcommands despite restricting servercommands - module.Offset(0x4FB65).Patch("EB 11"); - module.Offset(0x4FBAC).Patch("EB 16"); - - // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails - { - CMemoryAddress writeAddress(&bWasWritingStringTableSuccessful - module.Offset(0x234EDC).m_nAddress); - - CMemoryAddress addr = module.Offset(0x234ED2); - addr.Patch("C7 05"); - addr.Offset(2).Patch((BYTE*)&writeAddress, sizeof(writeAddress)); - - addr.Offset(6).Patch("00 00 00 00"); - - addr.Offset(10).NOP(5); - } -} - -ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(server.dll) - - // ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue) - // this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches - module.Offset(0x153920).Patch("C3"); - - // Dumb ANTITAMPER patches (they negatively impact performance and security) - constexpr const char* ANTITAMPER_EXPORTS[] = { - "ANTITAMPER_SPOTCHECK_CODEMARKER", - "ANTITAMPER_TESTVALUE_CODEMARKER", - "ANTITAMPER_TRIGGER_CODEMARKER", - }; - - // Prevent these from actually doing anything - for (auto exportName : ANTITAMPER_EXPORTS) - { - CMemoryAddress exportAddr = module.GetExport(exportName); - if (exportAddr) - { - // Just return, none of them have any args or are userpurge - exportAddr.Patch("C3"); - spdlog::info("Patched AntiTamper function export \"{}\"", exportName); - } - } - - Cvar_ns_exploitfixes_log = - new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something"); - Cvar_ns_should_log_all_clientcommands = - new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands"); - - Cvar_sv_cheats = g_pCVar->FindVar("sv_cheats"); -} diff --git a/NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp b/NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp deleted file mode 100644 index ccb6ac18..00000000 --- a/NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp +++ /dev/null @@ -1,78 +0,0 @@ - -AUTOHOOK_INIT() - -static constexpr int LZSS_LOOKSHIFT = 4; - -struct lzss_header_t -{ - unsigned int id; - unsigned int actualSize; -}; - -// 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. -// clang-format off -AUTOHOOK(CLZSS__SafeDecompress, engine.dll + 0x432A10, -unsigned int, __fastcall, (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize)) -// clang-format on -{ - unsigned int totalBytes = 0; - int getCmdByte = 0; - int cmdByte = 0; - - lzss_header_t header = *(lzss_header_t*)pInput; - - if (!pInput || !header.actualSize || header.id != 0x53535A4C || 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; -} - -ON_DLL_LOAD("engine.dll", ExploitFixes_LZSS, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp b/NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp deleted file mode 100644 index 3d97f750..00000000 --- a/NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp +++ /dev/null @@ -1,199 +0,0 @@ - -AUTOHOOK_INIT() - -INT64(__fastcall* sub_F1320)(DWORD a1, char* a2); - -// Reimplementation of an exploitable UTF decoding function in titanfall -bool __fastcall CheckUTF8Valid(INT64* a1, DWORD* a2, char* strData) -{ - DWORD v3; // eax - char* v4; // rbx - char v5; // si - char* _strData; // rdi - char* v7; // rbp - char v11; // al - DWORD v12; // er9 - DWORD v13; // ecx - DWORD v14; // edx - DWORD v15; // er8 - int v16; // eax - DWORD v17; // er9 - int v18; // eax - DWORD v19; // er9 - DWORD v20; // ecx - int v21; // eax - int v22; // er9 - DWORD v23; // edx - int v24; // eax - int v25; // er9 - DWORD v26; // er9 - DWORD v27; // er10 - DWORD v28; // ecx - DWORD v29; // edx - DWORD v30; // er8 - int v31; // eax - DWORD v32; // er10 - int v33; // eax - DWORD v34; // er10 - DWORD v35; // ecx - int v36; // eax - int v37; // er10 - DWORD v38; // edx - int v39; // eax - int v40; // er10 - DWORD v41; // er10 - INT64 v43; // r8 - INT64 v44; // rdx - INT64 v45; // rcx - INT64 v46; // rax - INT64 v47; // rax - char v48; // al - INT64 v49; // r8 - INT64 v50; // rdx - INT64 v51; // rcx - INT64 v52; // rax - INT64 v53; // rax - - v3 = a2[2]; - v4 = (char*)(a1[1] + *a2); - v5 = 0; - _strData = strData; - v7 = &v4[*((UINT16*)a2 + 2)]; - if (v3 >= 2) - { - ++v4; - --v7; - if (v3 != 2) - { - while (1) - { - if (!CMemoryAddress(v4).IsMemoryReadable(1)) - return false; // INVALID - - v11 = *v4++; // crash potential - if (v11 != 92) - goto LABEL_6; - v11 = *v4++; - if (v11 == 110) - break; - switch (v11) - { - case 't': - v11 = 9; - goto LABEL_6; - case 'r': - v11 = 13; - goto LABEL_6; - case 'b': - v11 = 8; - goto LABEL_6; - case 'f': - v11 = 12; - goto LABEL_6; - } - if (v11 != 117) - goto LABEL_6; - v12 = *v4 | 0x20; - v13 = v4[1] | 0x20; - v14 = v4[2] | 0x20; - v15 = v4[3] | 0x20; - v16 = 87; - if (v12 <= 0x39) - v16 = 48; - v17 = v12 - v16; - v18 = 87; - v19 = v17 << 12; - if (v13 <= 0x39) - v18 = 48; - v20 = v13 - v18; - v21 = 87; - v22 = (v20 << 8) | v19; - if (v14 <= 0x39) - v21 = 48; - v23 = v14 - v21; - v24 = 87; - v25 = (16 * v23) | v22; - if (v15 <= 0x39) - v24 = 48; - v4 += 4; - v26 = (v15 - v24) | v25; - if (v26 - 55296 <= 0x7FF) - { - if (v26 >= 0xDC00) - return true; - if (*v4 != 92 || v4[1] != 117) - return true; - - v27 = v4[2] | 0x20; - v28 = v4[3] | 0x20; - v29 = v4[4] | 0x20; - v30 = v4[5] | 0x20; - v31 = 87; - if (v27 <= 0x39) - v31 = 48; - v32 = v27 - v31; - v33 = 87; - v34 = v32 << 12; - if (v28 <= 0x39) - v33 = 48; - v35 = v28 - v33; - v36 = 87; - v37 = (v35 << 8) | v34; - if (v29 <= 0x39) - v36 = 48; - v38 = v29 - v36; - v39 = 87; - v40 = (16 * v38) | v37; - if (v30 <= 0x39) - v39 = 48; - v4 += 6; - v41 = ((v30 - v39) | v40) - 56320; - if (v41 > 0x3FF) - return true; - v26 = v41 | ((v26 - 55296) << 10); - } - _strData += (DWORD)sub_F1320(v26, _strData); - LABEL_7: - if (v4 == v7) - goto LABEL_48; - } - v11 = 10; - LABEL_6: - v5 |= v11; - *_strData++ = v11; - goto LABEL_7; - } - } -LABEL_48: - return true; -} - -// prevent utf8 parser from crashing when provided bad data, which can be sent through user-controlled openinvites -// clang-format off -AUTOHOOK(Rson_ParseUTF8, engine.dll + 0xEF670, -bool, __fastcall, (INT64* a1, DWORD* a2, char* strData)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A -// clang-format on -{ - static void* targetRetAddr = CModule("engine.dll").FindPattern("84 C0 75 2C 49 8B 16"); - - // only call if we're parsing utf8 data from the network (i.e. communities), otherwise we get perf issues - void* pReturnAddress = -#ifdef _MSC_VER - _ReturnAddress() -#else - __builtin_return_address(0) -#endif - ; - - if (pReturnAddress == targetRetAddr && !CheckUTF8Valid(a1, a2, strData)) - return false; - - return Rson_ParseUTF8(a1, a2, strData); -} - -ON_DLL_LOAD("engine.dll", EngineExploitFixes_UTF8Parser, (CModule module)) -{ - AUTOHOOK_DISPATCH() - - sub_F1320 = module.FindPattern("83 F9 7F 77 08 88 0A").RCast<INT64(__fastcall*)(DWORD, char*)>(); -} diff --git a/NorthstarDLL/shared/exploit_fixes/ns_limits.cpp b/NorthstarDLL/shared/exploit_fixes/ns_limits.cpp deleted file mode 100644 index bd855ee4..00000000 --- a/NorthstarDLL/shared/exploit_fixes/ns_limits.cpp +++ /dev/null @@ -1,298 +0,0 @@ -#include "ns_limits.h" -#include "engine/hoststate.h" -#include "client/r2client.h" -#include "engine/r2engine.h" -#include "server/r2server.h" -#include "core/tier0.h" -#include "core/math/vector.h" -#include "server/auth/serverauthentication.h" - -AUTOHOOK_INIT() - -ServerLimitsManager* g_pServerLimits; - -float (*CEngineServer__GetTimescale)(); - -// todo: make this work on higher timescales, also possibly disable when sv_cheats is set -void ServerLimitsManager::RunFrame(double flCurrentTime, float flFrameTime) -{ - if (Cvar_sv_antispeedhack_enable->GetBool()) - { - // for each player, set their usercmd processing budget for the frame to the last frametime for the server - for (int i = 0; i < g_pGlobals->m_nMaxClients; i++) - { - CBaseClient* player = &g_pClientArray[i]; - - if (m_PlayerLimitData.find(player) != m_PlayerLimitData.end()) - { - PlayerLimitData* pLimitData = &g_pServerLimits->m_PlayerLimitData[player]; - if (pLimitData->flFrameUserCmdBudget < g_pGlobals->m_flTickInterval * Cvar_sv_antispeedhack_maxtickbudget->GetFloat()) - { - pLimitData->flFrameUserCmdBudget += g_pServerLimits->Cvar_sv_antispeedhack_budgetincreasemultiplier->GetFloat() * - fmax(flFrameTime, g_pGlobals->m_flFrameTime * CEngineServer__GetTimescale()); - } - } - } - } -} - -void ServerLimitsManager::AddPlayer(CBaseClient* player) -{ - PlayerLimitData limitData; - limitData.flFrameUserCmdBudget = - g_pGlobals->m_flTickInterval * CEngineServer__GetTimescale() * Cvar_sv_antispeedhack_maxtickbudget->GetFloat(); - - m_PlayerLimitData.insert(std::make_pair(player, limitData)); -} - -void ServerLimitsManager::RemovePlayer(CBaseClient* player) -{ - if (m_PlayerLimitData.find(player) != m_PlayerLimitData.end()) - m_PlayerLimitData.erase(player); -} - -bool ServerLimitsManager::CheckStringCommandLimits(CBaseClient* player) -{ - if (CVar_sv_quota_stringcmdspersecond->GetInt() != -1) - { - // note: this isn't super perfect, legit clients can trigger it in lobby if they try, mostly good enough tho imo - if (Plat_FloatTime() - m_PlayerLimitData[player].lastClientCommandQuotaStart >= 1.0) - { - // reset quota - m_PlayerLimitData[player].lastClientCommandQuotaStart = Plat_FloatTime(); - m_PlayerLimitData[player].numClientCommandsInQuota = 0; - } - - m_PlayerLimitData[player].numClientCommandsInQuota++; - if (m_PlayerLimitData[player].numClientCommandsInQuota > CVar_sv_quota_stringcmdspersecond->GetInt()) - { - // too many stringcmds, dc player - return false; - } - } - - return true; -} - -bool ServerLimitsManager::CheckChatLimits(CBaseClient* player) -{ - if (Plat_FloatTime() - m_PlayerLimitData[player].lastSayTextLimitStart >= 1.0) - { - m_PlayerLimitData[player].lastSayTextLimitStart = Plat_FloatTime(); - m_PlayerLimitData[player].sayTextLimitCount = 0; - } - - if (m_PlayerLimitData[player].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->GetInt()) - return false; - - m_PlayerLimitData[player].sayTextLimitCount++; - return true; -} - -// clang-format off -AUTOHOOK(CNetChan__ProcessMessages, engine.dll + 0x2140A0, -char, __fastcall, (void* self, void* buf)) -// clang-format on -{ - enum eNetChanLimitMode - { - NETCHANLIMIT_WARN, - NETCHANLIMIT_KICK - }; - - double startTime = Plat_FloatTime(); - char ret = CNetChan__ProcessMessages(self, buf); - - // check processing limits, unless we're in a level transition - if (g_pHostState->m_iCurrentState == HostState_t::HS_RUN && ThreadInServerFrameThread()) - { - // player that sent the message - CBaseClient* sender = *(CBaseClient**)((char*)self + 368); - - // if no sender, return - // relatively certain this is fine? - if (!sender || !g_pServerLimits->m_PlayerLimitData.count(sender)) - return ret; - - // reset every second - if (startTime - g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart >= 1.0 || - g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart == -1.0) - { - g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart = startTime; - g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime = 0.0; - } - g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime += (Plat_FloatTime() * 1000) - (startTime * 1000); - - if (g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime >= - g_pServerLimits->Cvar_net_chan_limit_msec_per_sec->GetInt()) - { - spdlog::warn( - "Client {} hit netchan processing limit with {}ms of processing time this second (max is {})", - (char*)sender + 0x16, - g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime, - g_pServerLimits->Cvar_net_chan_limit_msec_per_sec->GetInt()); - - // never kick local player - if (g_pServerLimits->Cvar_net_chan_limit_mode->GetInt() != NETCHANLIMIT_WARN && strcmp(g_pLocalPlayerUserID, sender->m_UID)) - { - CBaseClient__Disconnect(sender, 1, "Exceeded net channel processing limit"); - return false; - } - } - } - - return ret; -} - -bool ServerLimitsManager::CheckConnectionlessPacketLimits(netpacket_t* packet) -{ - static const ConVar* Cvar_net_data_block_enabled = g_pCVar->FindVar("net_data_block_enabled"); - - // don't ratelimit datablock packets as long as datablock is enabled - if (packet->adr.type == NA_IP && - (!(packet->data[4] == 'N' && Cvar_net_data_block_enabled->GetBool()) || !Cvar_net_data_block_enabled->GetBool())) - { - // bad lookup: optimise later tm - UnconnectedPlayerLimitData* sendData = nullptr; - for (UnconnectedPlayerLimitData& foundSendData : g_pServerLimits->m_UnconnectedPlayerLimitData) - { - if (!memcmp(packet->adr.ip, foundSendData.ip, 16)) - { - sendData = &foundSendData; - break; - } - } - - if (!sendData) - { - sendData = &g_pServerLimits->m_UnconnectedPlayerLimitData.emplace_back(); - memcpy(sendData->ip, packet->adr.ip, 16); - } - - if (Plat_FloatTime() < sendData->timeoutEnd) - return false; - - if (Plat_FloatTime() - sendData->lastQuotaStart >= 1.0) - { - sendData->lastQuotaStart = Plat_FloatTime(); - sendData->packetCount = 0; - } - - sendData->packetCount++; - - if (sendData->packetCount >= g_pServerLimits->Cvar_sv_querylimit_per_sec->GetInt()) - { - spdlog::warn( - "Client went over connectionless ratelimit of {} per sec with packet of type {}", - g_pServerLimits->Cvar_sv_querylimit_per_sec->GetInt(), - packet->data[4]); - - // timeout for a minute - sendData->timeoutEnd = Plat_FloatTime() + 60.0; - return false; - } - } - - return true; -} - -// this is weird and i'm not sure if it's correct, so not using for now -/*AUTOHOOK(CBasePlayer__PhysicsSimulate, server.dll + 0x5A6E50, bool, __fastcall, (void* self, int a2, char a3)) -{ - spdlog::info("CBasePlayer::PhysicsSimulate"); - return CBasePlayer__PhysicsSimulate(self, a2, a3); -}*/ - -struct alignas(4) SV_CUserCmd -{ - DWORD command_number; - DWORD tick_count; - float command_time; - Vector3 worldViewAngles; - BYTE gap18[4]; - Vector3 localViewAngles; - Vector3 attackangles; - Vector3 move; - DWORD buttons; - BYTE impulse; - short weaponselect; - DWORD meleetarget; - BYTE gap4C[24]; - char headoffset; - BYTE gap65[11]; - Vector3 cameraPos; - Vector3 cameraAngles; - BYTE gap88[4]; - int tickSomething; - DWORD dword90; - DWORD predictedServerEventAck; - DWORD dword98; - float frameTime; -}; - -// clang-format off -AUTOHOOK(CPlayerMove__RunCommand, server.dll + 0x5B8100, -void, __fastcall, (void* self, CBasePlayer* player, SV_CUserCmd* pUserCmd, uint64_t a4)) -// clang-format on -{ - if (g_pServerLimits->Cvar_sv_antispeedhack_enable->GetBool()) - { - CBaseClient* pClient = &g_pClientArray[player->m_nPlayerIndex - 1]; - - if (g_pServerLimits->m_PlayerLimitData.find(pClient) != g_pServerLimits->m_PlayerLimitData.end()) - { - PlayerLimitData* pLimitData = &g_pServerLimits->m_PlayerLimitData[pClient]; - - pLimitData->flFrameUserCmdBudget = fmax(0.0, pLimitData->flFrameUserCmdBudget - pUserCmd->frameTime); - - if (pLimitData->flFrameUserCmdBudget <= 0.0) - { - spdlog::warn("player {} went over usercmd budget ({})", pClient->m_Name, pLimitData->flFrameUserCmdBudget); - return; - } - } - } - - CPlayerMove__RunCommand(self, player, pUserCmd, a4); -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerLimits, ConVar, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - g_pServerLimits = new ServerLimitsManager; - - g_pServerLimits->CVar_sv_quota_stringcmdspersecond = new ConVar( - "sv_quota_stringcmdspersecond", - "60", - FCVAR_GAMEDLL, - "How many string commands per second clients are allowed to submit, 0 to disallow all string commands, -1 to disable"); - g_pServerLimits->Cvar_net_chan_limit_mode = - new ConVar("net_chan_limit_mode", "0", FCVAR_GAMEDLL, "The mode for netchan processing limits: 0 = warn, 1 = kick"); - g_pServerLimits->Cvar_net_chan_limit_msec_per_sec = new ConVar( - "net_chan_limit_msec_per_sec", - "100", - FCVAR_GAMEDLL, - "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget"); - g_pServerLimits->Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, ""); - g_pServerLimits->Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, ""); - g_pServerLimits->Cvar_sv_antispeedhack_enable = - new ConVar("sv_antispeedhack_enable", "0", FCVAR_NONE, "whether to enable antispeedhack protections"); - g_pServerLimits->Cvar_sv_antispeedhack_maxtickbudget = new ConVar( - "sv_antispeedhack_maxtickbudget", - "64", - FCVAR_GAMEDLL, - "Maximum number of client-issued usercmd ticks that can be replayed in packet loss conditions"); - g_pServerLimits->Cvar_sv_antispeedhack_budgetincreasemultiplier = new ConVar( - "sv_antispeedhack_budgetincreasemultiplier", - "1", - FCVAR_GAMEDLL, - "Increase usercmd processing budget by tickinterval * value per tick"); - - CEngineServer__GetTimescale = module.Offset(0x240840).RCast<float (*)()>(); -} - -ON_DLL_LOAD("server.dll", ServerLimitsServer, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(server.dll) -} diff --git a/NorthstarDLL/shared/exploit_fixes/ns_limits.h b/NorthstarDLL/shared/exploit_fixes/ns_limits.h deleted file mode 100644 index 546fec6f..00000000 --- a/NorthstarDLL/shared/exploit_fixes/ns_limits.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include "engine/r2engine.h" -#include "core/convar/convar.h" -#include <unordered_map> - -struct PlayerLimitData -{ - double lastClientCommandQuotaStart = -1.0; - int numClientCommandsInQuota = 0; - - double lastNetChanProcessingLimitStart = -1.0; - double netChanProcessingLimitTime = 0.0; - - double lastSayTextLimitStart = -1.0; - int sayTextLimitCount = 0; - - float flFrameUserCmdBudget = 0.0; -}; - -struct UnconnectedPlayerLimitData -{ - char ip[16]; - double lastQuotaStart = 0.0; - int packetCount = 0; - double timeoutEnd = -1.0; -}; - -class ServerLimitsManager -{ -public: - ConVar* CVar_sv_quota_stringcmdspersecond; - ConVar* Cvar_net_chan_limit_mode; - ConVar* Cvar_net_chan_limit_msec_per_sec; - ConVar* Cvar_sv_querylimit_per_sec; - ConVar* Cvar_sv_max_chat_messages_per_sec; - ConVar* Cvar_sv_antispeedhack_enable; - ConVar* Cvar_sv_antispeedhack_maxtickbudget; - ConVar* Cvar_sv_antispeedhack_budgetincreasemultiplier; - - std::unordered_map<CBaseClient*, PlayerLimitData> m_PlayerLimitData; - std::vector<UnconnectedPlayerLimitData> m_UnconnectedPlayerLimitData; - -public: - void RunFrame(double flCurrentTime, float flFrameTime); - void AddPlayer(CBaseClient* player); - void RemovePlayer(CBaseClient* player); - bool CheckStringCommandLimits(CBaseClient* player); - bool CheckChatLimits(CBaseClient* player); - bool CheckConnectionlessPacketLimits(netpacket_t* packet); -}; - -extern ServerLimitsManager* g_pServerLimits; diff --git a/NorthstarDLL/shared/keyvalues.cpp b/NorthstarDLL/shared/keyvalues.cpp deleted file mode 100644 index 88753723..00000000 --- a/NorthstarDLL/shared/keyvalues.cpp +++ /dev/null @@ -1,1322 +0,0 @@ -#include "keyvalues.h" -#include <winnt.h> - -// implementation of the ConVar class -// heavily based on https://github.com/Mauler125/r5sdk/blob/master/r5dev/vpc/keyvalues.cpp - -typedef int HKeySymbol; -#define INVALID_KEY_SYMBOL (-1) - -#define MAKE_3_BYTES_FROM_1_AND_2(x1, x2) ((((uint16_t)x2) << 8) | (uint8_t)(x1)) -#define SPLIT_3_BYTES_INTO_1_AND_2(x1, x2, x3) \ - do \ - { \ - x1 = (uint8_t)(x3); \ - x2 = (uint16_t)((x3) >> 8); \ - } while (0) - -struct CKeyValuesSystem -{ -public: - struct __VTable - { - char pad0[8 * 3]; // 2 methods - HKeySymbol (*GetSymbolForString)(CKeyValuesSystem* self, const char* name, bool bCreate); - const char* (*GetStringForSymbol)(CKeyValuesSystem* self, HKeySymbol symbol); - char pad1[8 * 5]; - HKeySymbol (*GetSymbolForStringCaseSensitive)( - CKeyValuesSystem* self, HKeySymbol& hCaseInsensitiveSymbol, const char* name, bool bCreate); - }; - - const __VTable* m_pVtable; -}; - -int (*V_UTF8ToUnicode)(const char* pUTF8, wchar_t* pwchDest, int cubDestSizeInBytes); -int (*V_UnicodeToUTF8)(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes); -CKeyValuesSystem* (*KeyValuesSystem)(); - -KeyValues::KeyValues() {} // default constructor for copying and such - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : *pszSetName - -//----------------------------------------------------------------------------- -KeyValues::KeyValues(const char* pszSetName) -{ - Init(); - SetName(pszSetName); -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : *pszSetName - -// *pszFirstKey - -// *pszFirstValue - -//----------------------------------------------------------------------------- -KeyValues::KeyValues(const char* pszSsetName, const char* pszFirstKey, const char* pszFirstValue) -{ - Init(); - SetName(pszSsetName); - SetString(pszFirstKey, pszFirstValue); -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : *pszSetName - -// *pszFirstKey - -// *pwszFirstValue - -//----------------------------------------------------------------------------- -KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, const wchar_t* pwszFirstValue) -{ - Init(); - SetName(pszSetName); - SetWString(pszFirstKey, pwszFirstValue); -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : *pszSetName - -// *pszFirstKey - -// iFirstValue - -//----------------------------------------------------------------------------- -KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue) -{ - Init(); - SetName(pszSetName); - SetInt(pszFirstKey, iFirstValue); -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : *pszSetName - -// *pszFirstKey - -// *pszFirstValue - -// *pszSecondKey - -// *pszSecondValue - -//----------------------------------------------------------------------------- -KeyValues::KeyValues( - const char* pszSetName, const char* pszFirstKey, const char* pszFirstValue, const char* pszSecondKey, const char* pszSecondValue) -{ - Init(); - SetName(pszSetName); - SetString(pszFirstKey, pszFirstValue); - SetString(pszSecondKey, pszSecondValue); -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : *pszSetName - -// *pszFirstKey - -// iFirstValue - -// *pszSecondKey - -// iSecondValue - -//----------------------------------------------------------------------------- -KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue, const char* pszSecondKey, int iSecondValue) -{ - Init(); - SetName(pszSetName); - SetInt(pszFirstKey, iFirstValue); - SetInt(pszSecondKey, iSecondValue); -} - -//----------------------------------------------------------------------------- -// Purpose: Destructor -//----------------------------------------------------------------------------- -KeyValues::~KeyValues(void) -{ - RemoveEverything(); -} - -//----------------------------------------------------------------------------- -// Purpose: Initialize member variables -//----------------------------------------------------------------------------- -void KeyValues::Init(void) -{ - m_iKeyName = 0; - m_iKeyNameCaseSensitive1 = 0; - m_iKeyNameCaseSensitive2 = 0; - m_iDataType = TYPE_NONE; - - m_pSub = nullptr; - m_pPeer = nullptr; - m_pChain = nullptr; - - m_sValue = nullptr; - m_wsValue = nullptr; - m_pValue = nullptr; - - m_bHasEscapeSequences = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: Clear out all subkeys, and the current value -//----------------------------------------------------------------------------- -void KeyValues::Clear(void) -{ - delete m_pSub; - m_pSub = nullptr; - m_iDataType = TYPE_NONE; -} - -//----------------------------------------------------------------------------- -// for backwards compat - we used to need this to force the free to run from the same DLL -// as the alloc -//----------------------------------------------------------------------------- -void KeyValues::DeleteThis(void) -{ - delete this; -} - -//----------------------------------------------------------------------------- -// Purpose: remove everything -//----------------------------------------------------------------------------- -void KeyValues::RemoveEverything(void) -{ - KeyValues* dat; - KeyValues* datNext = nullptr; - for (dat = m_pSub; dat != nullptr; dat = datNext) - { - datNext = dat->m_pPeer; - dat->m_pPeer = nullptr; - delete dat; - } - - for (dat = m_pPeer; dat && dat != this; dat = datNext) - { - datNext = dat->m_pPeer; - dat->m_pPeer = nullptr; - delete dat; - } - - delete[] m_sValue; - m_sValue = nullptr; - delete[] m_wsValue; - m_wsValue = nullptr; -} - -//----------------------------------------------------------------------------- -// Purpose: Find a keyValue, create it if it is not found. -// Set bCreate to true to create the key if it doesn't already exist -// (which ensures a valid pointer will be returned) -// Input : *pszKeyName - -// bCreate - -// Output : *KeyValues -//----------------------------------------------------------------------------- -KeyValues* KeyValues::FindKey(const char* pszKeyName, bool bCreate) -{ - assert_msg(this, "Member function called on NULL KeyValues"); - - if (!pszKeyName || !*pszKeyName) - return this; - - const char* pSubStr = strchr(pszKeyName, '/'); - const char* pSearchStr = pszKeyName; - if (pSubStr && !*(pSubStr + 1)) - { - // if key name is just '/', then use it as a key directly - pSearchStr = pSubStr; - pSubStr = nullptr; - } - - HKeySymbol iSearchStr = KeyValuesSystem()->m_pVtable->GetSymbolForString(KeyValuesSystem(), pSearchStr, bCreate); - if (iSearchStr == INVALID_KEY_SYMBOL) - { - // not found, couldn't possibly be in key value list - return nullptr; - } - - KeyValues* pLastKVs = nullptr; - KeyValues* pCurrentKVs; - // find the searchStr in the current peer list - for (pCurrentKVs = m_pSub; pCurrentKVs != nullptr; pCurrentKVs = pCurrentKVs->m_pPeer) - { - pLastKVs = pCurrentKVs; // record the last item looked at (for if we need to append to the end of the list) - - // symbol compare - if (pLastKVs->m_iKeyName == (uint32_t)iSearchStr) - break; - } - - if (!pCurrentKVs && m_pChain) - pCurrentKVs = m_pChain->FindKey(pSearchStr, false); - - // make sure a key was found - if (!pCurrentKVs) - { - if (bCreate) - { - // we need to create a new key - pCurrentKVs = new KeyValues(pSearchStr); - // Assert(dat != NULL); - - // insert new key at end of list - if (pLastKVs) - pLastKVs->m_pPeer = pCurrentKVs; - else - m_pSub = pCurrentKVs; - - pCurrentKVs->m_pPeer = nullptr; - - // a key graduates to be a submsg as soon as it's m_pSub is set - // this should be the only place m_pSub is set - m_iDataType = TYPE_NONE; - } - else - { - return nullptr; - } - } - - // if we've still got a subStr we need to keep looking deeper in the tree - if (pSubStr) - { - // recursively chain down through the paths in the string - return pCurrentKVs->FindKey(pSubStr + 1, bCreate); - } - - return pCurrentKVs; -} - -//----------------------------------------------------------------------------- -// Purpose: Locate last child. Returns NULL if we have no children -// Output : *KeyValues -//----------------------------------------------------------------------------- -KeyValues* KeyValues::FindLastSubKey(void) const -{ - // No children? - if (m_pSub == nullptr) - return nullptr; - - // Scan for the last one - KeyValues* pLastChild = m_pSub; - while (pLastChild->m_pPeer) - pLastChild = pLastChild->m_pPeer; - return pLastChild; -} - -//----------------------------------------------------------------------------- -// Purpose: Adds a subkey. Make sure the subkey isn't a child of some other keyvalues -// Input : *pSubKey - -//----------------------------------------------------------------------------- -void KeyValues::AddSubKey(KeyValues* pSubkey) -{ - // Make sure the subkey isn't a child of some other keyvalues - assert(pSubkey != nullptr); - assert(pSubkey->m_pPeer == nullptr); - - // add into subkey list - if (m_pSub == nullptr) - { - m_pSub = pSubkey; - } - else - { - KeyValues* pTempDat = m_pSub; - while (pTempDat->GetNextKey() != nullptr) - { - pTempDat = pTempDat->GetNextKey(); - } - - pTempDat->SetNextKey(pSubkey); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Remove a subkey from the list -// Input : *pSubKey - -//----------------------------------------------------------------------------- -void KeyValues::RemoveSubKey(KeyValues* pSubKey) -{ - if (!pSubKey) - return; - - // check the list pointer - if (m_pSub == pSubKey) - { - m_pSub = pSubKey->m_pPeer; - } - else - { - // look through the list - KeyValues* kv = m_pSub; - while (kv->m_pPeer) - { - if (kv->m_pPeer == pSubKey) - { - kv->m_pPeer = pSubKey->m_pPeer; - break; - } - - kv = kv->m_pPeer; - } - } - - pSubKey->m_pPeer = nullptr; -} - -//----------------------------------------------------------------------------- -// Purpose: Insert a subkey at index -// Input : nIndex - -// *pSubKey - -//----------------------------------------------------------------------------- -void KeyValues::InsertSubKey(int nIndex, KeyValues* pSubKey) -{ - // Sub key must be valid and not part of another chain - assert(pSubKey && pSubKey->m_pPeer == nullptr); - - if (nIndex == 0) - { - pSubKey->m_pPeer = m_pSub; - m_pSub = pSubKey; - return; - } - else - { - int nCurrentIndex = 0; - for (KeyValues* pIter = GetFirstSubKey(); pIter != nullptr; pIter = pIter->GetNextKey()) - { - ++nCurrentIndex; - if (nCurrentIndex == nIndex) - { - pSubKey->m_pPeer = pIter->m_pPeer; - pIter->m_pPeer = pSubKey; - return; - } - } - // Index is out of range if we get here - assert(0); - return; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Checks if key contains a subkey -// Input : *pSubKey - -// Output : true if contains, false otherwise -//----------------------------------------------------------------------------- -bool KeyValues::ContainsSubKey(KeyValues* pSubKey) -{ - for (KeyValues* pIter = GetFirstSubKey(); pIter != nullptr; pIter = pIter->GetNextKey()) - { - if (pSubKey == pIter) - { - return true; - } - } - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Swaps existing subkey with another -// Input : *pExistingSubkey - -// *pNewSubKey - -//----------------------------------------------------------------------------- -void KeyValues::SwapSubKey(KeyValues* pExistingSubkey, KeyValues* pNewSubKey) -{ - assert(pExistingSubkey != nullptr && pNewSubKey != nullptr); - - // Make sure the new sub key isn't a child of some other keyvalues - assert(pNewSubKey->m_pPeer == nullptr); - - // Check the list pointer - if (m_pSub == pExistingSubkey) - { - pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer; - pExistingSubkey->m_pPeer = nullptr; - m_pSub = pNewSubKey; - } - else - { - // Look through the list - KeyValues* kv = m_pSub; - while (kv->m_pPeer) - { - if (kv->m_pPeer == pExistingSubkey) - { - pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer; - pExistingSubkey->m_pPeer = nullptr; - kv->m_pPeer = pNewSubKey; - break; - } - - kv = kv->m_pPeer; - } - // Existing sub key should always be found, otherwise it's a bug in the calling code. - assert(kv->m_pPeer != nullptr); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Elides subkey -// Input : *pSubKey - -//----------------------------------------------------------------------------- -void KeyValues::ElideSubKey(KeyValues* pSubKey) -{ - // This pointer's "next" pointer needs to be fixed up when we elide the key - KeyValues** ppPointerToFix = &m_pSub; - for (KeyValues* pKeyIter = m_pSub; pKeyIter != nullptr; ppPointerToFix = &pKeyIter->m_pPeer, pKeyIter = pKeyIter->GetNextKey()) - { - if (pKeyIter == pSubKey) - { - if (pSubKey->m_pSub == nullptr) - { - // No children, simply remove the key - *ppPointerToFix = pSubKey->m_pPeer; - delete pSubKey; - } - else - { - *ppPointerToFix = pSubKey->m_pSub; - // Attach the remainder of this chain to the last child of pSubKey - KeyValues* pChildIter = pSubKey->m_pSub; - while (pChildIter->m_pPeer != nullptr) - { - pChildIter = pChildIter->m_pPeer; - } - // Now points to the last child of pSubKey - pChildIter->m_pPeer = pSubKey->m_pPeer; - // Detach the node to be elided - pSubKey->m_pSub = nullptr; - pSubKey->m_pPeer = nullptr; - delete pSubKey; - } - return; - } - } - // Key not found; that's caller error. - assert(0); -} - -//----------------------------------------------------------------------------- -// Purpose: Check if a keyName has no value assigned to it. -// Input : *pszKeyName - -// Output : true on success, false otherwise -//----------------------------------------------------------------------------- -bool KeyValues::IsEmpty(const char* pszKeyName) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (!pKey) - return true; - - if (pKey->m_iDataType == TYPE_NONE && pKey->m_pSub == nullptr) - return true; - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: gets the first true sub key -// Output : *KeyValues -//----------------------------------------------------------------------------- -KeyValues* KeyValues::GetFirstTrueSubKey(void) const -{ - assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pSub : nullptr; - while (pRet && pRet->m_iDataType != TYPE_NONE) - pRet = pRet->m_pPeer; - - return pRet; -} - -//----------------------------------------------------------------------------- -// Purpose: gets the next true sub key -// Output : *KeyValues -//----------------------------------------------------------------------------- -KeyValues* KeyValues::GetNextTrueSubKey(void) const -{ - assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pPeer : nullptr; - while (pRet && pRet->m_iDataType != TYPE_NONE) - pRet = pRet->m_pPeer; - - return pRet; -} - -//----------------------------------------------------------------------------- -// Purpose: gets the first value -// Output : *KeyValues -//----------------------------------------------------------------------------- -KeyValues* KeyValues::GetFirstValue(void) const -{ - assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pSub : nullptr; - while (pRet && pRet->m_iDataType == TYPE_NONE) - pRet = pRet->m_pPeer; - - return pRet; -} - -//----------------------------------------------------------------------------- -// Purpose: gets the next value -// Output : *KeyValues -//----------------------------------------------------------------------------- -KeyValues* KeyValues::GetNextValue(void) const -{ - assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pPeer : nullptr; - while (pRet && pRet->m_iDataType == TYPE_NONE) - pRet = pRet->m_pPeer; - - return pRet; -} - -//----------------------------------------------------------------------------- -// Purpose: Return the first subkey in the list -//----------------------------------------------------------------------------- -KeyValues* KeyValues::GetFirstSubKey() const -{ - assert_msg(this, "Member function called on NULL KeyValues"); - return this ? m_pSub : nullptr; -} - -//----------------------------------------------------------------------------- -// Purpose: Return the next subkey -//----------------------------------------------------------------------------- -KeyValues* KeyValues::GetNextKey() const -{ - assert_msg(this, "Member function called on NULL KeyValues"); - return this ? m_pPeer : nullptr; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the name of the current key section -// Output : const char* -//----------------------------------------------------------------------------- -const char* KeyValues::GetName(void) const -{ - return KeyValuesSystem()->m_pVtable->GetStringForSymbol( - KeyValuesSystem(), MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2)); -} - -//----------------------------------------------------------------------------- -// Purpose: Get the integer value of a keyName. Default value is returned -// if the keyName can't be found. -// Input : *pszKeyName - -// nDefaultValue - -// Output : int -//----------------------------------------------------------------------------- -int KeyValues::GetInt(const char* pszKeyName, int iDefaultValue) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - switch (pKey->m_iDataType) - { - case TYPE_STRING: - return atoi(pKey->m_sValue); - case TYPE_WSTRING: - return _wtoi(pKey->m_wsValue); - case TYPE_FLOAT: - return static_cast<int>(pKey->m_flValue); - case TYPE_UINT64: - // can't convert, since it would lose data - assert(0); - return 0; - case TYPE_INT: - case TYPE_PTR: - default: - return pKey->m_iValue; - }; - } - return iDefaultValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the integer value of a keyName. Default value is returned -// if the keyName can't be found. -// Input : *pszKeyName - -// nDefaultValue - -// Output : uint64_t -//----------------------------------------------------------------------------- -uint64_t KeyValues::GetUint64(const char* pszKeyName, uint64_t nDefaultValue) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - switch (pKey->m_iDataType) - { - case TYPE_STRING: - { - uint64_t uiResult = 0ull; - sscanf(pKey->m_sValue, "%lld", &uiResult); - return uiResult; - } - case TYPE_WSTRING: - { - uint64_t uiResult = 0ull; - swscanf(pKey->m_wsValue, L"%lld", &uiResult); - return uiResult; - } - case TYPE_FLOAT: - return static_cast<int>(pKey->m_flValue); - case TYPE_UINT64: - return *reinterpret_cast<uint64_t*>(pKey->m_sValue); - case TYPE_PTR: - return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(pKey->m_pValue)); - case TYPE_INT: - default: - return pKey->m_iValue; - }; - } - return nDefaultValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the pointer value of a keyName. Default value is returned -// if the keyName can't be found. -// Input : *pszKeyName - -// pDefaultValue - -// Output : void* -//----------------------------------------------------------------------------- -void* KeyValues::GetPtr(const char* pszKeyName, void* pDefaultValue) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - switch (pKey->m_iDataType) - { - case TYPE_PTR: - return pKey->m_pValue; - - case TYPE_WSTRING: - case TYPE_STRING: - case TYPE_FLOAT: - case TYPE_INT: - case TYPE_UINT64: - default: - return nullptr; - }; - } - return pDefaultValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the float value of a keyName. Default value is returned -// if the keyName can't be found. -// Input : *pszKeyName - -// flDefaultValue - -// Output : float -//----------------------------------------------------------------------------- -float KeyValues::GetFloat(const char* pszKeyName, float flDefaultValue) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - switch (pKey->m_iDataType) - { - case TYPE_STRING: - return static_cast<float>(atof(pKey->m_sValue)); - case TYPE_WSTRING: - return static_cast<float>(_wtof(pKey->m_wsValue)); // no wtof - case TYPE_FLOAT: - return pKey->m_flValue; - case TYPE_INT: - return static_cast<float>(pKey->m_iValue); - case TYPE_UINT64: - return static_cast<float>((*(reinterpret_cast<uint64_t*>(pKey->m_sValue)))); - case TYPE_PTR: - default: - return 0.0f; - }; - } - return flDefaultValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the string pointer of a keyName. Default value is returned -// if the keyName can't be found. -// // Input : *pszKeyName - -// pszDefaultValue - -// Output : const char* -//----------------------------------------------------------------------------- -const char* KeyValues::GetString(const char* pszKeyName, const char* pszDefaultValue) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - // convert the data to string form then return it - char buf[64]; - switch (pKey->m_iDataType) - { - case TYPE_FLOAT: - snprintf(buf, sizeof(buf), "%f", pKey->m_flValue); - SetString(pszKeyName, buf); - break; - case TYPE_PTR: - snprintf(buf, sizeof(buf), "%lld", reinterpret_cast<uint64_t>(pKey->m_pValue)); - SetString(pszKeyName, buf); - break; - case TYPE_INT: - snprintf(buf, sizeof(buf), "%d", pKey->m_iValue); - SetString(pszKeyName, buf); - break; - case TYPE_UINT64: - snprintf(buf, sizeof(buf), "%lld", *(reinterpret_cast<uint64_t*>(pKey->m_sValue))); - SetString(pszKeyName, buf); - break; - case TYPE_COLOR: - snprintf(buf, sizeof(buf), "%d %d %d %d", pKey->m_Color[0], pKey->m_Color[1], pKey->m_Color[2], pKey->m_Color[3]); - SetString(pszKeyName, buf); - break; - - case TYPE_WSTRING: - { - // convert the string to char *, set it for future use, and return it - char wideBuf[512]; - int result = V_UnicodeToUTF8(pKey->m_wsValue, wideBuf, 512); - if (result) - { - // note: this will copy wideBuf - SetString(pszKeyName, wideBuf); - } - else - { - return pszDefaultValue; - } - break; - } - case TYPE_STRING: - break; - default: - return pszDefaultValue; - }; - - return pKey->m_sValue; - } - return pszDefaultValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the wide string pointer of a keyName. Default value is returned -// if the keyName can't be found. -// // Input : *pszKeyName - -// pwszDefaultValue - -// Output : const wchar_t* -//----------------------------------------------------------------------------- -const wchar_t* KeyValues::GetWString(const char* pszKeyName, const wchar_t* pwszDefaultValue) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - wchar_t wbuf[64]; - switch (pKey->m_iDataType) - { - case TYPE_FLOAT: - swprintf(wbuf, ARRAYSIZE(wbuf), L"%f", pKey->m_flValue); - SetWString(pszKeyName, wbuf); - break; - case TYPE_PTR: - swprintf(wbuf, ARRAYSIZE(wbuf), L"%lld", static_cast<int64_t>(reinterpret_cast<size_t>(pKey->m_pValue))); - SetWString(pszKeyName, wbuf); - break; - case TYPE_INT: - swprintf(wbuf, ARRAYSIZE(wbuf), L"%d", pKey->m_iValue); - SetWString(pszKeyName, wbuf); - break; - case TYPE_UINT64: - { - swprintf(wbuf, ARRAYSIZE(wbuf), L"%lld", *(reinterpret_cast<uint64_t*>(pKey->m_sValue))); - SetWString(pszKeyName, wbuf); - } - break; - case TYPE_COLOR: - swprintf(wbuf, ARRAYSIZE(wbuf), L"%d %d %d %d", pKey->m_Color[0], pKey->m_Color[1], pKey->m_Color[2], pKey->m_Color[3]); - SetWString(pszKeyName, wbuf); - break; - - case TYPE_WSTRING: - break; - case TYPE_STRING: - { - size_t bufSize = strlen(pKey->m_sValue) + 1; - wchar_t* pWBuf = new wchar_t[bufSize]; - int result = V_UTF8ToUnicode(pKey->m_sValue, pWBuf, static_cast<int>(bufSize * sizeof(wchar_t))); - if (result >= 0) // may be a zero length string - { - SetWString(pszKeyName, pWBuf); - delete[] pWBuf; - } - else - { - delete[] pWBuf; - return pwszDefaultValue; - } - - break; - } - default: - return pwszDefaultValue; - }; - - return reinterpret_cast<const wchar_t*>(pKey->m_wsValue); - } - return pwszDefaultValue; -} - -//----------------------------------------------------------------------------- -// Purpose: Gets a color -// Input : *pszKeyName - -// &defaultColor - -// Output : Color -//----------------------------------------------------------------------------- -Color KeyValues::GetColor(const char* pszKeyName, const Color& defaultColor) -{ - Color color = defaultColor; - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - { - if (pKey->m_iDataType == TYPE_COLOR) - { - color[0] = pKey->m_Color[0]; - color[1] = pKey->m_Color[1]; - color[2] = pKey->m_Color[2]; - color[3] = pKey->m_Color[3]; - } - else if (pKey->m_iDataType == TYPE_FLOAT) - { - color[0] = static_cast<unsigned char>(pKey->m_flValue); - } - else if (pKey->m_iDataType == TYPE_INT) - { - color[0] = static_cast<unsigned char>(pKey->m_iValue); - } - else if (pKey->m_iDataType == TYPE_STRING) - { - // parse the colors out of the string - float a = 0, b = 0, c = 0, d = 0; - sscanf(pKey->m_sValue, "%f %f %f %f", &a, &b, &c, &d); - color[0] = static_cast<unsigned char>(a); - color[1] = static_cast<unsigned char>(b); - color[2] = static_cast<unsigned char>(c); - color[3] = static_cast<unsigned char>(d); - } - } - return color; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the data type of the value stored in a keyName -// Input : *pszKeyName - -//----------------------------------------------------------------------------- -KeyValuesTypes_t KeyValues::GetDataType(const char* pszKeyName) -{ - KeyValues* pKey = FindKey(pszKeyName, false); - if (pKey) - return static_cast<KeyValuesTypes_t>(pKey->m_iDataType); - - return TYPE_NONE; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the data type of the value stored in this keyName -//----------------------------------------------------------------------------- -KeyValuesTypes_t KeyValues::GetDataType(void) const -{ - return static_cast<KeyValuesTypes_t>(m_iDataType); -} - -//----------------------------------------------------------------------------- -// Purpose: Set the integer value of a keyName. -// Input : *pszKeyName - -// iValue - -//----------------------------------------------------------------------------- -void KeyValues::SetInt(const char* pszKeyName, int iValue) -{ - KeyValues* pKey = FindKey(pszKeyName, true); - if (pKey) - { - pKey->m_iValue = iValue; - pKey->m_iDataType = TYPE_INT; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Set the integer value of a keyName. -//----------------------------------------------------------------------------- -void KeyValues::SetUint64(const char* pszKeyName, uint64_t nValue) -{ - KeyValues* pKey = FindKey(pszKeyName, true); - - if (pKey) - { - // delete the old value - delete[] pKey->m_sValue; - // make sure we're not storing the WSTRING - as we're converting over to STRING - delete[] pKey->m_wsValue; - pKey->m_wsValue = nullptr; - - pKey->m_sValue = new char[sizeof(uint64_t)]; - *(reinterpret_cast<uint64_t*>(pKey->m_sValue)) = nValue; - pKey->m_iDataType = TYPE_UINT64; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Set the float value of a keyName. -// Input : *pszKeyName - -// flValue - -//----------------------------------------------------------------------------- -void KeyValues::SetFloat(const char* pszKeyName, float flValue) -{ - KeyValues* pKey = FindKey(pszKeyName, true); - if (pKey) - { - pKey->m_flValue = flValue; - pKey->m_iDataType = TYPE_FLOAT; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Set the name value of a keyName. -// Input : *pszSetName - -//----------------------------------------------------------------------------- -void KeyValues::SetName(const char* pszSetName) -{ - HKeySymbol hCaseSensitiveKeyName = INVALID_KEY_SYMBOL, hCaseInsensitiveKeyName = INVALID_KEY_SYMBOL; - hCaseSensitiveKeyName = - KeyValuesSystem()->m_pVtable->GetSymbolForStringCaseSensitive(KeyValuesSystem(), hCaseInsensitiveKeyName, pszSetName, false); - - m_iKeyName = hCaseInsensitiveKeyName; - SPLIT_3_BYTES_INTO_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2, hCaseSensitiveKeyName); -} - -//----------------------------------------------------------------------------- -// Purpose: Set the pointer value of a keyName. -// Input : *pszKeyName - -// *pValue - -//----------------------------------------------------------------------------- -void KeyValues::SetPtr(const char* pszKeyName, void* pValue) -{ - KeyValues* pKey = FindKey(pszKeyName, true); - - if (pKey) - { - pKey->m_pValue = pValue; - pKey->m_iDataType = TYPE_PTR; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Set the string value (internal) -// Input : *pszValue - -//----------------------------------------------------------------------------- -void KeyValues::SetStringValue(char const* pszValue) -{ - // delete the old value - delete[] m_sValue; - // make sure we're not storing the WSTRING - as we're converting over to STRING - delete[] m_wsValue; - m_wsValue = nullptr; - - if (!pszValue) - { - // ensure a valid value - pszValue = ""; - } - - // allocate memory for the new value and copy it in - size_t len = strlen(pszValue); - m_sValue = new char[len + 1]; - memcpy(m_sValue, pszValue, len + 1); - - m_iDataType = TYPE_STRING; -} - -//----------------------------------------------------------------------------- -// Purpose: Sets this key's peer to the KeyValues passed in -// Input : *pDat - -//----------------------------------------------------------------------------- -void KeyValues::SetNextKey(KeyValues* pDat) -{ - m_pPeer = pDat; -} - -//----------------------------------------------------------------------------- -// Purpose: Set the string value of a keyName. -// Input : *pszKeyName - -// *pszValue - -//----------------------------------------------------------------------------- -void KeyValues::SetString(const char* pszKeyName, const char* pszValue) -{ - if (KeyValues* pKey = FindKey(pszKeyName, true)) - { - pKey->SetStringValue(pszValue); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Set the string value of a keyName. -// Input : *pszKeyName - -// *pwszValue - -//----------------------------------------------------------------------------- -void KeyValues::SetWString(const char* pszKeyName, const wchar_t* pwszValue) -{ - KeyValues* pKey = FindKey(pszKeyName, true); - if (pKey) - { - // delete the old value - delete[] pKey->m_wsValue; - // make sure we're not storing the STRING - as we're converting over to WSTRING - delete[] pKey->m_sValue; - pKey->m_sValue = nullptr; - - if (!pwszValue) - { - // ensure a valid value - pwszValue = L""; - } - - // allocate memory for the new value and copy it in - size_t len = wcslen(pwszValue); - pKey->m_wsValue = new wchar_t[len + 1]; - memcpy(pKey->m_wsValue, pwszValue, (len + 1) * sizeof(wchar_t)); - - pKey->m_iDataType = TYPE_WSTRING; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Sets a color -// Input : *pszKeyName - -// color - -//----------------------------------------------------------------------------- -void KeyValues::SetColor(const char* pszKeyName, Color color) -{ - KeyValues* pKey = FindKey(pszKeyName, true); - - if (pKey) - { - pKey->m_iDataType = TYPE_COLOR; - pKey->m_Color[0] = color[0]; - pKey->m_Color[1] = color[1]; - pKey->m_Color[2] = color[2]; - pKey->m_Color[3] = color[3]; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &src - -//----------------------------------------------------------------------------- -void KeyValues::RecursiveCopyKeyValues(KeyValues& src) -{ - // garymcthack - need to check this code for possible buffer overruns. - - m_iKeyName = src.m_iKeyName; - m_iKeyNameCaseSensitive1 = src.m_iKeyNameCaseSensitive1; - m_iKeyNameCaseSensitive2 = src.m_iKeyNameCaseSensitive2; - - if (!src.m_pSub) - { - m_iDataType = src.m_iDataType; - char buf[256]; - switch (src.m_iDataType) - { - case TYPE_NONE: - break; - case TYPE_STRING: - if (src.m_sValue) - { - size_t len = strlen(src.m_sValue) + 1; - m_sValue = new char[len]; - strncpy(m_sValue, src.m_sValue, len); - } - break; - case TYPE_INT: - { - m_iValue = src.m_iValue; - snprintf(buf, sizeof(buf), "%d", m_iValue); - size_t len = strlen(buf) + 1; - m_sValue = new char[len]; - strncpy(m_sValue, buf, len); - } - break; - case TYPE_FLOAT: - { - m_flValue = src.m_flValue; - snprintf(buf, sizeof(buf), "%f", m_flValue); - size_t len = strlen(buf) + 1; - m_sValue = new char[len]; - strncpy(m_sValue, buf, len); - } - break; - case TYPE_PTR: - { - m_pValue = src.m_pValue; - } - break; - case TYPE_UINT64: - { - m_sValue = new char[sizeof(uint64_t)]; - memcpy(m_sValue, src.m_sValue, sizeof(uint64_t)); - } - break; - case TYPE_COLOR: - { - m_Color[0] = src.m_Color[0]; - m_Color[1] = src.m_Color[1]; - m_Color[2] = src.m_Color[2]; - m_Color[3] = src.m_Color[3]; - } - break; - - default: - { - // do nothing . .what the heck is this? - assert(0); - } - break; - } - } - - // Handle the immediate child - if (src.m_pSub) - { - m_pSub = new KeyValues; - - m_pSub->Init(); - m_pSub->SetName(nullptr); - - m_pSub->RecursiveCopyKeyValues(*src.m_pSub); - } - - // Handle the immediate peer - if (src.m_pPeer) - { - m_pPeer = new KeyValues; - - m_pPeer->Init(); - m_pPeer->SetName(nullptr); - - m_pPeer->RecursiveCopyKeyValues(*src.m_pPeer); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Make a new copy of all subkeys, add them all to the passed-in keyvalues -// Input : *pParent - -//----------------------------------------------------------------------------- -void KeyValues::CopySubkeys(KeyValues* pParent) const -{ - // recursively copy subkeys - // Also maintain ordering.... - KeyValues* pPrev = nullptr; - for (KeyValues* pSub = m_pSub; pSub != nullptr; pSub = pSub->m_pPeer) - { - // take a copy of the subkey - KeyValues* pKey = pSub->MakeCopy(); - - // add into subkey list - if (pPrev) - { - pPrev->m_pPeer = pKey; - } - else - { - pParent->m_pSub = pKey; - } - pKey->m_pPeer = nullptr; - pPrev = pKey; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Makes a copy of the whole key-value pair set -// Output : KeyValues* -//----------------------------------------------------------------------------- -KeyValues* KeyValues::MakeCopy(void) const -{ - KeyValues* pNewKeyValue = new KeyValues; - - pNewKeyValue->Init(); - pNewKeyValue->SetName(GetName()); - - // copy data - pNewKeyValue->m_iDataType = m_iDataType; - switch (m_iDataType) - { - case TYPE_STRING: - { - if (m_sValue) - { - size_t len = strlen(m_sValue); - assert(!pNewKeyValue->m_sValue); - pNewKeyValue->m_sValue = new char[len + 1]; - memcpy(pNewKeyValue->m_sValue, m_sValue, len + 1); - } - } - break; - case TYPE_WSTRING: - { - if (m_wsValue) - { - size_t len = wcslen(m_wsValue); - pNewKeyValue->m_wsValue = new wchar_t[len + 1]; - memcpy(pNewKeyValue->m_wsValue, m_wsValue, len + 1 * sizeof(wchar_t)); - } - } - break; - - case TYPE_INT: - pNewKeyValue->m_iValue = m_iValue; - break; - - case TYPE_FLOAT: - pNewKeyValue->m_flValue = m_flValue; - break; - - case TYPE_PTR: - pNewKeyValue->m_pValue = m_pValue; - break; - - case TYPE_COLOR: - pNewKeyValue->m_Color[0] = m_Color[0]; - pNewKeyValue->m_Color[1] = m_Color[1]; - pNewKeyValue->m_Color[2] = m_Color[2]; - pNewKeyValue->m_Color[3] = m_Color[3]; - break; - - case TYPE_UINT64: - pNewKeyValue->m_sValue = new char[sizeof(uint64_t)]; - memcpy(pNewKeyValue->m_sValue, m_sValue, sizeof(uint64_t)); - break; - }; - - // recursively copy subkeys - CopySubkeys(pNewKeyValue); - return pNewKeyValue; -} - -ON_DLL_LOAD("vstdlib.dll", KeyValues, (CModule module)) -{ - V_UTF8ToUnicode = module.GetExport("V_UTF8ToUnicode").RCast<int (*)(const char*, wchar_t*, int)>(); - V_UnicodeToUTF8 = module.GetExport("V_UnicodeToUTF8").RCast<int (*)(const wchar_t*, char*, int)>(); - KeyValuesSystem = module.GetExport("KeyValuesSystem").RCast<CKeyValuesSystem* (*)()>(); -} - -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30, -char, __fastcall, (KeyValues* self, const char* pResourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7)) -// clang-format on -{ - static void* pSavedFilesystemPtr = nullptr; - - // this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of - // LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it - // from a function call that always happens before playlists is loaded - - // note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos - if (pFileSystem != nullptr) - pSavedFilesystemPtr = pFileSystem; - if (!pFileSystem && !strcmp(pResourceName, "playlists")) - pFileSystem = pSavedFilesystemPtr; - - return KeyValues__LoadFromBuffer(self, pResourceName, pBuffer, pFileSystem, a5, a6, a7); -} - -ON_DLL_LOAD("engine.dll", EngineKeyValues, (CModule module)) -{ - AUTOHOOK_DISPATCH() -} diff --git a/NorthstarDLL/shared/keyvalues.h b/NorthstarDLL/shared/keyvalues.h deleted file mode 100644 index bd62797e..00000000 --- a/NorthstarDLL/shared/keyvalues.h +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once -#include "core/math/color.h" - -enum KeyValuesTypes_t : char -{ - TYPE_NONE = 0x0, - TYPE_STRING = 0x1, - TYPE_INT = 0x2, - TYPE_FLOAT = 0x3, - TYPE_PTR = 0x4, - TYPE_WSTRING = 0x5, - TYPE_COLOR = 0x6, - TYPE_UINT64 = 0x7, - TYPE_COMPILED_INT_BYTE = 0x8, - TYPE_COMPILED_INT_0 = 0x9, - TYPE_COMPILED_INT_1 = 0xA, - TYPE_NUMTYPES = 0xB, -}; - -enum MergeKeyValuesOp_t -{ - MERGE_KV_ALL, - MERGE_KV_UPDATE, // update values are copied into storage, adding new keys to storage or updating existing ones - MERGE_KV_DELETE, // update values specify keys that get deleted from storage - MERGE_KV_BORROW, // update values only update existing keys in storage, keys in update that do not exist in storage are discarded -}; - -//----------------------------------------------------------------------------- -// Purpose: Simple recursive data access class -// Used in vgui for message parameters and resource files -// Destructor deletes all child KeyValues nodes -// Data is stored in key (string names) - (string/int/float)value pairs called nodes. -// -// About KeyValues Text File Format: - -// It has 3 control characters '{', '}' and '"'. Names and values may be quoted or -// not. The quote '"' character must not be used within name or values, only for -// quoting whole tokens. You may use escape sequences wile parsing and add within a -// quoted token a \" to add quotes within your name or token. When using Escape -// Sequence the parser must now that by setting KeyValues::UsesEscapeSequences( true ), -// which it's off by default. Non-quoted tokens ends with a whitespace, '{', '}' and '"'. -// So you may use '{' and '}' within quoted tokens, but not for non-quoted tokens. -// An open bracket '{' after a key name indicates a list of subkeys which is finished -// with a closing bracket '}'. Subkeys use the same definitions recursively. -// Whitespaces are space, return, newline and tabulator. Allowed Escape sequences -// are \n, \t, \\, \n and \". The number character '#' is used for macro purposes -// (eg #include), don't use it as first character in key names. -//----------------------------------------------------------------------------- -class KeyValues -{ -private: - KeyValues(); // for internal use only - -public: - // Constructors/destructors - KeyValues(const char* pszSetName); - KeyValues(const char* pszSetName, const char* pszFirstKey, const char* pszFirstValue); - KeyValues(const char* pszSetName, const char* pszFirstKey, const wchar_t* pwszFirstValue); - KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue); - KeyValues( - const char* pszSetName, const char* pszFirstKey, const char* pszFirstValue, const char* pszSecondKey, const char* pszSecondValue); - KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue, const char* pszSecondKey, int iSecondValue); - ~KeyValues(void); - - void Init(void); - void Clear(void); - void DeleteThis(void); - void RemoveEverything(); - - KeyValues* FindKey(const char* pKeyName, bool bCreate = false); - KeyValues* FindLastSubKey(void) const; - - void AddSubKey(KeyValues* pSubkey); - void RemoveSubKey(KeyValues* pSubKey); - void InsertSubKey(int nIndex, KeyValues* pSubKey); - bool ContainsSubKey(KeyValues* pSubKey); - void SwapSubKey(KeyValues* pExistingSubkey, KeyValues* pNewSubKey); - void ElideSubKey(KeyValues* pSubKey); - - // Data access - bool IsEmpty(const char* pszKeyName); - KeyValues* GetFirstTrueSubKey(void) const; - KeyValues* GetNextTrueSubKey(void) const; - KeyValues* GetFirstValue(void) const; - KeyValues* GetNextValue(void) const; - KeyValues* GetFirstSubKey() const; - KeyValues* GetNextKey() const; - const char* GetName(void) const; - int GetInt(const char* pszKeyName, int iDefaultValue); - uint64_t GetUint64(const char* pszKeyName, uint64_t nDefaultValue); - void* GetPtr(const char* pszKeyName, void* pDefaultValue); - float GetFloat(const char* pszKeyName, float flDefaultValue); - const char* GetString(const char* pszKeyName = nullptr, const char* pszDefaultValue = ""); - const wchar_t* GetWString(const char* pszKeyName = nullptr, const wchar_t* pwszDefaultValue = L""); - Color GetColor(const char* pszKeyName, const Color& defaultColor); - KeyValuesTypes_t GetDataType(const char* pszKeyName); - KeyValuesTypes_t GetDataType(void) const; - - // Key writing - void SetInt(const char* pszKeyName, int iValue); - void SetUint64(const char* pszKeyName, uint64_t nValue); - void SetPtr(const char* pszKeyName, void* pValue); - void SetNextKey(KeyValues* pDat); - void SetName(const char* pszName); - void SetString(const char* pszKeyName, const char* pszValue); - void SetWString(const char* pszKeyName, const wchar_t* pwszValue); - void SetStringValue(char const* pszValue); - void SetColor(const char* pszKeyName, Color color); - void SetFloat(const char* pszKeyName, float flValue); - - void RecursiveCopyKeyValues(KeyValues& src); - void CopySubkeys(KeyValues* pParent) const; - KeyValues* MakeCopy(void) const; - -public: - uint32_t m_iKeyName : 24; // 0x0000 - uint32_t m_iKeyNameCaseSensitive1 : 8; // 0x0003 - char* m_sValue; // 0x0008 - wchar_t* m_wsValue; // 0x0010 - union // 0x0018 - { - int m_iValue; - float m_flValue; - void* m_pValue; - unsigned char m_Color[4]; - }; - char m_szShortName[8]; // 0x0020 - char m_iDataType; // 0x0028 - char m_bHasEscapeSequences; // 0x0029 - uint16_t m_iKeyNameCaseSensitive2; // 0x002A - KeyValues* m_pPeer; // 0x0030 - KeyValues* m_pSub; // 0x0038 - KeyValues* m_pChain; // 0x0040 -}; diff --git a/NorthstarDLL/shared/maxplayers.cpp b/NorthstarDLL/shared/maxplayers.cpp deleted file mode 100644 index 711193d4..00000000 --- a/NorthstarDLL/shared/maxplayers.cpp +++ /dev/null @@ -1,640 +0,0 @@ -#include "core/tier0.h" -#include "maxplayers.h" - -AUTOHOOK_INIT() - -// never set this to anything below 32 -#define NEW_MAX_PLAYERS 64 -// dg note: the theoretical limit is actually 100, 76 works without entity issues, and 64 works without clientside prediction issues. - -#define PAD_NUMBER(number, boundary) (((number) + ((boundary)-1)) / (boundary)) * (boundary) - -// this is horrible -constexpr int PlayerResource_Name_Start = 0; // Start of modded allocated space. -constexpr int PlayerResource_Name_Size = ((NEW_MAX_PLAYERS + 1) * 8); // const char* m_szName[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_Ping_Start = PlayerResource_Name_Start + PlayerResource_Name_Size; -constexpr int PlayerResource_Ping_Size = ((NEW_MAX_PLAYERS + 1) * 4); // int m_iPing[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_Team_Start = PlayerResource_Ping_Start + PlayerResource_Ping_Size; -constexpr int PlayerResource_Team_Size = ((NEW_MAX_PLAYERS + 1) * 4); // int m_iTeam[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_PRHealth_Start = PlayerResource_Team_Start + PlayerResource_Team_Size; -constexpr int PlayerResource_PRHealth_Size = ((NEW_MAX_PLAYERS + 1) * 4); // int m_iPRHealth[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_Connected_Start = PlayerResource_PRHealth_Start + PlayerResource_PRHealth_Size; -constexpr int PlayerResource_Connected_Size = ((NEW_MAX_PLAYERS + 1) * 4); // int (used as a bool) m_bConnected[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_Alive_Start = PlayerResource_Connected_Start + PlayerResource_Connected_Size; -constexpr int PlayerResource_Alive_Size = ((NEW_MAX_PLAYERS + 1) * 4); // int (used as a bool) m_bAlive[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_BoolStats_Start = PlayerResource_Alive_Start + PlayerResource_Alive_Size; -constexpr int PlayerResource_BoolStats_Size = ((NEW_MAX_PLAYERS + 1) * 4); // int (used as a bool idk) m_boolStats[MAX_PLAYERS + 1]; - -constexpr int PlayerResource_KillStats_Start = PlayerResource_BoolStats_Start + PlayerResource_BoolStats_Size; -constexpr int PlayerResource_KillStats_Length = PAD_NUMBER((NEW_MAX_PLAYERS + 1) * 6, 4); -constexpr int PlayerResource_KillStats_Size = (PlayerResource_KillStats_Length * 6); // int m_killStats[MAX_PLAYERS + 1][6]; - -constexpr int PlayerResource_ScoreStats_Start = PlayerResource_KillStats_Start + PlayerResource_KillStats_Size; -constexpr int PlayerResource_ScoreStats_Length = PAD_NUMBER((NEW_MAX_PLAYERS + 1) * 5, 4); -constexpr int PlayerResource_ScoreStats_Size = (PlayerResource_ScoreStats_Length * 4); // int m_scoreStats[MAX_PLAYERS + 1][5]; - -// must be the usage of the last field to account for any possible paddings -constexpr int PlayerResource_TotalSize = PlayerResource_ScoreStats_Start + PlayerResource_ScoreStats_Size; - -constexpr int Team_PlayerArray_AddedLength = NEW_MAX_PLAYERS - 32; -constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4); -constexpr int Team_AddedSize = Team_PlayerArray_AddedSize; - -bool MaxPlayersIncreaseEnabled() -{ - static bool bMaxPlayersIncreaseEnabled = CommandLine()->CheckParm("-experimentalmaxplayersincrease"); - return bMaxPlayersIncreaseEnabled; -} - -int GetMaxPlayers() -{ - if (MaxPlayersIncreaseEnabled()) - return NEW_MAX_PLAYERS; - - return 32; -} - -template <class T> void ChangeOffset(CMemoryAddress addr, unsigned int offset) -{ - addr.Patch((BYTE*)&offset, sizeof(T)); -} - -// clang-format off -AUTOHOOK(StringTables_CreateStringTable, engine.dll + 0x22E220, -void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags)) -// clang-format on -{ - // Change the amount of entries to account for a bigger player amount - if (!strcmp(name, "userinfo")) - { - int maxPlayersPowerOf2 = 1; - while (maxPlayersPowerOf2 < NEW_MAX_PLAYERS) - maxPlayersPowerOf2 <<= 1; - - maxentries = maxPlayersPowerOf2; - } - - return StringTables_CreateStringTable(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags); -} - -ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module)) -{ - if (!MaxPlayersIncreaseEnabled()) - return; - - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - // patch GetPlayerLimits to ignore the boundary limit - module.Offset(0x116458).Patch("0xEB"); // jle => jmp - - // patch ED_Alloc to change nFirstIndex - ChangeOffset<int>(module.Offset(0x18F46C + 1), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1) - - // patch CGameServer::SpawnServer to change GetMaxClients inline - ChangeOffset<int>(module.Offset(0x119543 + 2), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1) - - // patch CGameServer::SpawnServer to change for loop - ChangeOffset<unsigned char>(module.Offset(0x11957F + 2), NEW_MAX_PLAYERS); // original: 32 - - // patch CGameServer::SpawnServer to change for loop (there are two) - ChangeOffset<unsigned char>(module.Offset(0x119586 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1) - - // patch max players somewhere in CClientState - ChangeOffset<unsigned char>(module.Offset(0x1A162C + 2), NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1) - - // patch max players in userinfo stringtable creation - /*{ - int maxPlayersPowerOf2 = 1; - while (maxPlayersPowerOf2 < NEW_MAX_PLAYERS) - maxPlayersPowerOf2 <<= 1; - ChangeOffset<unsigned char>((char*)baseAddress + 0x114B79 + 3, maxPlayersPowerOf2); // original: 32 - }*/ - // this is not supposed to work at all but it does on 64 players (how) - // proper fix below - - // patch max players in userinfo stringtable creation loop - ChangeOffset<unsigned char>(module.Offset(0x114C48 + 2), NEW_MAX_PLAYERS); // original: 32 - - // do not load prebaked SendTable message list - module.Offset(0x75859).Patch("EB"); // jnz -> jmp -} - -typedef void (*RunUserCmds_Type)(bool a1, float a2); -RunUserCmds_Type RunUserCmds_Original; - -HMODULE serverBase = 0; -auto RandomIntZeroMax = (__int64(__fastcall*)())0; - -// lazy rebuild -// clang-format off -AUTOHOOK(RunUserCmds, server.dll + 0x483D10, -void,, (bool a1, float a2)) -// clang-format on -{ - unsigned char v3; // bl - int v5; // er14 - int i; // edi - __int64 v7; // rax - DWORD* v8; // rbx - int v9; // edi - __int64* v10; // rsi - __int64 v11; // rax - int v12; // er12 - __int64 v13; // rdi - int v14; // ebx - int v15; // eax - __int64 v16; // r8 - int v17; // edx - char v18; // r15 - char v19; // bp - int v20; // esi - __int64* v21; // rdi - __int64 v22; // rcx - bool v23; // al - __int64 v24; // rax - __int64 v25[NEW_MAX_PLAYERS]; // [rsp+20h] [rbp-138h] BYREF - - uintptr_t base = (__int64)serverBase; - auto g_pGlobals = *(__int64*)(base + 0xBFBE08); - __int64 globals = g_pGlobals; - - auto g_pEngineServer = *(__int64*)(base + 0xBFBD98); - - auto qword_1814D9648 = *(__int64*)(base + 0x14D9648); - auto qword_1814DA408 = *(__int64*)(base + 0x14DA408); - auto qword_1812107E8 = *(__int64*)(base + 0x12107E8); - auto qword_1812105A8 = *(__int64*)(base + 0x12105A8); - - auto UTIL_PlayerByIndex = (__int64(__fastcall*)(int index))(base + 0x26AA10); - auto sub_180485590 = (void(__fastcall*)(__int64))(base + 0x485590); - auto sub_18058CD80 = (void(__fastcall*)(__int64))(base + 0x58CD80); - auto sub_1805A6D90 = (void(__fastcall*)(__int64))(base + 0x5A6D90); - auto sub_1805A6E50 = (bool(__fastcall*)(__int64, int, char))(base + 0x5A6E50); - auto sub_1805A6C20 = (void(__fastcall*)(__int64))(base + 0x5A6C20); - - v3 = *(unsigned char*)(g_pGlobals + 73); - if (*(DWORD*)(qword_1814D9648 + 92) && - ((*(unsigned __int8(__fastcall**)(__int64))(*(__int64*)g_pEngineServer + 32))(g_pEngineServer) || - !*(DWORD*)(qword_1814DA408 + 92)) && - v3) - { - globals = g_pGlobals; - v5 = 1; - for (i = 1; i <= *(DWORD*)(g_pGlobals + 52); ++i) - { - v7 = UTIL_PlayerByIndex(i); - v8 = (DWORD*)v7; - if (v7) - { - *(__int64*)(base + 0x1210420) = v7; - *(float*)(g_pGlobals + 16) = a2; - if (!a1) - sub_18058CD80(v7); - sub_1805A6D90((__int64)v8); - } - globals = g_pGlobals; - } - memset(v25, 0, sizeof(v25)); - v9 = 0; - if (*(int*)(globals + 52) > 0) - { - v10 = v25; - do - { - v11 = UTIL_PlayerByIndex(++v9); - globals = g_pGlobals; - *v10++ = v11; - } while (v9 < *(DWORD*)(globals + 52)); - } - v12 = *(DWORD*)(qword_1812107E8 + 92); - if (*(DWORD*)(qword_1812105A8 + 92)) - { - v13 = *(DWORD*)(globals + 52) - 1; - if (v13 >= 1) - { - v14 = *(DWORD*)(globals + 52); - do - { - v15 = RandomIntZeroMax(); - v16 = v25[v13--]; - v17 = v15 % v14--; - v25[v13 + 1] = v25[v17]; - v25[v17] = v16; - } while (v13 >= 1); - globals = g_pGlobals; - } - } - v18 = 1; - do - { - v19 = 0; - v20 = 0; - if (*(int*)(globals + 52) > 0) - { - v21 = v25; - do - { - v22 = *v21; - if (*v21) - { - *(__int64*)(base + 0x1210420) = *v21; - *(float*)(globals + 16) = a2; - v23 = sub_1805A6E50(v22, v12, v18); - globals = g_pGlobals; - if (v23) - v19 = 1; - else - *v21 = 0; - } - ++v20; - ++v21; - } while (v20 < *(DWORD*)(globals + 52)); - } - v18 = 0; - } while (v19); - if (*(int*)(globals + 52) >= 1) - { - do - { - v24 = UTIL_PlayerByIndex(v5); - if (v24) - { - *(__int64*)(base + 0x1210420) = v24; - *(float*)(g_pGlobals + 16) = a2; - sub_1805A6C20(v24); - } - ++v5; - } while (v5 <= *(DWORD*)(g_pGlobals + 52)); - } - sub_180485590(*(__int64*)(base + 0xB7B2D8)); - } -} - -// clang-format off -AUTOHOOK(SendPropArray2, server.dll + 0x12B130, -__int64,, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)) -// clang-format on -{ - // Change the amount of elements to account for a bigger player amount - if (!strcmp(name, "\"player_array\"")) - elements = NEW_MAX_PLAYERS; - - return SendPropArray2(recvProp, elements, flags, name, proxyFn, unk1); -} - -ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module)) -{ - if (!MaxPlayersIncreaseEnabled()) - return; - - AUTOHOOK_DISPATCH_MODULE(server.dll) - - // get required data - serverBase = (HMODULE)module.m_nAddress; - RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax")); - - // patch max players amount - ChangeOffset<unsigned char>(module.Offset(0x9A44D + 3), NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128) - - // patch SpawnGlobalNonRewinding to change forced edict index - ChangeOffset<unsigned char>(module.Offset(0x2BC403 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1) - - constexpr int CPlayerResource_OriginalSize = 4776; - constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize; - constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize; - - // CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data - ChangeOffset<unsigned int>(module.Offset(0x5C560A + 1), CPlayerResource_ModifiedSize); - - // DT_PlayerResource::m_iPing SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_iPing DataMap - ChangeOffset<unsigned int>(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned short>(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1); - ChangeOffset<unsigned short>(module.Offset(0xB945C0), PlayerResource_Ping_Size); - - // DT_PlayerResource::m_iTeam SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_iTeam DataMap - ChangeOffset<unsigned int>(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned short>(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1); - ChangeOffset<unsigned short>(module.Offset(0xB94628), PlayerResource_Team_Size); - - // DT_PlayerResource::m_iPRHealth SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_iPRHealth DataMap - ChangeOffset<unsigned int>(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned short>(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1); - ChangeOffset<unsigned short>(module.Offset(0xB94690), PlayerResource_PRHealth_Size); - - // DT_PlayerResource::m_bConnected SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_bConnected DataMap - ChangeOffset<unsigned int>(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned short>(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1); - ChangeOffset<unsigned short>(module.Offset(0xB946F8), PlayerResource_Connected_Size); - - // DT_PlayerResource::m_bAlive SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_bAlive DataMap - ChangeOffset<unsigned int>(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned short>(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1); - ChangeOffset<unsigned short>(module.Offset(0xB94760), PlayerResource_Alive_Size); - - // DT_PlayerResource::m_boolStats SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_boolStats DataMap - ChangeOffset<unsigned int>(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned short>(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1); - ChangeOffset<unsigned short>(module.Offset(0xB947C8), PlayerResource_BoolStats_Size); - - // DT_PlayerResource::m_killStats SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length); - - // DT_PlayerResource::m_killStats DataMap - ChangeOffset<unsigned int>(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned short>(module.Offset(0xB9480C), PlayerResource_KillStats_Length); - ChangeOffset<unsigned short>(module.Offset(0xB94830), PlayerResource_KillStats_Size); - - // DT_PlayerResource::m_scoreStats SendProp - ChangeOffset<unsigned int>(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length); - - // DT_PlayerResource::m_scoreStats DataMap - ChangeOffset<unsigned int>(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned short>(module.Offset(0xB94874), PlayerResource_ScoreStats_Length); - ChangeOffset<unsigned short>(module.Offset(0xB94898), PlayerResource_ScoreStats_Size); - - // CPlayerResource::UpdatePlayerData - m_bConnected - ChangeOffset<unsigned int>(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - - // CPlayerResource::UpdatePlayerData - m_iPing - ChangeOffset<unsigned int>(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - - // CPlayerResource::UpdatePlayerData - m_iTeam - ChangeOffset<unsigned int>(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start); - - // CPlayerResource::UpdatePlayerData - m_iPRHealth - ChangeOffset<unsigned int>(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - - // CPlayerResource::UpdatePlayerData - m_bConnected - ChangeOffset<unsigned int>(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - - // CPlayerResource::UpdatePlayerData - m_bAlive - ChangeOffset<unsigned int>(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - - // CPlayerResource::UpdatePlayerData - m_boolStats - ChangeOffset<unsigned int>(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - - // CPlayerResource::UpdatePlayerData - m_scoreStats - ChangeOffset<unsigned int>(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - - // CPlayerResource::UpdatePlayerData - m_killStats - ChangeOffset<unsigned int>(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - - *module.Offset(0x14E7390).RCast<DWORD*>() = 0; - auto DT_PlayerResource_Construct = module.Offset(0x5C4FE0).RCast<__int64(__fastcall*)()>(); - DT_PlayerResource_Construct(); - - constexpr int CTeam_OriginalSize = 3336; - constexpr int CTeam_AddedSize = Team_AddedSize; - constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize; - - // CTeam class allocation function - allocate a bigger amount to fit all new team player data - ChangeOffset<unsigned int>(module.Offset(0x23924A + 1), CTeam_ModifiedSize); - - // CTeam::CTeam - increase memset length to clean newly allocated data - ChangeOffset<unsigned int>(module.Offset(0x2395AE + 2), 256 + CTeam_AddedSize); - - *module.Offset(0xC945A0).RCast<DWORD*>() = 0; - auto DT_Team_Construct = module.Offset(0x238F50).RCast<__int64(__fastcall*)()>(); - DT_Team_Construct(); -} - -// clang-format off -AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0, -__int64,, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)) -// clang-format on -{ - // Change the amount of elements to account for a bigger player amount - if (!strcmp(name, "\"player_array\"")) - elements = NEW_MAX_PLAYERS; - - return RecvPropArray2(recvProp, elements, flags, name, proxyFn); -} - -ON_DLL_LOAD("client.dll", MaxPlayersOverride_Client, (CModule module)) -{ - if (!MaxPlayersIncreaseEnabled()) - return; - - AUTOHOOK_DISPATCH_MODULE(client.dll) - - constexpr int C_PlayerResource_OriginalSize = 5768; - constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize; - constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize; - - // C_PlayerResource class allocation function - allocate a bigger amount to fit all new max player data - ChangeOffset<unsigned int>(module.Offset(0x164C41 + 1), C_PlayerResource_ModifiedSize); - - // C_PlayerResource::C_PlayerResource - change loop end value - ChangeOffset<unsigned char>(module.Offset(0x1640C4 + 2), NEW_MAX_PLAYERS - 32); - - // C_PlayerResource::C_PlayerResource - change m_szName address - ChangeOffset<unsigned int>( - module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class - - // C_PlayerResource::C_PlayerResource - change m_szName address - ChangeOffset<unsigned int>( - module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class - - // C_PlayerResource::C_PlayerResource - increase memset length to clean newly allocated data - ChangeOffset<unsigned int>(module.Offset(0x1640D0 + 3), 2244 + C_PlayerResource_AddedSize); - - // C_PlayerResource::UpdatePlayerName - change m_szName address - ChangeOffset<unsigned int>(module.Offset(0x16431F + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName - change m_szName address 1 - ChangeOffset<unsigned int>(module.Offset(0x1645B1 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName - change m_szName address 2 - ChangeOffset<unsigned int>(module.Offset(0x1645C0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName - change m_szName address 3 - ChangeOffset<unsigned int>(module.Offset(0x1645DD + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName internal func - change m_szName address 1 - ChangeOffset<unsigned int>(module.Offset(0x164B71 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName internal func - change m_szName address 2 - ChangeOffset<unsigned int>(module.Offset(0x164B9B + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1 - ChangeOffset<unsigned int>(module.Offset(0x164641 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2 - ChangeOffset<unsigned int>(module.Offset(0x164650 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3 - ChangeOffset<unsigned int>(module.Offset(0x16466D + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1 - ChangeOffset<unsigned int>(module.Offset(0x164BA3 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2 - ChangeOffset<unsigned int>(module.Offset(0x164BCE + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3 - ChangeOffset<unsigned int>(module.Offset(0x164BE7 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - - // C_PlayerResource::m_szName - ChangeOffset<unsigned int>(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - ChangeOffset<unsigned short>(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource size - ChangeOffset<unsigned int>(module.Offset(0x163415 + 6), C_PlayerResource_ModifiedSize); - - // DT_PlayerResource::m_iPing RecvProp - ChangeOffset<unsigned int>(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned int>(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned int>(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1); - - // C_PlayerResource::m_iPing - ChangeOffset<unsigned int>(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset<unsigned short>(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_iTeam RecvProp - ChangeOffset<unsigned int>(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned int>(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned int>(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1); - - // C_PlayerResource::m_iTeam - ChangeOffset<unsigned int>(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset<unsigned short>(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_iPRHealth RecvProp - ChangeOffset<unsigned int>(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned int>(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned int>(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1); - - // C_PlayerResource::m_iPRHealth - ChangeOffset<unsigned int>(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset<unsigned short>(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_bConnected RecvProp - ChangeOffset<unsigned int>(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned int>(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned int>(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1); - - // C_PlayerResource::m_bConnected - ChangeOffset<unsigned int>(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset<unsigned short>(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_bAlive RecvProp - ChangeOffset<unsigned int>(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned int>(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned int>(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1); - - // C_PlayerResource::m_bAlive - ChangeOffset<unsigned int>(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset<unsigned short>(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_boolStats RecvProp - ChangeOffset<unsigned int>(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1); - - // C_PlayerResource::m_boolStats - ChangeOffset<unsigned int>(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset<unsigned short>(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1); - - // DT_PlayerResource::m_killStats RecvProp - ChangeOffset<unsigned int>(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length); - - // C_PlayerResource::m_killStats - ChangeOffset<unsigned int>(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset<unsigned short>(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length); - - // DT_PlayerResource::m_scoreStats RecvProp - ChangeOffset<unsigned int>(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned int>(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length); - - // C_PlayerResource::m_scoreStats - ChangeOffset<unsigned int>(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset<unsigned short>(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length); - - // C_PlayerResource::GetPlayerName - change m_bConnected address - ChangeOffset<unsigned int>(module.Offset(0x164599 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - - // C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address - ChangeOffset<unsigned int>(module.Offset(0x164629 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - - // C_PlayerResource::GetPlayerName internal func - change m_bConnected address - ChangeOffset<unsigned int>(module.Offset(0x164B13 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - - // Some other get name func (that seems to be unused) - change m_bConnected address - ChangeOffset<unsigned int>(module.Offset(0x164860 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - - // Some other get name func 2 (that seems to be unused too) - change m_bConnected address - ChangeOffset<unsigned int>(module.Offset(0x164834 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - - *module.Offset(0xC35068).RCast<DWORD*>() = 0; - auto DT_PlayerResource_Construct = module.Offset(0x163400).RCast<__int64(__fastcall*)()>(); - DT_PlayerResource_Construct(); - - constexpr int C_Team_OriginalSize = 3200; - constexpr int C_Team_AddedSize = Team_AddedSize; - constexpr int C_Team_ModifiedSize = C_Team_OriginalSize + C_Team_AddedSize; - - // C_Team class allocation function - allocate a bigger amount to fit all new team player data - ChangeOffset<unsigned int>(module.Offset(0x182321 + 1), C_Team_ModifiedSize); - - // C_Team::C_Team - increase memset length to clean newly allocated data - ChangeOffset<unsigned int>(module.Offset(0x1804A2 + 2), 256 + C_Team_AddedSize); - - // DT_Team size - ChangeOffset<unsigned int>(module.Offset(0xC3AA0C), C_Team_ModifiedSize); - - *module.Offset(0xC3AFF8).RCast<DWORD*>() = 0; - auto DT_Team_Construct = module.Offset(0x17F950).RCast<__int64(__fastcall*)()>(); - DT_Team_Construct(); -} diff --git a/NorthstarDLL/shared/maxplayers.h b/NorthstarDLL/shared/maxplayers.h deleted file mode 100644 index 40a3ac58..00000000 --- a/NorthstarDLL/shared/maxplayers.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -int GetMaxPlayers(); diff --git a/NorthstarDLL/shared/misccommands.cpp b/NorthstarDLL/shared/misccommands.cpp deleted file mode 100644 index 15da6767..00000000 --- a/NorthstarDLL/shared/misccommands.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include "misccommands.h" -#include "core/convar/concommand.h" -#include "shared/playlist.h" -#include "engine/r2engine.h" -#include "client/r2client.h" -#include "core/tier0.h" -#include "engine/hoststate.h" -#include "masterserver/masterserver.h" -#include "mods/modmanager.h" -#include "server/auth/serverauthentication.h" -#include "squirrel/squirrel.h" - -void ConCommand_force_newgame(const CCommand& arg) -{ - if (arg.ArgC() < 2) - return; - - g_pHostState->m_iNextState = HostState_t::HS_NEW_GAME; - strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName)); -} - -void ConCommand_ns_start_reauth_and_leave_to_lobby(const CCommand& arg) -{ - // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect - g_pMasterServerManager->m_bNewgameAfterSelfAuth = true; - g_pMasterServerManager->AuthenticateWithOwnServer(g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken); -} - -void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg) -{ - if (g_pServerAuthentication->m_RemoteAuthenticationData.size()) - g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str()); - - // weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this - if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM) - { - g_pServerAuthentication->m_bNeedLocalAuthForNewgame = true; - - // this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta - // fucks things should maybe set this in HostState_NewGame? - R2::SetCurrentPlaylist("tdm"); - strcpy(g_pHostState->m_levelName, "mp_lobby"); - g_pHostState->m_iNextState = HostState_t::HS_NEW_GAME; - } -} - -void ConCommand_cvar_setdefaultvalue(const CCommand& arg) -{ - if (arg.ArgC() < 3) - { - spdlog::info("usage: cvar_setdefaultvalue mp_gamemode tdm"); - return; - } - - ConVar* pCvar = g_pCVar->FindVar(arg.Arg(1)); - if (!pCvar) - { - spdlog::info("usage: cvar_setdefaultvalue mp_gamemode tdm"); - return; - } - - // unfortunately no way for us to not leak memory here, as default value might not be in writeable memory by default - int nLen = strlen(arg.Arg(2)); - char* pBuf = new char[nLen + 1]; - strncpy_s(pBuf, nLen + 1, arg.Arg(2), nLen); - - pCvar->m_pszDefaultValue = pBuf; -} - -void ConCommand_cvar_setvalueanddefaultvalue(const CCommand& arg) -{ - if (arg.ArgC() < 3) - { - spdlog::info("usage: cvar_setvalueanddefaultvalue mp_gamemode tdm"); - return; - } - - ConVar* pCvar = g_pCVar->FindVar(arg.Arg(1)); - if (!pCvar) - { - spdlog::info("usage: cvar_setvalueanddefaultvalue mp_gamemode tdm"); - return; - } - - // unfortunately no way for us to not leak memory here, as default value might not be in writeable memory by default - int nLen = strlen(arg.Arg(2)); - char* pBuf = new char[nLen + 1]; - strncpy_s(pBuf, nLen + 1, arg.Arg(2), nLen); - - pCvar->m_pszDefaultValue = pBuf; - pCvar->SetValue(pCvar->m_pszDefaultValue); -} - -void ConCommand_cvar_reset(const CCommand& arg) -{ - if (arg.ArgC() < 2) - { - spdlog::info("usage: cvar_reset mp_gamemode"); - return; - } - - ConVar* pCvar = g_pCVar->FindVar(arg.Arg(1)); - if (!pCvar) - { - spdlog::info("usage: cvar_reset mp_gamemode"); - return; - } - - // reset cvar - pCvar->SetValue(pCvar->m_pszDefaultValue); -} - -void AddMiscConCommands() -{ - RegisterConCommand( - "force_newgame", - ConCommand_force_newgame, - "forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME", - FCVAR_NONE); - - RegisterConCommand( - "ns_start_reauth_and_leave_to_lobby", - ConCommand_ns_start_reauth_and_leave_to_lobby, - "called by the server, used to reauth and return the player to lobby when leaving a game", - FCVAR_SERVER_CAN_EXECUTE); - - // this is a concommand because we make a deferred call to it from another thread - RegisterConCommand("ns_end_reauth_and_leave_to_lobby", ConCommand_ns_end_reauth_and_leave_to_lobby, "", FCVAR_NONE); - - RegisterConCommand( - "cvar_setdefaultvalue", - ConCommand_cvar_setdefaultvalue, - "overwrites the default value of a cvar, for use with script and cvar_reset", - FCVAR_NONE); - RegisterConCommand( - "cvar_setvalueanddefaultvalue", - ConCommand_cvar_setvalueanddefaultvalue, - "overwrites the current value and default value of a cvar, for use with script and cvar_reset", - FCVAR_NONE); - RegisterConCommand("cvar_reset", ConCommand_cvar_reset, "resets a cvar's value to its default value", FCVAR_NONE); -} - -// fixes up various cvar flags to have more sane values -void FixupCvarFlags() -{ - if (CommandLine()->CheckParm("-allowdevcvars")) - { - // strip hidden and devonly cvar flags - int iNumCvarsAltered = 0; - for (auto& pair : g_pCVar->DumpToMap()) - { - // strip flags - int flags = pair.second->GetFlags(); - if (flags & FCVAR_DEVELOPMENTONLY) - { - flags &= ~FCVAR_DEVELOPMENTONLY; - iNumCvarsAltered++; - } - - if (flags & FCVAR_HIDDEN) - { - flags &= ~FCVAR_HIDDEN; - iNumCvarsAltered++; - } - - pair.second->m_nFlags = flags; - } - - spdlog::info("Removed {} hidden/devonly cvar flags", iNumCvarsAltered); - } - - // make all engine client commands FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS - // these are usually checked through CGameClient::IsEngineClientCommand, but we get more control over this if we just do it through - // cvar flags - const char** ppEngineClientCommands = CModule("engine.dll").Offset(0x7C5EF0).RCast<const char**>(); - - int i = 0; - do - { - ConCommandBase* pCommand = g_pCVar->FindCommandBase(ppEngineClientCommands[i]); - if (pCommand) // not all the commands in this array actually exist in respawn source - pCommand->m_nFlags |= FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS; - } while (ppEngineClientCommands[++i]); - - // array of cvars and the flags we want to add to them - const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_ADD_FLAGS = { - // system commands (i.e. necessary for proper functionality) - // servers need to be able to disconnect - {"disconnect", FCVAR_SERVER_CAN_EXECUTE}, - - // cheat commands - {"give", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"give_server", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"givecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"takecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - - {"switchclass", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"set", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"_setClassVarServer", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - - {"ent_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"ent_throw", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"ent_setname", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"ent_teleport", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"ent_remove", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"ent_remove_all", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"ent_fire", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - - {"particle_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"particle_recreate", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"particle_kill", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - - {"test_setteam", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"melee_lunge_ent", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - - // fcvars that should be cheats - {"net_ignoreAllSnapshots", FCVAR_CHEAT}, - {"highlight_draw", FCVAR_CHEAT}, - // these should potentially be replicated rather than cheat, like sv_footsteps is - // however they're defined on client, so can't make replicated atm sadly - {"cl_footstep_event_max_dist", FCVAR_CHEAT}, - {"cl_footstep_event_max_dist_titan", FCVAR_CHEAT}, - }; - - // array of cvars and the flags we want to remove from them - const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_REMOVE_FLAGS = { - // unsure how this command works, not even sure it's used on retail servers, deffo shouldn't be used on northstar - {"migrateme", FCVAR_SERVER_CAN_EXECUTE | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"recheck", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, // we don't need this on northstar servers, it's for communities - - // unsure how these work exactly (rpt system likely somewhat stripped?), removing anyway since they won't be used - {"rpt_client_enable", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - {"rpt_password", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, - - // these are devonly by default but should be modifyable - // NOTE: not all of these may actually do anything or work properly in practice - // network settings - {"cl_updaterate_mp", FCVAR_DEVELOPMENTONLY}, - {"cl_updaterate_sp", FCVAR_DEVELOPMENTONLY}, - {"clock_bias_sp", FCVAR_DEVELOPMENTONLY}, - {"clock_bias_mp", FCVAR_DEVELOPMENTONLY}, - {"cl_interpolate", FCVAR_DEVELOPMENTONLY}, // super duper ultra fucks anims if changed - {"cl_interpolateSoAllAnimsLoop", FCVAR_DEVELOPMENTONLY}, - {"cl_cmdrate", FCVAR_DEVELOPMENTONLY}, - {"cl_cmdbackup", FCVAR_DEVELOPMENTONLY}, - {"rate", FCVAR_DEVELOPMENTONLY}, - {"net_minroutable", FCVAR_DEVELOPMENTONLY}, - {"net_maxroutable", FCVAR_DEVELOPMENTONLY}, - {"net_lerpFields", FCVAR_DEVELOPMENTONLY}, - {"net_ignoreAllSnapshots", FCVAR_DEVELOPMENTONLY}, - {"net_chokeloop", FCVAR_DEVELOPMENTONLY}, - {"sv_unlag", FCVAR_DEVELOPMENTONLY}, - {"sv_maxunlag", FCVAR_DEVELOPMENTONLY}, - {"sv_lagpushticks", FCVAR_DEVELOPMENTONLY}, - {"sv_instancebaselines", FCVAR_DEVELOPMENTONLY}, - {"sv_voiceEcho", FCVAR_DEVELOPMENTONLY}, - {"net_compresspackets", FCVAR_DEVELOPMENTONLY}, - {"net_compresspackets_minsize", FCVAR_DEVELOPMENTONLY}, - {"net_verifyEncryption", FCVAR_DEVELOPMENTONLY}, // unsure if functional in retail - - // gameplay settings - {"vel_samples", FCVAR_DEVELOPMENTONLY}, - {"vel_sampleFrequency", FCVAR_DEVELOPMENTONLY}, - {"sv_friction", FCVAR_DEVELOPMENTONLY}, - {"sv_stopspeed", FCVAR_DEVELOPMENTONLY}, - {"sv_airaccelerate", FCVAR_DEVELOPMENTONLY}, - {"sv_forceGrapplesToFail", FCVAR_DEVELOPMENTONLY}, - {"sv_maxvelocity", FCVAR_DEVELOPMENTONLY}, - {"sv_footsteps", FCVAR_DEVELOPMENTONLY}, - // these 2 are flagged as CHEAT above, could be made REPLICATED later potentially - {"cl_footstep_event_max_dist", FCVAR_DEVELOPMENTONLY}, - {"cl_footstep_event_max_dist_titan", FCVAR_DEVELOPMENTONLY}, - {"sv_balanceTeams", FCVAR_DEVELOPMENTONLY}, - {"rodeo_enable", FCVAR_DEVELOPMENTONLY}, - {"sv_forceRodeoToFail", FCVAR_DEVELOPMENTONLY}, - {"player_find_rodeo_target_per_cmd", FCVAR_DEVELOPMENTONLY}, // todo test before merge - {"hud_takesshots", FCVAR_DEVELOPMENTONLY}, // very likely does not work but would be cool if it did - - {"cam_collision", FCVAR_DEVELOPMENTONLY}, - {"cam_idealdelta", FCVAR_DEVELOPMENTONLY}, - {"cam_ideallag", FCVAR_DEVELOPMENTONLY}, - - // graphics/visual settings - {"mat_colorcorrection", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoRadius", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoDepthMax", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoBlurSharpness", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoIntensity", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoBias", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoDistanceLerp", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoBlurRadius", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoExponent", FCVAR_DEVELOPMENTONLY}, - {"r_hbaoDepthFadePctDefault", FCVAR_DEVELOPMENTONLY}, - {"r_drawscreenspaceparticles", FCVAR_DEVELOPMENTONLY}, - {"ui_loadingscreen_fadeout_time", FCVAR_DEVELOPMENTONLY}, - {"ui_loadingscreen_fadein_time", FCVAR_DEVELOPMENTONLY}, - {"ui_loadingscreen_transition_time", FCVAR_DEVELOPMENTONLY}, - {"ui_loadingscreen_mintransition_time", FCVAR_DEVELOPMENTONLY}, - // these 2 could be FCVAR_CHEAT, i guess? - {"cl_draw_player_model", FCVAR_DEVELOPMENTONLY}, - {"cl_always_draw_3p_player", FCVAR_DEVELOPMENTONLY}, - {"idcolor_neutral", FCVAR_DEVELOPMENTONLY}, - {"idcolor_ally", FCVAR_DEVELOPMENTONLY}, - {"idcolor_ally_cb1", FCVAR_DEVELOPMENTONLY}, - {"idcolor_ally_cb2", FCVAR_DEVELOPMENTONLY}, - {"idcolor_ally_cb3", FCVAR_DEVELOPMENTONLY}, - {"idcolor_enemy", FCVAR_DEVELOPMENTONLY}, - {"idcolor_enemy_cb1", FCVAR_DEVELOPMENTONLY}, - {"idcolor_enemy_cb2", FCVAR_DEVELOPMENTONLY}, - {"idcolor_enemy_cb3", FCVAR_DEVELOPMENTONLY}, - {"playerListPartyColorR", FCVAR_DEVELOPMENTONLY}, - {"playerListPartyColorG", FCVAR_DEVELOPMENTONLY}, - {"playerListPartyColorB", FCVAR_DEVELOPMENTONLY}, - {"playerListUseFriendColor", FCVAR_DEVELOPMENTONLY}, - {"fx_impact_neutral", FCVAR_DEVELOPMENTONLY}, - {"fx_impact_ally", FCVAR_DEVELOPMENTONLY}, - {"fx_impact_enemy", FCVAR_DEVELOPMENTONLY}, - {"hitch_alert_color", FCVAR_DEVELOPMENTONLY}, - {"particles_cull_all", FCVAR_DEVELOPMENTONLY}, - {"particles_cull_dlights", FCVAR_DEVELOPMENTONLY}, - {"map_settings_override", FCVAR_DEVELOPMENTONLY}, - {"highlight_draw", FCVAR_DEVELOPMENTONLY}, - - // sys/engine settings - {"sleep_when_meeting_framerate", FCVAR_DEVELOPMENTONLY}, - {"sleep_when_meeting_framerate_headroom_ms", FCVAR_DEVELOPMENTONLY}, - {"not_focus_sleep", FCVAR_DEVELOPMENTONLY}, - {"sp_not_focus_pause", FCVAR_DEVELOPMENTONLY}, - {"joy_requireFocus", FCVAR_DEVELOPMENTONLY}, - - {"host_thread_mode", FCVAR_DEVELOPMENTONLY}, - {"phys_enable_simd_optimizations", FCVAR_DEVELOPMENTONLY}, - {"phys_enable_experimental_optimizations", FCVAR_DEVELOPMENTONLY}, - - {"community_frame_run", FCVAR_DEVELOPMENTONLY}, - {"sv_single_core_dedi", FCVAR_DEVELOPMENTONLY}, - {"sv_stressbots", FCVAR_DEVELOPMENTONLY}, - - {"fatal_script_errors", FCVAR_DEVELOPMENTONLY}, - {"fatal_script_errors_client", FCVAR_DEVELOPMENTONLY}, - {"fatal_script_errors_server", FCVAR_DEVELOPMENTONLY}, - {"script_error_on_midgame_load", FCVAR_DEVELOPMENTONLY}, // idk what this is - - {"ai_ainRebuildOnMapStart", FCVAR_DEVELOPMENTONLY}, - - {"save_enable", FCVAR_DEVELOPMENTONLY}, - - // cheat commands - {"switchclass", FCVAR_DEVELOPMENTONLY}, - {"set", FCVAR_DEVELOPMENTONLY}, - {"_setClassVarServer", FCVAR_DEVELOPMENTONLY}, - - // reparse commands - {"aisettings_reparse", FCVAR_DEVELOPMENTONLY}, - {"aisettings_reparse_client", FCVAR_DEVELOPMENTONLY}, - {"damagedefs_reparse", FCVAR_DEVELOPMENTONLY}, - {"damagedefs_reparse_client", FCVAR_DEVELOPMENTONLY}, - {"playerSettings_reparse", FCVAR_DEVELOPMENTONLY}, - {"_playerSettings_reparse_Server", FCVAR_DEVELOPMENTONLY}, - - }; - - const std::vector<std::tuple<const char*, const char*>> CVAR_FIXUP_DEFAULT_VALUES = { - {"sv_stressbots", "0"}, // not currently used but this is probably a bad default if we get bots working - {"cl_pred_optimize", "0"} // fixes issues with animation prediction in thirdperson - }; - - for (auto& fixup : CVAR_FIXUP_ADD_FLAGS) - { - ConCommandBase* command = g_pCVar->FindCommandBase(std::get<0>(fixup)); - if (command) - command->m_nFlags |= std::get<1>(fixup); - } - - for (auto& fixup : CVAR_FIXUP_REMOVE_FLAGS) - { - ConCommandBase* command = g_pCVar->FindCommandBase(std::get<0>(fixup)); - if (command) - command->m_nFlags &= ~std::get<1>(fixup); - } - - for (auto& fixup : CVAR_FIXUP_DEFAULT_VALUES) - { - ConVar* cvar = g_pCVar->FindVar(std::get<0>(fixup)); - if (cvar && !strcmp(cvar->GetString(), cvar->m_pszDefaultValue)) - { - cvar->SetValue(std::get<1>(fixup)); - cvar->m_pszDefaultValue = std::get<1>(fixup); - } - } -} diff --git a/NorthstarDLL/shared/misccommands.h b/NorthstarDLL/shared/misccommands.h deleted file mode 100644 index 07a07fb3..00000000 --- a/NorthstarDLL/shared/misccommands.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -void AddMiscConCommands(); -void FixupCvarFlags(); diff --git a/NorthstarDLL/shared/playlist.cpp b/NorthstarDLL/shared/playlist.cpp deleted file mode 100644 index 2b9ad979..00000000 --- a/NorthstarDLL/shared/playlist.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "playlist.h" -#include "core/convar/concommand.h" -#include "core/convar/convar.h" -#include "squirrel/squirrel.h" -#include "engine/hoststate.h" -#include "engine/r2engine.h" -#include "server/serverpresence.h" - -AUTOHOOK_INIT() - -// use the R2 namespace for game funcs -namespace R2 -{ - DEFINED_VAR_AT(engine.dll + 0x18C640, GetCurrentPlaylistName); - DEFINED_VAR_AT(engine.dll + 0x18EB20, SetCurrentPlaylist); - DEFINED_VAR_AT(engine.dll + 0x18ED00, SetPlaylistVarOverride); - DEFINED_VAR_AT(engine.dll + 0x18C680, GetCurrentPlaylistVar); -} // namespace R2 - -ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride; - -// clang-format off -AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180, -char, __fastcall, (void* a1, void* a2)) -// clang-format on -{ - // the private_match playlist on mp_lobby is the only situation where there should be any legitimate sending of this netmessage - if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match") || - strcmp(g_pGlobals->m_pMapName, "mp_lobby")) - return 1; - - return clc_SetPlaylistVarOverride__Process(a1, a2); -} - -// clang-format off -AUTOHOOK(SetCurrentPlaylist, engine.dll + 0x18EB20, -bool, __fastcall, (const char* pPlaylistName)) -// clang-format on -{ - bool bSuccess = SetCurrentPlaylist(pPlaylistName); - - if (bSuccess) - { - spdlog::info("Set playlist to {}", R2::GetCurrentPlaylistName()); - g_pServerPresence->SetPlaylist(R2::GetCurrentPlaylistName()); - } - - return bSuccess; -} - -// clang-format off -AUTOHOOK(SetPlaylistVarOverride, engine.dll + 0x18ED00, -void, __fastcall, (const char* pVarName, const char* pValue)) -// clang-format on -{ - if (strlen(pValue) >= 64) - return; - - SetPlaylistVarOverride(pVarName, pValue); -} - -// clang-format off -AUTOHOOK(GetCurrentPlaylistVar, engine.dll + 0x18C680, -const char*, __fastcall, (const char* pVarName, bool bUseOverrides)) -// clang-format on -{ - if (!bUseOverrides && !strcmp(pVarName, "max_players")) - bUseOverrides = true; - - return GetCurrentPlaylistVar(pVarName, bUseOverrides); -} - -// clang-format off -AUTOHOOK(GetCurrentGamemodeMaxPlayers, engine.dll + 0x18C430, -int, __fastcall, ()) -// clang-format on -{ - const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", 0); - if (!pMaxPlayers) - return GetCurrentGamemodeMaxPlayers(); - - int iMaxPlayers = atoi(pMaxPlayers); - return iMaxPlayers; -} - -void ConCommand_playlist(const CCommand& args) -{ - if (args.ArgC() < 2) - return; - - R2::SetCurrentPlaylist(args.Arg(1)); -} - -void ConCommand_setplaylistvaroverride(const CCommand& args) -{ - if (args.ArgC() < 3) - return; - - for (int i = 1; i < args.ArgC(); i += 2) - R2::SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1)); -} - -ON_DLL_LOAD_RELIESON("engine.dll", PlaylistHooks, (ConCommand, ConVar), (CModule module)) -{ - AUTOHOOK_DISPATCH() - - // playlist is the name of the command on respawn servers, but we already use setplaylist so can't get rid of it - RegisterConCommand("playlist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE); - RegisterConCommand("setplaylist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE); - RegisterConCommand("setplaylistvaroverrides", ConCommand_setplaylistvaroverride, "sets a playlist var override", FCVAR_NONE); - - // note: clc_SetPlaylistVarOverride is pretty insecure, since it allows for entirely arbitrary playlist var overrides to be sent to the - // server, this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be - // disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client - // compatibility - Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar( - "ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages"); - - // patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is - // this possible respawn, wtf) todo: add a warning for this - module.Offset(0x18ED8D).Patch("C3"); - - // patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game - module.Offset(0x18ED17).NOP(6); -} diff --git a/NorthstarDLL/shared/playlist.h b/NorthstarDLL/shared/playlist.h deleted file mode 100644 index e56fdf96..00000000 --- a/NorthstarDLL/shared/playlist.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -// use the R2 namespace for game funcs -namespace R2 -{ - inline const char* (*GetCurrentPlaylistName)(); - inline void (*SetCurrentPlaylist)(const char* pPlaylistName); - inline void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue); - inline const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides); -} // namespace R2 diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp deleted file mode 100644 index ac9a2ce9..00000000 --- a/NorthstarDLL/squirrel/squirrel.cpp +++ /dev/null @@ -1,943 +0,0 @@ -#include "squirrel.h" -#include "mods/modsavefiles.h" -#include "logging/logging.h" -#include "core/convar/concommand.h" -#include "mods/modmanager.h" -#include "dedicated/dedicated.h" -#include "engine/r2engine.h" -#include "core/tier0.h" -#include "plugins/plugin_abi.h" -#include "plugins/plugins.h" -#include "ns_version.h" -#include "core/vanilla.h" - -#include <any> - -AUTOHOOK_INIT() - -std::shared_ptr<ColoredLogger> getSquirrelLoggerByContext(ScriptContext context) -{ - switch (context) - { - case ScriptContext::UI: - return NS::log::SCRIPT_UI; - case ScriptContext::CLIENT: - return NS::log::SCRIPT_CL; - case ScriptContext::SERVER: - return NS::log::SCRIPT_SV; - default: - throw std::runtime_error("getSquirrelLoggerByContext called with invalid context"); - return nullptr; - } -} - -namespace NS::log -{ - template <ScriptContext context> std::shared_ptr<spdlog::logger> squirrel_logger() - { - // Switch statements can't be constexpr afaik - // clang-format off - if constexpr (context == ScriptContext::UI) { return SCRIPT_UI; } - if constexpr (context == ScriptContext::CLIENT) { return SCRIPT_CL; } - if constexpr (context == ScriptContext::SERVER) { return SCRIPT_SV; } - // clang-format on - } -}; // namespace NS::log - -const char* GetContextName(ScriptContext context) -{ - switch (context) - { - case ScriptContext::CLIENT: - return "CLIENT"; - case ScriptContext::SERVER: - return "SERVER"; - case ScriptContext::UI: - return "UI"; - default: - return "UNKNOWN"; - } -} - -const char* GetContextName_Short(ScriptContext context) -{ - switch (context) - { - case ScriptContext::CLIENT: - return "CL"; - case ScriptContext::SERVER: - return "SV"; - case ScriptContext::UI: - return "UI"; - default: - return "??"; - } -} - -eSQReturnType SQReturnTypeFromString(const char* pReturnType) -{ - static const std::map<std::string, eSQReturnType> sqReturnTypeNameToString = { - {"bool", eSQReturnType::Boolean}, - {"float", eSQReturnType::Float}, - {"vector", eSQReturnType::Vector}, - {"int", eSQReturnType::Integer}, - {"entity", eSQReturnType::Entity}, - {"string", eSQReturnType::String}, - {"array", eSQReturnType::Arrays}, - {"asset", eSQReturnType::Asset}, - {"table", eSQReturnType::Table}}; - - if (sqReturnTypeNameToString.find(pReturnType) != sqReturnTypeNameToString.end()) - return sqReturnTypeNameToString.at(pReturnType); - else - return eSQReturnType::Default; // previous default value -} - -ScriptContext ScriptContextFromString(std::string string) -{ - if (string == "UI") - return ScriptContext::UI; - if (string == "CLIENT") - return ScriptContext::CLIENT; - if (string == "SERVER") - return ScriptContext::SERVER; - else - return ScriptContext::INVALID; -} - -const char* SQTypeNameFromID(int type) -{ - switch (type) - { - case OT_ASSET: - return "asset"; - case OT_INTEGER: - return "int"; - case OT_BOOL: - return "bool"; - case SQOBJECT_NUMERIC: - return "float or int"; - case OT_NULL: - return "null"; - case OT_VECTOR: - return "vector"; - case 0: - return "var"; - case OT_USERDATA: - return "userdata"; - case OT_FLOAT: - return "float"; - case OT_STRING: - return "string"; - case OT_ARRAY: - return "array"; - case 0x8000200: - return "function"; - case 0x8100000: - return "structdef"; - case OT_THREAD: - return "thread"; - case OT_FUNCPROTO: - return "function"; - case OT_CLAAS: - return "class"; - case OT_WEAKREF: - return "weakref"; - case 0x8080000: - return "unimplemented function"; - case 0x8200000: - return "struct instance"; - case OT_TABLE: - return "table"; - case 0xA008000: - return "instance"; - case OT_ENTITY: - return "entity"; - } - return ""; -} - -template <ScriptContext context> void SquirrelManager<context>::GenerateSquirrelFunctionsStruct(SquirrelFunctions* s) -{ - s->RegisterSquirrelFunc = RegisterSquirrelFunc; - s->__sq_defconst = __sq_defconst; - - s->__sq_compilebuffer = __sq_compilebuffer; - s->__sq_call = __sq_call; - s->__sq_raiseerror = __sq_raiseerror; - s->__sq_compilefile = __sq_compilefile; - - s->__sq_newarray = __sq_newarray; - s->__sq_arrayappend = __sq_arrayappend; - - s->__sq_newtable = __sq_newtable; - s->__sq_newslot = __sq_newslot; - - s->__sq_pushroottable = __sq_pushroottable; - s->__sq_pushstring = __sq_pushstring; - s->__sq_pushinteger = __sq_pushinteger; - s->__sq_pushfloat = __sq_pushfloat; - s->__sq_pushbool = __sq_pushbool; - s->__sq_pushasset = __sq_pushasset; - s->__sq_pushvector = __sq_pushvector; - s->__sq_pushobject = __sq_pushobject; - - s->__sq_getstring = __sq_getstring; - s->__sq_getinteger = __sq_getinteger; - s->__sq_getfloat = __sq_getfloat; - s->__sq_getbool = __sq_getbool; - s->__sq_get = __sq_get; - s->__sq_getasset = __sq_getasset; - s->__sq_getuserdata = __sq_getuserdata; - s->__sq_getvector = __sq_getvector; - s->__sq_getthisentity = __sq_getthisentity; - s->__sq_getobject = __sq_getobject; - - s->__sq_stackinfos = __sq_stackinfos; - - s->__sq_createuserdata = __sq_createuserdata; - s->__sq_setuserdatatypeid = __sq_setuserdatatypeid; - s->__sq_getfunction = __sq_getfunction; - - s->__sq_schedule_call_external = AsyncCall_External; - - s->__sq_getentityfrominstance = __sq_getentityfrominstance; - s->__sq_GetEntityConstant_CBaseEntity = __sq_GetEntityConstant_CBaseEntity; - - s->__sq_pushnewstructinstance = __sq_pushnewstructinstance; - s->__sq_sealstructslot = __sq_sealstructslot; -} - -// Allows for generating squirrelmessages from plugins. -void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function, void* userdata) -{ - SquirrelMessage message {}; - message.functionName = func_name; - message.isExternal = true; - message.externalFunc = function; - message.userdata = userdata; - switch (context) - { - case ScriptContext::CLIENT: - g_pSquirrel<ScriptContext::CLIENT>->messageBuffer->push(message); - break; - case ScriptContext::SERVER: - g_pSquirrel<ScriptContext::SERVER>->messageBuffer->push(message); - break; - case ScriptContext::UI: - g_pSquirrel<ScriptContext::UI>->messageBuffer->push(message); - break; - } -} - -// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors -template class SquirrelManager<ScriptContext::SERVER>; -template class SquirrelManager<ScriptContext::CLIENT>; -template class SquirrelManager<ScriptContext::UI>; - -template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquirrelVM* newSqvm) -{ - m_pSQVM = newSqvm; - - for (SQFuncRegistration* funcReg : m_funcRegistrations) - { - spdlog::info("Registering {} function {}", GetContextName(context), funcReg->squirrelFuncName); - RegisterSquirrelFunc(m_pSQVM, funcReg, 1); - } - - for (auto& pair : g_pModManager->m_DependencyConstants) - { - bool bWasFound = false; - - for (Mod& dependency : g_pModManager->m_LoadedMods) - { - if (!dependency.m_bEnabled) - continue; - - if (dependency.Name == pair.second) - { - bWasFound = true; - break; - } - } - - defconst(m_pSQVM, pair.first.c_str(), bWasFound); - } - - auto loadedPlugins = &g_pPluginManager->m_vLoadedPlugins; - for (const auto& pluginName : g_pModManager->m_PluginDependencyConstants) - { - auto f = [&](Plugin plugin) -> bool { return plugin.dependencyName == pluginName; }; - defconst(m_pSQVM, pluginName.c_str(), std::find_if(loadedPlugins->begin(), loadedPlugins->end(), f) != loadedPlugins->end()); - } - - defconst(m_pSQVM, "MAX_FOLDER_SIZE", GetMaxSaveFolderSize() / 1024); - - // define squirrel constants for northstar(.dll) version - constexpr int version[4] {NORTHSTAR_VERSION}; - defconst(m_pSQVM, "NS_VERSION_MAJOR", version[0]); - defconst(m_pSQVM, "NS_VERSION_MINOR", version[1]); - defconst(m_pSQVM, "NS_VERSION_PATCH", version[2]); - defconst(m_pSQVM, "NS_VERSION_DEV", version[3]); - - // define squirrel constant for if we are in vanilla-compatibility mode - defconst(m_pSQVM, "VANILLA", g_pVanillaCompatibility->GetVanillaCompatibility()); - - g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer(); - g_pPluginManager->InformSQVMCreated(context, newSqvm); -} - -template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() -{ - // Call all registered mod Destroy callbacks. - if (g_pModManager) - { - NS::log::squirrel_logger<context>()->info("Calling Destroy callbacks for all loaded mods."); - - for (const Mod& loadedMod : g_pModManager->m_LoadedMods) - { - for (const ModScript& script : loadedMod.Scripts) - { - for (const ModScriptCallback& callback : script.Callbacks) - { - if (callback.Context != context || callback.DestroyCallback.length() == 0) - { - continue; - } - - Call(callback.DestroyCallback.c_str()); - NS::log::squirrel_logger<context>()->info("Executed Destroy callback {}.", callback.DestroyCallback); - } - } - } - } - - g_pPluginManager->InformSQVMDestroyed(context); - - // Discard the previous vm and delete the message buffer. - m_pSQVM = nullptr; - - delete g_pSquirrel<context>->messageBuffer; - g_pSquirrel<context>->messageBuffer = nullptr; -} - -template <ScriptContext context> void SquirrelManager<context>::ExecuteCode(const char* pCode) -{ - if (!m_pSQVM || !m_pSQVM->sqvm) - { - spdlog::error("Cannot execute code, {} squirrel vm is not initialised", GetContextName(context)); - return; - } - - spdlog::info("Executing {} script code {} ", GetContextName(context), pCode); - - std::string strCode(pCode); - CompileBufferState bufferState = CompileBufferState(strCode); - - SQRESULT compileResult = compilebuffer(&bufferState, "console"); - spdlog::info("sq_compilebuffer returned {}", PrintSQRESULT.at(compileResult)); - - if (compileResult != SQRESULT_ERROR) - { - pushroottable(m_pSQVM->sqvm); - SQRESULT callResult = _call(m_pSQVM->sqvm, 0); - spdlog::info("sq_call returned {}", PrintSQRESULT.at(callResult)); - } -} - -template <ScriptContext context> void SquirrelManager<context>::AddFuncRegistration( - std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func) -{ - SQFuncRegistration* reg = new SQFuncRegistration; - - reg->squirrelFuncName = new char[name.size() + 1]; - strcpy((char*)reg->squirrelFuncName, name.c_str()); - reg->cppFuncName = reg->squirrelFuncName; - - reg->helpText = new char[helpText.size() + 1]; - strcpy((char*)reg->helpText, helpText.c_str()); - - reg->returnTypeString = new char[returnType.size() + 1]; - strcpy((char*)reg->returnTypeString, returnType.c_str()); - reg->returnType = SQReturnTypeFromString(returnType.c_str()); - - reg->argTypes = new char[argTypes.size() + 1]; - strcpy((char*)reg->argTypes, argTypes.c_str()); - - reg->funcPtr = func; - - m_funcRegistrations.push_back(reg); -} - -template <ScriptContext context> SQRESULT SquirrelManager<context>::setupfunc(const SQChar* funcname) -{ - pushroottable(m_pSQVM->sqvm); - pushstring(m_pSQVM->sqvm, funcname, -1); - - SQRESULT result = get(m_pSQVM->sqvm, -2); - if (result != SQRESULT_ERROR) - pushroottable(m_pSQVM->sqvm); - - return result; -} - -template <ScriptContext context> void SquirrelManager<context>::AddFuncOverride(std::string name, SQFunction func) -{ - m_funcOverrides[name] = func; -} - -// hooks -bool IsUIVM(ScriptContext context, HSquirrelVM* pSqvm) -{ - return ScriptContext(pSqvm->sharedState->cSquirrelVM->vmContext) == ScriptContext::UI; -} - -template <ScriptContext context> void* (*sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); -template <ScriptContext context> void* __fastcall sq_compiler_createHook(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError) -{ - // store whether errors generated from this compile should be fatal - if (IsUIVM(context, sqvm)) - g_pSquirrel<ScriptContext::UI>->m_bFatalCompilationErrors = bShouldThrowError; - else - g_pSquirrel<context>->m_bFatalCompilationErrors = bShouldThrowError; - - return sq_compiler_create<context>(sqvm, a2, a3, bShouldThrowError); -} - -template <ScriptContext context> SQInteger (*SQPrint)(HSquirrelVM* sqvm, const char* fmt); -template <ScriptContext context> SQInteger SQPrintHook(HSquirrelVM* sqvm, const char* fmt, ...) -{ - va_list va; - va_start(va, fmt); - - SQChar buf[1024]; - int charsWritten = vsnprintf_s(buf, _TRUNCATE, fmt, va); - - if (charsWritten > 0) - { - if (buf[charsWritten - 1] == '\n') - buf[charsWritten - 1] = '\0'; - g_pSquirrel<context>->logger->info("{}", buf); - } - - va_end(va); - return 0; -} - -template <ScriptContext context> CSquirrelVM* (*CreateNewVM)(void* a1, ScriptContext realContext); -template <ScriptContext context> CSquirrelVM* __fastcall CreateNewVMHook(void* a1, ScriptContext realContext) -{ - CSquirrelVM* sqvm = CreateNewVM<context>(a1, realContext); - if (realContext == ScriptContext::UI) - g_pSquirrel<ScriptContext::UI>->VMCreated(sqvm); - else - g_pSquirrel<context>->VMCreated(sqvm); - - spdlog::info("CreateNewVM {} {}", GetContextName(realContext), (void*)sqvm); - return sqvm; -} - -template <ScriptContext context> bool (*CSquirrelVM_init)(CSquirrelVM* vm, ScriptContext realContext, float time); -template <ScriptContext context> bool __fastcall CSquirrelVM_initHook(CSquirrelVM* vm, ScriptContext realContext, float time) -{ - bool ret = CSquirrelVM_init<context>(vm, realContext, time); - for (Mod mod : g_pModManager->m_LoadedMods) - { - if (mod.m_bEnabled && mod.initScript.size() != 0) - { - std::string name = mod.initScript.substr(mod.initScript.find_last_of('/') + 1); - std::string path = std::string("scripts/vscripts/") + mod.initScript; - if (g_pSquirrel<context>->compilefile(vm, path.c_str(), name.c_str(), 0)) - g_pSquirrel<context>->compilefile(vm, path.c_str(), name.c_str(), 1); - } - } - return ret; -} - -template <ScriptContext context> void (*DestroyVM)(void* a1, CSquirrelVM* sqvm); -template <ScriptContext context> void __fastcall DestroyVMHook(void* a1, CSquirrelVM* sqvm) -{ - ScriptContext realContext = context; // ui and client use the same function so we use this for prints - if (IsUIVM(context, sqvm->sqvm)) - { - realContext = ScriptContext::UI; - g_pSquirrel<ScriptContext::UI>->VMDestroyed(); - DestroyVM<ScriptContext::CLIENT>(a1, sqvm); // If we pass UI here it crashes - } - else - { - g_pSquirrel<context>->VMDestroyed(); - DestroyVM<context>(a1, sqvm); - } - - spdlog::info("DestroyVM {} {}", GetContextName(realContext), (void*)sqvm); -} - -template <ScriptContext context> void (*SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); -template <ScriptContext context> -void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column) -{ - bool bIsFatalError = g_pSquirrel<context>->m_bFatalCompilationErrors; - ScriptContext realContext = context; // ui and client use the same function so we use this for prints - if (IsUIVM(context, sqvm)) - { - realContext = ScriptContext::UI; - bIsFatalError = g_pSquirrel<ScriptContext::UI>->m_bFatalCompilationErrors; - } - - auto logger = getSquirrelLoggerByContext(realContext); - - logger->error("COMPILE ERROR {}", error); - logger->error("{} line [{}] column [{}]", file, line, column); - - // use disconnect to display an error message for the compile error, but only if the compilation error was fatal - // todo, we could get this from sqvm itself probably, rather than hooking sq_compiler_create - if (bIsFatalError) - { - // kill dedicated server if we hit this - if (IsDedicatedServer()) - { - logger->error("Exiting dedicated server, compile error is fatal"); - // flush the logger before we exit so debug things get saved to log file - logger->flush(); - exit(EXIT_FAILURE); - } - else - { - Cbuf_AddText( - Cbuf_GetCurrentPlayer(), - fmt::format("disconnect \"Encountered {} script compilation error, see console for details.\"", GetContextName(realContext)) - .c_str(), - cmd_source_t::kCommandSrcCode); - - // likely temp: show console so user can see any errors, as error message wont display if ui is dead - // maybe we could disable all mods other than the coremods and try a reload before doing this? - // could also maybe do some vgui bullshit to show something visually rather than console - if (realContext == ScriptContext::UI) - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "showconsole", cmd_source_t::kCommandSrcCode); - } - } - - // dont call the original function since it kills game lol -} - -template <ScriptContext context> int64_t (*RegisterSquirrelFunction)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); -template <ScriptContext context> -int64_t __fastcall RegisterSquirrelFunctionHook(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown) -{ - if (IsUIVM(context, sqvm->sqvm)) - { - if (g_pSquirrel<ScriptContext::UI>->m_funcOverrides.count(funcReg->squirrelFuncName)) - { - g_pSquirrel<ScriptContext::UI>->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; - funcReg->funcPtr = g_pSquirrel<ScriptContext::UI>->m_funcOverrides[funcReg->squirrelFuncName]; - spdlog::info("Replacing {} in UI", std::string(funcReg->squirrelFuncName)); - } - - return g_pSquirrel<ScriptContext::UI>->RegisterSquirrelFunc(sqvm, funcReg, unknown); - } - - if (g_pSquirrel<context>->m_funcOverrides.find(funcReg->squirrelFuncName) != g_pSquirrel<context>->m_funcOverrides.end()) - { - g_pSquirrel<context>->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; - funcReg->funcPtr = g_pSquirrel<context>->m_funcOverrides[funcReg->squirrelFuncName]; - spdlog::info("Replacing {} in Client", std::string(funcReg->squirrelFuncName)); - } - - return g_pSquirrel<context>->RegisterSquirrelFunc(sqvm, funcReg, unknown); -} - -template <ScriptContext context> bool (*CallScriptInitCallback)(void* sqvm, const char* callback); -template <ScriptContext context> bool __fastcall CallScriptInitCallbackHook(void* sqvm, const char* callback) -{ - ScriptContext realContext = context; - bool bShouldCallCustomCallbacks = true; - - if (context == ScriptContext::CLIENT) - { - if (!strcmp(callback, "UICodeCallback_UIInit")) - realContext = ScriptContext::UI; - else if (strcmp(callback, "ClientCodeCallback_MapSpawn")) - bShouldCallCustomCallbacks = false; - } - else if (context == ScriptContext::SERVER) - bShouldCallCustomCallbacks = !strcmp(callback, "CodeCallback_MapSpawn"); - - if (bShouldCallCustomCallbacks) - { - for (Mod mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - for (ModScript script : mod.Scripts) - { - for (ModScriptCallback modCallback : script.Callbacks) - { - if (modCallback.Context == realContext && modCallback.BeforeCallback.length()) - { - spdlog::info("Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.BeforeCallback); - CallScriptInitCallback<context>(sqvm, modCallback.BeforeCallback.c_str()); - } - } - } - } - } - - spdlog::info("{} CodeCallback {} called", GetContextName(realContext), callback); - if (!bShouldCallCustomCallbacks) - spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); - bool ret = CallScriptInitCallback<context>(sqvm, callback); - - // run after callbacks - if (bShouldCallCustomCallbacks) - { - for (Mod mod : g_pModManager->m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - for (ModScript script : mod.Scripts) - { - for (ModScriptCallback modCallback : script.Callbacks) - { - if (modCallback.Context == realContext && modCallback.AfterCallback.length()) - { - spdlog::info("Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.AfterCallback); - CallScriptInitCallback<context>(sqvm, modCallback.AfterCallback.c_str()); - } - } - } - } - } - - return ret; -} - -template <ScriptContext context> void ConCommand_script(const CCommand& args) -{ - g_pSquirrel<context>->ExecuteCode(args.ArgS()); -} - -// literal class type that wraps a constant expression string -template <size_t N> struct TemplateStringLiteral -{ - constexpr TemplateStringLiteral(const char (&str)[N]) - { - std::copy_n(str, N, value); - } - - char value[N]; -}; - -template <ScriptContext context, TemplateStringLiteral funcName> SQRESULT SQ_StubbedFunc(HSquirrelVM* sqvm) -{ - spdlog::info("Blocking call to stubbed function {} in {}", funcName.value, GetContextName(context)); - return SQRESULT_NULL; -} - -template <ScriptContext context> void StubUnsafeSQFuncs() -{ - if (!CommandLine()->CheckParm("-allowunsafesqfuncs")) - { - g_pSquirrel<context>->AddFuncOverride("DevTextBufferWrite", SQ_StubbedFunc<context, "DevTextBufferWrite">); - g_pSquirrel<context>->AddFuncOverride("DevTextBufferClear", SQ_StubbedFunc<context, "DevTextBufferClear">); - g_pSquirrel<context>->AddFuncOverride("DevTextBufferDumpToFile", SQ_StubbedFunc<context, "DevTextBufferDumpToFile">); - g_pSquirrel<context>->AddFuncOverride("Dev_CommandLineAddParam", SQ_StubbedFunc<context, "Dev_CommandLineAddParam">); - g_pSquirrel<context>->AddFuncOverride("DevP4Checkout", SQ_StubbedFunc<context, "DevP4Checkout">); - g_pSquirrel<context>->AddFuncOverride("DevP4Add", SQ_StubbedFunc<context, "DevP4Add">); - } -} - -template <ScriptContext context> void SquirrelManager<context>::ProcessMessageBuffer() -{ - while (std::optional<SquirrelMessage> maybeMessage = messageBuffer->pop()) - { - SquirrelMessage message = maybeMessage.value(); - - SQObject functionobj {}; - int result = sq_getfunction(m_pSQVM->sqvm, message.functionName.c_str(), &functionobj, 0); - if (result != 0) // This func returns 0 on success for some reason - { - NS::log::squirrel_logger<context>()->error( - "ProcessMessageBuffer was unable to find function with name '{}'. Is it global?", message.functionName); - continue; - } - - pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object - pushroottable(m_pSQVM->sqvm); - - int argsAmount = message.args.size(); - - if (message.isExternal && message.externalFunc != NULL) - { - argsAmount = message.externalFunc(m_pSQVM->sqvm, message.userdata); - } - else - { - for (auto& v : message.args) - { - // Execute lambda to push arg to stack - v(); - } - } - - _call(m_pSQVM->sqvm, argsAmount); - } -} - -ADD_SQFUNC( - "string", - NSGetCurrentModName, - "", - "Returns the mod name of the script running this function", - ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - int depth = g_pSquirrel<context>->getinteger(sqvm, 1); - if (auto mod = g_pSquirrel<context>->getcallingmod(sqvm, depth); mod == nullptr) - { - g_pSquirrel<context>->raiseerror(sqvm, "NSGetModName was called from a non-mod script. This shouldn't be possible"); - return SQRESULT_ERROR; - } - else - { - g_pSquirrel<context>->pushstring(sqvm, mod->Name.c_str()); - } - return SQRESULT_NOTNULL; -} - -ADD_SQFUNC( - "string", - NSGetCallingModName, - "int depth = 0", - "Returns the mod name of the script running this function", - ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - int depth = g_pSquirrel<context>->getinteger(sqvm, 1); - if (auto mod = g_pSquirrel<context>->getcallingmod(sqvm, depth); mod == nullptr) - { - g_pSquirrel<context>->pushstring(sqvm, "Unknown"); - } - else - { - g_pSquirrel<context>->pushstring(sqvm, mod->Name.c_str()); - } - return SQRESULT_NOTNULL; -} - -ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(client.dll) - - g_pSquirrel<ScriptContext::CLIENT>->__sq_defconst = module.Offset(0x12120).RCast<sq_defconstType>(); - g_pSquirrel<ScriptContext::UI>->__sq_defconst = g_pSquirrel<ScriptContext::CLIENT>->__sq_defconst; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer = module.Offset(0x3110).RCast<sq_compilebufferType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable = module.Offset(0x5860).RCast<sq_pushroottableType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_compilefile = module.Offset(0xF950).RCast<sq_compilefileType>(); - g_pSquirrel<ScriptContext::UI>->__sq_compilebuffer = g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer; - g_pSquirrel<ScriptContext::UI>->__sq_pushroottable = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable; - g_pSquirrel<ScriptContext::UI>->__sq_compilefile = g_pSquirrel<ScriptContext::CLIENT>->__sq_compilefile; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_call = module.Offset(0x8650).RCast<sq_callType>(); - g_pSquirrel<ScriptContext::UI>->__sq_call = g_pSquirrel<ScriptContext::CLIENT>->__sq_call; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_newarray = module.Offset(0x39F0).RCast<sq_newarrayType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_arrayappend = module.Offset(0x3C70).RCast<sq_arrayappendType>(); - g_pSquirrel<ScriptContext::UI>->__sq_newarray = g_pSquirrel<ScriptContext::CLIENT>->__sq_newarray; - g_pSquirrel<ScriptContext::UI>->__sq_arrayappend = g_pSquirrel<ScriptContext::CLIENT>->__sq_arrayappend; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_newtable = module.Offset(0x3960).RCast<sq_newtableType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_newslot = module.Offset(0x70B0).RCast<sq_newslotType>(); - g_pSquirrel<ScriptContext::UI>->__sq_newtable = g_pSquirrel<ScriptContext::CLIENT>->__sq_newtable; - g_pSquirrel<ScriptContext::UI>->__sq_newslot = g_pSquirrel<ScriptContext::CLIENT>->__sq_newslot; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushstring = module.Offset(0x3440).RCast<sq_pushstringType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushinteger = module.Offset(0x36A0).RCast<sq_pushintegerType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushfloat = module.Offset(0x3800).RCast<sq_pushfloatType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushbool = module.Offset(0x3710).RCast<sq_pushboolType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushasset = module.Offset(0x3560).RCast<sq_pushassetType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushvector = module.Offset(0x3780).RCast<sq_pushvectorType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushobject = module.Offset(0x83D0).RCast<sq_pushobjectType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_raiseerror = module.Offset(0x8470).RCast<sq_raiseerrorType>(); - g_pSquirrel<ScriptContext::UI>->__sq_pushstring = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushstring; - g_pSquirrel<ScriptContext::UI>->__sq_pushinteger = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushinteger; - g_pSquirrel<ScriptContext::UI>->__sq_pushfloat = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushfloat; - g_pSquirrel<ScriptContext::UI>->__sq_pushbool = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushbool; - g_pSquirrel<ScriptContext::UI>->__sq_pushvector = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushvector; - g_pSquirrel<ScriptContext::UI>->__sq_pushasset = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushasset; - g_pSquirrel<ScriptContext::UI>->__sq_pushobject = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushobject; - g_pSquirrel<ScriptContext::UI>->__sq_raiseerror = g_pSquirrel<ScriptContext::CLIENT>->__sq_raiseerror; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_getstring = module.Offset(0x60C0).RCast<sq_getstringType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getinteger = module.Offset(0x60E0).RCast<sq_getintegerType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getfloat = module.Offset(0x6100).RCast<sq_getfloatType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getbool = module.Offset(0x6130).RCast<sq_getboolType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_get = module.Offset(0x7C30).RCast<sq_getType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getasset = module.Offset(0x6010).RCast<sq_getassetType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getuserdata = module.Offset(0x63D0).RCast<sq_getuserdataType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getvector = module.Offset(0x6140).RCast<sq_getvectorType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getthisentity = module.Offset(0x12F80).RCast<sq_getthisentityType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getobject = module.Offset(0x6160).RCast<sq_getobjectType>(); - g_pSquirrel<ScriptContext::UI>->__sq_getstring = g_pSquirrel<ScriptContext::CLIENT>->__sq_getstring; - g_pSquirrel<ScriptContext::UI>->__sq_getinteger = g_pSquirrel<ScriptContext::CLIENT>->__sq_getinteger; - g_pSquirrel<ScriptContext::UI>->__sq_getfloat = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfloat; - g_pSquirrel<ScriptContext::UI>->__sq_getbool = g_pSquirrel<ScriptContext::CLIENT>->__sq_getbool; - g_pSquirrel<ScriptContext::UI>->__sq_get = g_pSquirrel<ScriptContext::CLIENT>->__sq_get; - g_pSquirrel<ScriptContext::UI>->__sq_getasset = g_pSquirrel<ScriptContext::CLIENT>->__sq_getasset; - g_pSquirrel<ScriptContext::UI>->__sq_getuserdata = g_pSquirrel<ScriptContext::CLIENT>->__sq_getuserdata; - g_pSquirrel<ScriptContext::UI>->__sq_getvector = g_pSquirrel<ScriptContext::CLIENT>->__sq_getvector; - g_pSquirrel<ScriptContext::UI>->__sq_getthisentity = g_pSquirrel<ScriptContext::CLIENT>->__sq_getthisentity; - g_pSquirrel<ScriptContext::UI>->__sq_getobject = g_pSquirrel<ScriptContext::CLIENT>->__sq_getobject; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_createuserdata = module.Offset(0x38D0).RCast<sq_createuserdataType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_setuserdatatypeid = module.Offset(0x6490).RCast<sq_setuserdatatypeidType>(); - g_pSquirrel<ScriptContext::UI>->__sq_createuserdata = g_pSquirrel<ScriptContext::CLIENT>->__sq_createuserdata; - g_pSquirrel<ScriptContext::UI>->__sq_setuserdatatypeid = g_pSquirrel<ScriptContext::CLIENT>->__sq_setuserdatatypeid; - - g_pSquirrel<ScriptContext::CLIENT>->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x3E49B0).RCast<sq_GetEntityConstantType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_getentityfrominstance = module.Offset(0x114F0).RCast<sq_getentityfrominstanceType>(); - g_pSquirrel<ScriptContext::UI>->__sq_GetEntityConstant_CBaseEntity = - g_pSquirrel<ScriptContext::CLIENT>->__sq_GetEntityConstant_CBaseEntity; - g_pSquirrel<ScriptContext::UI>->__sq_getentityfrominstance = g_pSquirrel<ScriptContext::CLIENT>->__sq_getentityfrominstance; - - // Message buffer stuff - g_pSquirrel<ScriptContext::UI>->messageBuffer = g_pSquirrel<ScriptContext::CLIENT>->messageBuffer; - g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x572FB0).RCast<sq_getfunctionType>(); - g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; - g_pSquirrel<ScriptContext::CLIENT>->__sq_stackinfos = module.Offset(0x35970).RCast<sq_stackinfosType>(); - g_pSquirrel<ScriptContext::UI>->__sq_stackinfos = g_pSquirrel<ScriptContext::CLIENT>->__sq_stackinfos; - - // Structs - g_pSquirrel<ScriptContext::CLIENT>->__sq_pushnewstructinstance = module.Offset(0x5400).RCast<sq_pushnewstructinstanceType>(); - g_pSquirrel<ScriptContext::CLIENT>->__sq_sealstructslot = module.Offset(0x5530).RCast<sq_sealstructslotType>(); - g_pSquirrel<ScriptContext::UI>->__sq_pushnewstructinstance = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushnewstructinstance; - g_pSquirrel<ScriptContext::UI>->__sq_sealstructslot = g_pSquirrel<ScriptContext::CLIENT>->__sq_sealstructslot; - - MAKEHOOK( - module.Offset(0x108E0), - &RegisterSquirrelFunctionHook<ScriptContext::CLIENT>, - &g_pSquirrel<ScriptContext::CLIENT>->RegisterSquirrelFunc); - g_pSquirrel<ScriptContext::UI>->RegisterSquirrelFunc = g_pSquirrel<ScriptContext::CLIENT>->RegisterSquirrelFunc; - - g_pSquirrel<ScriptContext::CLIENT>->logger = NS::log::SCRIPT_CL; - g_pSquirrel<ScriptContext::UI>->logger = NS::log::SCRIPT_UI; - - // uiscript_reset concommand: don't loop forever if compilation fails - module.Offset(0x3C6E4C).NOP(6); - - MAKEHOOK(module.Offset(0x8AD0), &sq_compiler_createHook<ScriptContext::CLIENT>, &sq_compiler_create<ScriptContext::CLIENT>); - - MAKEHOOK(module.Offset(0x12B00), &SQPrintHook<ScriptContext::CLIENT>, &SQPrint<ScriptContext::CLIENT>); - MAKEHOOK(module.Offset(0x12BA0), &SQPrintHook<ScriptContext::UI>, &SQPrint<ScriptContext::UI>); - - MAKEHOOK(module.Offset(0x26130), &CreateNewVMHook<ScriptContext::CLIENT>, &CreateNewVM<ScriptContext::CLIENT>); - MAKEHOOK(module.Offset(0x26E70), &DestroyVMHook<ScriptContext::CLIENT>, &DestroyVM<ScriptContext::CLIENT>); - MAKEHOOK(module.Offset(0x79A50), &ScriptCompileErrorHook<ScriptContext::CLIENT>, &SQCompileError<ScriptContext::CLIENT>); - - MAKEHOOK(module.Offset(0x10190), &CallScriptInitCallbackHook<ScriptContext::CLIENT>, &CallScriptInitCallback<ScriptContext::CLIENT>); - - MAKEHOOK(module.Offset(0xE3B0), &CSquirrelVM_initHook<ScriptContext::CLIENT>, &CSquirrelVM_init<ScriptContext::CLIENT>); - - RegisterConCommand("script_client", ConCommand_script<ScriptContext::CLIENT>, "Executes script code on the client vm", FCVAR_CLIENTDLL); - RegisterConCommand("script_ui", ConCommand_script<ScriptContext::UI>, "Executes script code on the ui vm", FCVAR_CLIENTDLL); - - StubUnsafeSQFuncs<ScriptContext::CLIENT>(); - StubUnsafeSQFuncs<ScriptContext::UI>(); - - g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x6CB0).RCast<sq_getfunctionType>(); - g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; - - SquirrelFunctions s = {}; - g_pSquirrel<ScriptContext::CLIENT>->GenerateSquirrelFunctionsStruct(&s); - g_pPluginManager->InformSQVMLoad(ScriptContext::CLIENT, &s); -} - -ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) -{ - AUTOHOOK_DISPATCH_MODULE(server.dll) - - g_pSquirrel<ScriptContext::SERVER>->__sq_defconst = module.Offset(0x1F550).RCast<sq_defconstType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_compilebuffer = module.Offset(0x3110).RCast<sq_compilebufferType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushroottable = module.Offset(0x5840).RCast<sq_pushroottableType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_call = module.Offset(0x8620).RCast<sq_callType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_compilefile = module.Offset(0x1CD80).RCast<sq_compilefileType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_newarray = module.Offset(0x39F0).RCast<sq_newarrayType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_arrayappend = module.Offset(0x3C70).RCast<sq_arrayappendType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_newtable = module.Offset(0x3960).RCast<sq_newtableType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_newslot = module.Offset(0x7080).RCast<sq_newslotType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_pushstring = module.Offset(0x3440).RCast<sq_pushstringType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushinteger = module.Offset(0x36A0).RCast<sq_pushintegerType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushfloat = module.Offset(0x3800).RCast<sq_pushfloatType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushbool = module.Offset(0x3710).RCast<sq_pushboolType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushasset = module.Offset(0x3560).RCast<sq_pushassetType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushvector = module.Offset(0x3780).RCast<sq_pushvectorType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_pushobject = module.Offset(0x83A0).RCast<sq_pushobjectType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_raiseerror = module.Offset(0x8440).RCast<sq_raiseerrorType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_getstring = module.Offset(0x60A0).RCast<sq_getstringType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getinteger = module.Offset(0x60C0).RCast<sq_getintegerType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getfloat = module.Offset(0x60E0).RCast<sq_getfloatType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getbool = module.Offset(0x6110).RCast<sq_getboolType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getasset = module.Offset(0x5FF0).RCast<sq_getassetType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getuserdata = module.Offset(0x63B0).RCast<sq_getuserdataType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getvector = module.Offset(0x6120).RCast<sq_getvectorType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_get = module.Offset(0x7C00).RCast<sq_getType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_getthisentity = module.Offset(0x203B0).RCast<sq_getthisentityType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getobject = module.Offset(0x6140).RCast<sq_getobjectType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_createuserdata = module.Offset(0x38D0).RCast<sq_createuserdataType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_setuserdatatypeid = module.Offset(0x6470).RCast<sq_setuserdatatypeidType>(); - - g_pSquirrel<ScriptContext::SERVER>->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x418AF0).RCast<sq_GetEntityConstantType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_getentityfrominstance = module.Offset(0x1E920).RCast<sq_getentityfrominstanceType>(); - - g_pSquirrel<ScriptContext::SERVER>->logger = NS::log::SCRIPT_SV; - // Message buffer stuff - g_pSquirrel<ScriptContext::SERVER>->__sq_getfunction = module.Offset(0x6C85).RCast<sq_getfunctionType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_stackinfos = module.Offset(0x35920).RCast<sq_stackinfosType>(); - - // Structs - g_pSquirrel<ScriptContext::SERVER>->__sq_pushnewstructinstance = module.Offset(0x53e0).RCast<sq_pushnewstructinstanceType>(); - g_pSquirrel<ScriptContext::SERVER>->__sq_sealstructslot = module.Offset(0x5510).RCast<sq_sealstructslotType>(); - - MAKEHOOK( - module.Offset(0x1DD10), - &RegisterSquirrelFunctionHook<ScriptContext::SERVER>, - &g_pSquirrel<ScriptContext::SERVER>->RegisterSquirrelFunc); - - MAKEHOOK(module.Offset(0x8AA0), &sq_compiler_createHook<ScriptContext::SERVER>, &sq_compiler_create<ScriptContext::SERVER>); - - MAKEHOOK(module.Offset(0x1FE90), &SQPrintHook<ScriptContext::SERVER>, &SQPrint<ScriptContext::SERVER>); - MAKEHOOK(module.Offset(0x260E0), &CreateNewVMHook<ScriptContext::SERVER>, &CreateNewVM<ScriptContext::SERVER>); - MAKEHOOK(module.Offset(0x26E20), &DestroyVMHook<ScriptContext::SERVER>, &DestroyVM<ScriptContext::SERVER>); - MAKEHOOK(module.Offset(0x799E0), &ScriptCompileErrorHook<ScriptContext::SERVER>, &SQCompileError<ScriptContext::SERVER>); - MAKEHOOK(module.Offset(0x1D5C0), &CallScriptInitCallbackHook<ScriptContext::SERVER>, &CallScriptInitCallback<ScriptContext::SERVER>); - MAKEHOOK(module.Offset(0x1B7E0), &CSquirrelVM_initHook<ScriptContext::SERVER>, &CSquirrelVM_init<ScriptContext::SERVER>); - // FCVAR_CHEAT and FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS 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( - "script", - ConCommand_script<ScriptContext::SERVER>, - "Executes script code on the server vm", - FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS | FCVAR_CHEAT); - - StubUnsafeSQFuncs<ScriptContext::SERVER>(); - - SquirrelFunctions s = {}; - g_pSquirrel<ScriptContext::SERVER>->GenerateSquirrelFunctionsStruct(&s); - g_pPluginManager->InformSQVMLoad(ScriptContext::SERVER, &s); -} - -void InitialiseSquirrelManagers() -{ - g_pSquirrel<ScriptContext::CLIENT> = new SquirrelManager<ScriptContext::CLIENT>; - g_pSquirrel<ScriptContext::UI> = new SquirrelManager<ScriptContext::UI>; - g_pSquirrel<ScriptContext::SERVER> = new SquirrelManager<ScriptContext::SERVER>; -} diff --git a/NorthstarDLL/squirrel/squirrel.h b/NorthstarDLL/squirrel/squirrel.h deleted file mode 100644 index a4932044..00000000 --- a/NorthstarDLL/squirrel/squirrel.h +++ /dev/null @@ -1,526 +0,0 @@ -#pragma once - -#include "squirrelclasstypes.h" -#include "squirrelautobind.h" -#include "core/math/vector.h" -#include "plugins/plugin_abi.h" -#include "mods/modmanager.h" - -/* - definitions from hell - required to function -*/ - -template <ScriptContext context, typename T> inline void SqRecurseArgs(FunctionVector& v, T& arg); - -template <ScriptContext context, typename T, typename... Args> inline void SqRecurseArgs(FunctionVector& v, T& arg, Args... args); - -/* - sanity below -*/ - -// stolen from ttf2sdk: sqvm types -typedef float SQFloat; -typedef long SQInteger; -typedef unsigned long SQUnsignedInteger; -typedef char SQChar; -typedef SQUnsignedInteger SQBool; - -static constexpr int operator&(ScriptContext first, ScriptContext second) -{ - return first == second; -} - -static constexpr int operator&(int first, ScriptContext second) -{ - return first & (1 << static_cast<int>(second)); -} - -static constexpr int operator|(ScriptContext first, ScriptContext second) -{ - return (1 << static_cast<int>(first)) + (1 << static_cast<int>(second)); -} - -static constexpr int operator|(int first, ScriptContext second) -{ - return first + (1 << static_cast<int>(second)); -} - -const char* GetContextName(ScriptContext context); -const char* GetContextName_Short(ScriptContext context); -eSQReturnType SQReturnTypeFromString(const char* pReturnType); -const char* SQTypeNameFromID(const int iTypeId); - -void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function, void* userdata); - -ScriptContext ScriptContextFromString(std::string string); - -namespace NS::log -{ - template <ScriptContext context> std::shared_ptr<spdlog::logger> squirrel_logger(); -}; // namespace NS::log - -// This base class means that only the templated functions have to be rebuilt for each template instance -// Cuts down on compile time by ~5 seconds -class SquirrelManagerBase -{ -protected: - std::vector<SQFuncRegistration*> m_funcRegistrations; - -public: - CSquirrelVM* m_pSQVM; - std::map<std::string, SQFunction> m_funcOverrides = {}; - std::map<std::string, SQFunction> m_funcOriginals = {}; - - bool m_bFatalCompilationErrors = false; - - std::shared_ptr<spdlog::logger> logger; - -#pragma region SQVM funcs - RegisterSquirrelFuncType RegisterSquirrelFunc; - sq_defconstType __sq_defconst; - - sq_compilebufferType __sq_compilebuffer; - sq_callType __sq_call; - sq_raiseerrorType __sq_raiseerror; - sq_compilefileType __sq_compilefile; - - sq_newarrayType __sq_newarray; - sq_arrayappendType __sq_arrayappend; - - sq_newtableType __sq_newtable; - sq_newslotType __sq_newslot; - - sq_pushroottableType __sq_pushroottable; - sq_pushstringType __sq_pushstring; - sq_pushintegerType __sq_pushinteger; - sq_pushfloatType __sq_pushfloat; - sq_pushboolType __sq_pushbool; - sq_pushassetType __sq_pushasset; - sq_pushvectorType __sq_pushvector; - sq_pushobjectType __sq_pushobject; - - sq_getstringType __sq_getstring; - sq_getintegerType __sq_getinteger; - sq_getfloatType __sq_getfloat; - sq_getboolType __sq_getbool; - sq_getType __sq_get; - sq_getassetType __sq_getasset; - sq_getuserdataType __sq_getuserdata; - sq_getvectorType __sq_getvector; - sq_getthisentityType __sq_getthisentity; - sq_getobjectType __sq_getobject; - - sq_stackinfosType __sq_stackinfos; - - sq_createuserdataType __sq_createuserdata; - sq_setuserdatatypeidType __sq_setuserdatatypeid; - sq_getfunctionType __sq_getfunction; - - sq_getentityfrominstanceType __sq_getentityfrominstance; - sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; - - sq_pushnewstructinstanceType __sq_pushnewstructinstance; - sq_sealstructslotType __sq_sealstructslot; - -#pragma endregion - -#pragma region SQVM func wrappers - inline void defconst(CSquirrelVM* sqvm, const SQChar* pName, int nValue) - { - __sq_defconst(sqvm, pName, nValue); - } - - inline SQRESULT - compilebuffer(CompileBufferState* bufferState, const SQChar* bufferName = "unnamedbuffer", const SQBool bShouldThrowError = false) - { - return __sq_compilebuffer(m_pSQVM->sqvm, bufferState, bufferName, -1, bShouldThrowError); - } - - inline SQRESULT _call(HSquirrelVM* sqvm, const SQInteger args) - { - return __sq_call(sqvm, args + 1, false, false); - } - - inline SQInteger raiseerror(HSquirrelVM* sqvm, const SQChar* sError) - { - return __sq_raiseerror(sqvm, sError); - } - - inline bool compilefile(CSquirrelVM* sqvm, const char* path, const char* name, int a4) - { - return __sq_compilefile(sqvm, path, name, a4); - } - - inline void newarray(HSquirrelVM* sqvm, const SQInteger stackpos = 0) - { - __sq_newarray(sqvm, stackpos); - } - - inline SQRESULT arrayappend(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_arrayappend(sqvm, stackpos); - } - - inline SQRESULT newtable(HSquirrelVM* sqvm) - { - return __sq_newtable(sqvm); - } - - inline SQRESULT newslot(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic) - { - return __sq_newslot(sqvm, idx, bStatic); - } - - inline void pushroottable(HSquirrelVM* sqvm) - { - __sq_pushroottable(sqvm); - } - - inline void pushstring(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) - { - __sq_pushstring(sqvm, sVal, length); - } - - inline void pushinteger(HSquirrelVM* sqvm, const SQInteger iVal) - { - __sq_pushinteger(sqvm, iVal); - } - - inline void pushfloat(HSquirrelVM* sqvm, const SQFloat flVal) - { - __sq_pushfloat(sqvm, flVal); - } - - inline void pushbool(HSquirrelVM* sqvm, const SQBool bVal) - { - __sq_pushbool(sqvm, bVal); - } - - inline void pushasset(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) - { - __sq_pushasset(sqvm, sVal, length); - } - - inline void pushvector(HSquirrelVM* sqvm, const Vector3 pVal) - { - __sq_pushvector(sqvm, (float*)&pVal); - } - - inline void pushobject(HSquirrelVM* sqvm, SQObject* obj) - { - __sq_pushobject(sqvm, obj); - } - - inline const SQChar* getstring(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getstring(sqvm, stackpos); - } - - inline SQInteger getinteger(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getinteger(sqvm, stackpos); - } - - inline SQFloat getfloat(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getfloat(sqvm, stackpos); - } - - inline SQBool getbool(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getbool(sqvm, stackpos); - } - - inline SQRESULT get(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_get(sqvm, stackpos); - } - - inline Vector3 getvector(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return *(Vector3*)__sq_getvector(sqvm, stackpos); - } - - inline int sq_getfunction(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature) - { - return __sq_getfunction(sqvm, name, returnObj, signature); - } - - inline SQRESULT getasset(HSquirrelVM* sqvm, const SQInteger stackpos, const char** result) - { - return __sq_getasset(sqvm, stackpos, result); - } - - inline long long sq_stackinfos(HSquirrelVM* sqvm, int level, SQStackInfos& out) - { - return __sq_stackinfos(sqvm, level, &out, sqvm->_callstacksize); - } - - inline Mod* getcallingmod(HSquirrelVM* sqvm, int depth = 0) - { - SQStackInfos stackInfo {}; - if (1 + depth >= sqvm->_callstacksize) - { - return nullptr; - } - sq_stackinfos(sqvm, 1 + depth, stackInfo); - std::string sourceName = stackInfo._sourceName; - std::replace(sourceName.begin(), sourceName.end(), '/', '\\'); - std::string filename = g_pModManager->NormaliseModFilePath(fs::path("scripts\\vscripts\\" + sourceName)); - if (auto res = g_pModManager->m_ModFiles.find(filename); res != g_pModManager->m_ModFiles.end()) - { - return res->second.m_pOwningMod; - } - return nullptr; - } - template <typename T> inline SQRESULT getuserdata(HSquirrelVM* sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) - { - return __sq_getuserdata(sqvm, stackpos, (void**)data, typeId); // this sometimes crashes idk - } - - template <typename T> inline T* createuserdata(HSquirrelVM* sqvm, SQInteger size) - { - void* ret = __sq_createuserdata(sqvm, size); - memset(ret, 0, size); - return (T*)ret; - } - - inline SQRESULT setuserdatatypeid(HSquirrelVM* sqvm, const SQInteger stackpos, uint64_t typeId) - { - return __sq_setuserdatatypeid(sqvm, stackpos, typeId); - } - - template <typename T> inline SQBool getthisentity(HSquirrelVM* sqvm, T* ppEntity) - { - return __sq_getthisentity(sqvm, (void**)ppEntity); - } - - template <typename T> inline T* getentity(HSquirrelVM* sqvm, SQInteger iStackPos) - { - SQObject obj; - __sq_getobject(sqvm, iStackPos, &obj); - - // there are entity constants for other types, but seemingly CBaseEntity's is the only one needed - return (T*)__sq_getentityfrominstance(m_pSQVM, &obj, __sq_GetEntityConstant_CBaseEntity()); - } - - inline SQRESULT pushnewstructinstance(HSquirrelVM* sqvm, const int fieldCount) - { - return __sq_pushnewstructinstance(sqvm, fieldCount); - } - - inline SQRESULT sealstructslot(HSquirrelVM* sqvm, const int fieldIndex) - { - return __sq_sealstructslot(sqvm, fieldIndex); - } -#pragma endregion -}; - -template <ScriptContext context> class SquirrelManager : public virtual SquirrelManagerBase -{ -public: -#pragma region MessageBuffer - SquirrelMessageBuffer* messageBuffer; - - template <typename... Args> SquirrelMessage AsyncCall(std::string funcname, Args... args) - { - // This function schedules a call to be executed on the next frame - // This is useful for things like threads and plugins, which do not run on the main thread - FunctionVector functionVector; - SqRecurseArgs<context>(functionVector, args...); - SquirrelMessage message = {funcname, functionVector}; - messageBuffer->push(message); - return message; - } - - SquirrelMessage AsyncCall(std::string funcname) - { - // This function schedules a call to be executed on the next frame - // This is useful for things like threads and plugins, which do not run on the main thread - FunctionVector functionVector = {}; - SquirrelMessage message = {funcname, functionVector}; - messageBuffer->push(message); - return message; - } - - SQRESULT Call(const char* funcname) - { - // Warning! - // This function assumes the squirrel VM is stopped/blocked at the moment of call - // Calling this function while the VM is running is likely to result in a crash due to stack destruction - // If you want to call into squirrel asynchronously, use `AsyncCall` instead - - if (!m_pSQVM || !m_pSQVM->sqvm) - { - spdlog::error( - "{} was called on context {} while VM was not initialized. This will crash", __FUNCTION__, GetContextName(context)); - } - - SQObject functionobj {}; - int result = sq_getfunction(m_pSQVM->sqvm, funcname, &functionobj, 0); - if (result != 0) // This func returns 0 on success for some reason - { - NS::log::squirrel_logger<context>()->error("Call was unable to find function with name '{}'. Is it global?", funcname); - return SQRESULT_ERROR; - } - pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object - pushroottable(m_pSQVM->sqvm); // Push root table - return _call(m_pSQVM->sqvm, 0); - } - - template <typename... Args> SQRESULT Call(const char* funcname, Args... args) - { - // Warning! - // This function assumes the squirrel VM is stopped/blocked at the moment of call - // Calling this function while the VM is running is likely to result in a crash due to stack destruction - // If you want to call into squirrel asynchronously, use `schedule_call` instead - if (!m_pSQVM || !m_pSQVM->sqvm) - { - spdlog::error( - "{} was called on context {} while VM was not initialized. This will crash", __FUNCTION__, GetContextName(context)); - } - SQObject functionobj {}; - int result = sq_getfunction(m_pSQVM->sqvm, funcname, &functionobj, 0); - if (result != 0) // This func returns 0 on success for some reason - { - NS::log::squirrel_logger<context>()->error("Call was unable to find function with name '{}'. Is it global?", funcname); - return SQRESULT_ERROR; - } - pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object - pushroottable(m_pSQVM->sqvm); // Push root table - - FunctionVector functionVector; - SqRecurseArgs<context>(functionVector, args...); - - for (auto& v : functionVector) - { - // Execute lambda to push arg to stack - v(); - } - - return _call(m_pSQVM->sqvm, functionVector.size()); - } - -#pragma endregion - -public: - SquirrelManager() - { - m_pSQVM = nullptr; - } - - void VMCreated(CSquirrelVM* newSqvm); - void VMDestroyed(); - void ExecuteCode(const char* code); - void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func); - SQRESULT setupfunc(const SQChar* funcname); - void AddFuncOverride(std::string name, SQFunction func); - void ProcessMessageBuffer(); - void GenerateSquirrelFunctionsStruct(SquirrelFunctions* s); -}; - -template <ScriptContext context> SquirrelManager<context>* g_pSquirrel; - -void InitialiseSquirrelManagers(); - -/* - Beware all ye who enter below. - This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. - What is here was dangerous and repulsive to us. This message is a warning about danger. -*/ - -#pragma region MessageBuffer templates - -// Clang-formatting makes this whole thing unreadable -// clang-format off - -#ifndef MessageBufferFuncs -#define MessageBufferFuncs -// Bools -template <ScriptContext context, typename T> -requires std::convertible_to<T, bool> && (!std::is_floating_point_v<T>) && (!std::convertible_to<T, std::string>) && (!std::convertible_to<T, int>) -inline VoidFunction SQMessageBufferPushArg(T& arg) { - return [arg]{ g_pSquirrel<context>->pushbool(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<bool>(arg)); }; -} -// Vectors -template <ScriptContext context> -inline VoidFunction SQMessageBufferPushArg(Vector3& arg) { - return [arg]{ g_pSquirrel<context>->pushvector(g_pSquirrel<context>->m_pSQVM->sqvm, arg); }; -} -// Vectors -template <ScriptContext context> -inline VoidFunction SQMessageBufferPushArg(SQObject* arg) { - return [arg]{ g_pSquirrel<context>->pushSQObject(g_pSquirrel<context>->m_pSQVM->sqvm, arg); }; -} -// Ints -template <ScriptContext context, typename T> -requires std::convertible_to<T, int> && (!std::is_floating_point_v<T>) -inline VoidFunction SQMessageBufferPushArg(T& arg) { - return [arg]{ g_pSquirrel<context>->pushinteger(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<int>(arg)); }; -} -// Floats -template <ScriptContext context, typename T> -requires std::convertible_to<T, float> && (std::is_floating_point_v<T>) -inline VoidFunction SQMessageBufferPushArg(T& arg) { - return [arg]{ g_pSquirrel<context>->pushfloat(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<float>(arg)); }; -} -// Strings -template <ScriptContext context, typename T> -requires (std::convertible_to<T, std::string> || std::is_constructible_v<std::string, T>) -inline VoidFunction SQMessageBufferPushArg(T& arg) { - auto converted = std::string(arg); - return [converted]{ g_pSquirrel<context>->pushstring(g_pSquirrel<context>->m_pSQVM->sqvm, converted.c_str(), converted.length()); }; -} -// Assets -template <ScriptContext context> -inline VoidFunction SQMessageBufferPushArg(SquirrelAsset& arg) { - return [arg]{ g_pSquirrel<context>->pushasset(g_pSquirrel<context>->m_pSQVM->sqvm, arg.path.c_str(), arg.path.length()); }; -} -// Maps -template <ScriptContext context, typename T> -requires is_iterable<T> -inline VoidFunction SQMessageBufferPushArg(T& arg) { - FunctionVector localv = {}; - localv.push_back([]{g_pSquirrel<context>->newarray(g_pSquirrel<context>->m_pSQVM->sqvm, 0);}); - - for (const auto& item : arg) { - localv.push_back(SQMessageBufferPushArg<context>(item)); - localv.push_back([]{g_pSquirrel<context>->arrayappend(g_pSquirrel<context>->m_pSQVM->sqvm, -2);}); - } - - return [localv] { for (auto& func : localv) { func(); } }; -} -// Vectors -template <ScriptContext context, typename T> -requires is_map<T> -inline VoidFunction SQMessageBufferPushArg(T& map) { - FunctionVector localv = {}; - localv.push_back([]{g_pSquirrel<context>->newtable(g_pSquirrel<context>->m_pSQVM->sqvm);}); - - for (const auto& item : map) { - localv.push_back(SQMessageBufferPushArg<context>(item.first)); - localv.push_back(SQMessageBufferPushArg<context>(item.second)); - localv.push_back([]{g_pSquirrel<context>->newslot(g_pSquirrel<context>->m_pSQVM->sqvm, -3, false);}); - } - - return [localv]{ for (auto& func : localv) { func(); } }; -} - -template <ScriptContext context, typename T> -inline void SqRecurseArgs(FunctionVector& v, T& arg) { - v.push_back(SQMessageBufferPushArg<context>(arg)); -} - -// This function is separated from the PushArg function so as to not generate too many template instances -// This is the main function responsible for unrolling the argument pack -template <ScriptContext context, typename T, typename... Args> -inline void SqRecurseArgs(FunctionVector& v, T& arg, Args... args) { - v.push_back(SQMessageBufferPushArg<context>(arg)); - SqRecurseArgs<context>(v, args...); -} - -// clang-format on -#endif - -#pragma endregion diff --git a/NorthstarDLL/squirrel/squirrelautobind.cpp b/NorthstarDLL/squirrel/squirrelautobind.cpp deleted file mode 100644 index c15240f5..00000000 --- a/NorthstarDLL/squirrel/squirrelautobind.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "squirrelautobind.h" - -SquirrelAutoBindContainer* g_pSqAutoBindContainer; - -ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrelAutoBind, ClientSquirrel, (CModule module)) -{ - spdlog::info("ClientSquirrelAutoBInd AutoBindFuncsVectorsize {}", g_pSqAutoBindContainer->clientSqAutoBindFuncs.size()); - for (auto& autoBindFunc : g_pSqAutoBindContainer->clientSqAutoBindFuncs) - { - autoBindFunc(); - } -} - -ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrelAutoBind, ServerSquirrel, (CModule module)) -{ - for (auto& autoBindFunc : g_pSqAutoBindContainer->serverSqAutoBindFuncs) - { - autoBindFunc(); - } -} diff --git a/NorthstarDLL/squirrel/squirrelautobind.h b/NorthstarDLL/squirrel/squirrelautobind.h deleted file mode 100644 index 0fc599f3..00000000 --- a/NorthstarDLL/squirrel/squirrelautobind.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once -#include <vector> - -typedef void (*SqAutoBindFunc)(); - -class SquirrelAutoBindContainer -{ -public: - std::vector<std::function<void()>> clientSqAutoBindFuncs; - std::vector<std::function<void()>> serverSqAutoBindFuncs; -}; - -extern SquirrelAutoBindContainer* g_pSqAutoBindContainer; - -class __squirrelautobind; - -#define ADD_SQFUNC(returnType, funcName, argTypes, helpText, runOnContext) \ - template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ - namespace \ - { \ - __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ - []() \ - { \ - if constexpr ((runOnContext)&ScriptContext::UI) \ - g_pSquirrel<ScriptContext::UI>->AddFuncRegistration( \ - returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::UI >); \ - if constexpr ((runOnContext)&ScriptContext::CLIENT) \ - g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration( \ - returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::CLIENT >); \ - }, \ - []() \ - { \ - if constexpr ((runOnContext)&ScriptContext::SERVER) \ - g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration( \ - returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ - }); \ - } \ - template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) - -#define REPLACE_SQFUNC(funcName, runOnContext) \ - template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ - namespace \ - { \ - __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ - []() \ - { \ - if constexpr ((runOnContext)&ScriptContext::UI) \ - g_pSquirrel<ScriptContext::UI>->AddFuncOverride(__STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::UI >); \ - if constexpr ((runOnContext)&ScriptContext::CLIENT) \ - g_pSquirrel<ScriptContext::CLIENT>->AddFuncOverride( \ - __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::CLIENT >); \ - }, \ - []() \ - { \ - if constexpr ((runOnContext)&ScriptContext::SERVER) \ - g_pSquirrel<ScriptContext::SERVER>->AddFuncOverride( \ - __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ - }); \ - } \ - template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) - -class __squirrelautobind -{ -public: - __squirrelautobind() = delete; - - __squirrelautobind(std::function<void()> clientAutoBindFunc, std::function<void()> serverAutoBindFunc) - { - // Bit hacky but we can't initialise this normally since this gets run automatically on load - if (g_pSqAutoBindContainer == nullptr) - g_pSqAutoBindContainer = new SquirrelAutoBindContainer(); - - g_pSqAutoBindContainer->clientSqAutoBindFuncs.push_back(clientAutoBindFunc); - g_pSqAutoBindContainer->serverSqAutoBindFuncs.push_back(serverAutoBindFunc); - } -}; diff --git a/NorthstarDLL/squirrel/squirrelclasstypes.h b/NorthstarDLL/squirrel/squirrelclasstypes.h deleted file mode 100644 index cd777551..00000000 --- a/NorthstarDLL/squirrel/squirrelclasstypes.h +++ /dev/null @@ -1,248 +0,0 @@ -#pragma once -#include "squirreldatatypes.h" - -#include <queue> - -enum SQRESULT : SQInteger -{ - SQRESULT_ERROR = -1, - SQRESULT_NULL = 0, - SQRESULT_NOTNULL = 1, -}; - -typedef SQRESULT (*SQFunction)(HSquirrelVM* sqvm); - -enum class eSQReturnType -{ - Float = 0x1, - Vector = 0x3, - Integer = 0x5, - Boolean = 0x6, - Entity = 0xD, - String = 0x21, - Default = 0x20, - Arrays = 0x25, - Asset = 0x28, - Table = 0x26, -}; - -const std::map<SQRESULT, const char*> PrintSQRESULT = { - {SQRESULT::SQRESULT_ERROR, "SQRESULT_ERROR"}, - {SQRESULT::SQRESULT_NULL, "SQRESULT_NULL"}, - {SQRESULT::SQRESULT_NOTNULL, "SQRESULT_NOTNULL"}}; - -struct CompileBufferState -{ - const SQChar* buffer; - const SQChar* bufferPlusLength; - const SQChar* bufferAgain; - - CompileBufferState(const std::string& code) - { - buffer = code.c_str(); - bufferPlusLength = code.c_str() + code.size(); - bufferAgain = code.c_str(); - } -}; - -struct SQFuncRegistration -{ - const char* squirrelFuncName; - const char* cppFuncName; - const char* helpText; - const char* returnTypeString; - const char* argTypes; - uint32_t unknown1; - uint32_t devLevel; - const char* shortNameMaybe; - uint32_t unknown2; - eSQReturnType returnType; - uint32_t* externalBufferPointer; - uint64_t externalBufferSize; - uint64_t unknown3; - uint64_t unknown4; - SQFunction funcPtr; - - SQFuncRegistration() - { - memset(this, 0, sizeof(SQFuncRegistration)); - this->returnType = eSQReturnType::Default; - } -}; - -enum class ScriptContext : int -{ - INVALID = -1, - SERVER, - CLIENT, - UI, -}; - -typedef std::vector<std::function<void()>> FunctionVector; -typedef std::function<void()> VoidFunction; - -// clang-format off -template <typename T> -concept is_map = - // Simple maps - std::same_as<T, std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>> || - std::same_as<T, std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> || - - // Nested maps - std::same_as < - std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, - std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> - > || - std::same_as < - std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, - std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> - > || - std::same_as < - std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, - std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> - > || - std::same_as < - std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, - std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> - > -; - -template<typename T> -concept is_iterable = requires(std::ranges::range_value_t<T> x) -{ - x.begin(); // must have `x.begin()` - x.end(); // and `x.end()` -}; - -// clang-format on - -typedef int (*SquirrelMessage_External_Pop)(HSquirrelVM* sqvm, void* userdata); -typedef void (*sq_schedule_call_externalType)( - ScriptContext context, const char* funcname, SquirrelMessage_External_Pop function, void* userdata); - -class SquirrelMessage -{ -public: - std::string functionName; - FunctionVector args; - bool isExternal = false; - void* userdata = NULL; - SquirrelMessage_External_Pop externalFunc = NULL; -}; - -class SquirrelMessageBuffer -{ - -private: - std::queue<SquirrelMessage> messages = {}; - -public: - std::mutex mutex; - std::optional<SquirrelMessage> pop() - { - std::lock_guard<std::mutex> guard(mutex); - if (!messages.empty()) - { - auto message = messages.front(); - messages.pop(); - return message; - } - else - { - return std::nullopt; - } - } - - void unwind() - { - auto maybeMessage = this->pop(); - if (!maybeMessage) - { - spdlog::error("Plugin tried consuming SquirrelMessage while buffer was empty"); - return; - } - auto message = maybeMessage.value(); - for (auto& v : message.args) - { - // Execute lambda to push arg to stack - v(); - } - } - - void push(SquirrelMessage message) - { - std::lock_guard<std::mutex> guard(mutex); - messages.push(message); - } -}; - -// Super simple wrapper class to allow pushing Assets via call -class SquirrelAsset -{ -public: - std::string path; - SquirrelAsset(std::string path) : path(path) {}; -}; - -#pragma region TypeDefs - -// core sqvm funcs -typedef int64_t (*RegisterSquirrelFuncType)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); -typedef void (*sq_defconstType)(CSquirrelVM* sqvm, const SQChar* name, int value); - -typedef SQRESULT (*sq_compilebufferType)( - HSquirrelVM* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, SQBool bShouldThrowError); -typedef SQRESULT (*sq_callType)(HSquirrelVM* sqvm, SQInteger iArgs, SQBool bShouldReturn, SQBool bThrowError); -typedef SQInteger (*sq_raiseerrorType)(HSquirrelVM* sqvm, const SQChar* pError); -typedef bool (*sq_compilefileType)(CSquirrelVM* sqvm, const char* path, const char* name, int a4); - -// sq stack array funcs -typedef void (*sq_newarrayType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQRESULT (*sq_arrayappendType)(HSquirrelVM* sqvm, SQInteger iStackpos); - -// sq table funcs -typedef SQRESULT (*sq_newtableType)(HSquirrelVM* sqvm); -typedef SQRESULT (*sq_newslotType)(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic); - -// sq stack push funcs -typedef void (*sq_pushroottableType)(HSquirrelVM* sqvm); -typedef void (*sq_pushstringType)(HSquirrelVM* sqvm, const SQChar* pStr, SQInteger iLength); -typedef void (*sq_pushintegerType)(HSquirrelVM* sqvm, SQInteger i); -typedef void (*sq_pushfloatType)(HSquirrelVM* sqvm, SQFloat f); -typedef void (*sq_pushboolType)(HSquirrelVM* sqvm, SQBool b); -typedef void (*sq_pushassetType)(HSquirrelVM* sqvm, const SQChar* str, SQInteger iLength); -typedef void (*sq_pushvectorType)(HSquirrelVM* sqvm, const SQFloat* pVec); -typedef void (*sq_pushobjectType)(HSquirrelVM* sqvm, SQObject* pVec); - -// sq stack get funcs -typedef const SQChar* (*sq_getstringType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQInteger (*sq_getintegerType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQFloat (*sq_getfloatType)(HSquirrelVM*, SQInteger iStackpos); -typedef SQBool (*sq_getboolType)(HSquirrelVM*, SQInteger iStackpos); -typedef SQRESULT (*sq_getType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQRESULT (*sq_getassetType)(HSquirrelVM* sqvm, SQInteger iStackpos, const char** pResult); -typedef SQRESULT (*sq_getuserdataType)(HSquirrelVM* sqvm, SQInteger iStackpos, void** pData, uint64_t* pTypeId); -typedef SQFloat* (*sq_getvectorType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQBool (*sq_getthisentityType)(HSquirrelVM*, void** ppEntity); -typedef void (*sq_getobjectType)(HSquirrelVM*, SQInteger iStackPos, SQObject* pOutObj); - -typedef long long (*sq_stackinfosType)(HSquirrelVM* sqvm, int iLevel, SQStackInfos* pOutObj, int iCallStackSize); - -// sq stack userpointer funcs -typedef void* (*sq_createuserdataType)(HSquirrelVM* sqvm, SQInteger iSize); -typedef SQRESULT (*sq_setuserdatatypeidType)(HSquirrelVM* sqvm, SQInteger iStackpos, uint64_t iTypeId); - -// sq misc entity funcs -typedef void* (*sq_getentityfrominstanceType)(CSquirrelVM* sqvm, SQObject* pInstance, char** ppEntityConstant); -typedef char** (*sq_GetEntityConstantType)(); - -typedef int (*sq_getfunctionType)(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature); - -// structs -typedef SQRESULT (*sq_pushnewstructinstanceType)(HSquirrelVM* sqvm, int fieldCount); -typedef SQRESULT (*sq_sealstructslotType)(HSquirrelVM* sqvm, int slotIndex); - -#pragma endregion - -// These "external" versions of the types are for plugins -typedef int64_t (*RegisterSquirrelFuncType_External)(ScriptContext context, SQFuncRegistration* funcReg, char unknown); diff --git a/NorthstarDLL/squirrel/squirreldatatypes.h b/NorthstarDLL/squirrel/squirreldatatypes.h deleted file mode 100644 index 84ab15ec..00000000 --- a/NorthstarDLL/squirrel/squirreldatatypes.h +++ /dev/null @@ -1,501 +0,0 @@ -#pragma once -/* - This file has been generated by IDA. - It contains local type definitions from - the type library 'server.dll' -*/ - -struct HSquirrelVM; -struct CallInfo; -struct SQTable; -struct SQString; -struct SQFunctionProto; -struct SQClosure; -struct SQSharedState; -struct StringTable; -struct SQStructInstance; -struct SQStructDef; -struct SQNativeClosure; -struct SQArray; -struct tableNode; -struct SQUserData; -struct CSquirrelVM; - -typedef void (*releasehookType)(void* val, int size); - -// stolen from ttf2sdk: sqvm types -typedef float SQFloat; -typedef long SQInteger; -typedef unsigned long SQUnsignedInteger; -typedef char SQChar; -typedef SQUnsignedInteger SQBool; - -/* 127 */ -enum SQObjectType : int -{ - _RT_NULL = 0x1, - _RT_INTEGER = 0x2, - _RT_FLOAT = 0x4, - _RT_BOOL = 0x8, - _RT_STRING = 0x10, - _RT_TABLE = 0x20, - _RT_ARRAY = 0x40, - _RT_USERDATA = 0x80, - _RT_CLOSURE = 0x100, - _RT_NATIVECLOSURE = 0x200, - _RT_GENERATOR = 0x400, - OT_USERPOINTER = 0x800, - _RT_USERPOINTER = 0x800, - _RT_THREAD = 0x1000, - _RT_FUNCPROTO = 0x2000, - _RT_CLASS = 0x4000, - _RT_INSTANCE = 0x8000, - _RT_WEAKREF = 0x10000, - OT_VECTOR = 0x40000, - SQOBJECT_CANBEFALSE = 0x1000000, - OT_NULL = 0x1000001, - OT_BOOL = 0x1000008, - SQOBJECT_DELEGABLE = 0x2000000, - SQOBJECT_NUMERIC = 0x4000000, - OT_INTEGER = 0x5000002, - OT_FLOAT = 0x5000004, - SQOBJECT_REF_COUNTED = 0x8000000, - OT_STRING = 0x8000010, - OT_ARRAY = 0x8000040, - OT_CLOSURE = 0x8000100, - OT_NATIVECLOSURE = 0x8000200, - OT_ASSET = 0x8000400, - OT_THREAD = 0x8001000, - OT_FUNCPROTO = 0x8002000, - OT_CLAAS = 0x8004000, - OT_STRUCT = 0x8200000, - OT_WEAKREF = 0x8010000, - OT_TABLE = 0xA000020, - OT_USERDATA = 0xA000080, - OT_INSTANCE = 0xA008000, - OT_ENTITY = 0xA400000, -}; - -/* 156 */ -union SQObjectValue -{ - SQString* asString; - SQTable* asTable; - SQClosure* asClosure; - SQFunctionProto* asFuncProto; - SQStructDef* asStructDef; - long long as64Integer; - SQNativeClosure* asNativeClosure; - SQArray* asArray; - HSquirrelVM* asThread; - float asFloat; - int asInteger; - SQUserData* asUserdata; - SQStructInstance* asStructInstance; -}; - -/* 160 */ -struct SQVector -{ - SQObjectType _Type; - float x; - float y; - float z; -}; - -/* 128 */ -struct SQObject -{ - SQObjectType _Type; - int structNumber; - SQObjectValue _VAL; -}; - -/* 138 */ -struct alignas(8) SQString -{ - void* vftable; - int uiRef; - int padding; - SQString* _next_maybe; - SQSharedState* sharedState; - int length; - unsigned char gap_24[4]; - char _hash[8]; - char _val[1]; -}; - -/* 137 */ -struct alignas(8) SQTable -{ - void* vftable; - unsigned char gap_08[4]; - int uiRef; - unsigned char gap_10[8]; - void* pointer_18; - void* pointer_20; - void* _sharedState; - long long field_30; - tableNode* _nodes; - int _numOfNodes; - int size; - int field_48; - int _usedNodes; - unsigned char _gap_50[20]; - int field_64; - unsigned char _gap_68[80]; -}; - -/* 140 */ -struct alignas(8) SQClosure -{ - void* vftable; - unsigned char gap_08[4]; - int uiRef; - void* pointer_10; - void* pointer_18; - void* pointer_20; - void* sharedState; - SQObject obj_30; - SQObject _function; - SQObject* _outervalues; - unsigned char gap_58[8]; - unsigned char gap_60[96]; - SQObject* objectPointer_C0; - unsigned char gap_C8[16]; -}; - -/* 139 */ -struct alignas(8) SQFunctionProto -{ - void* vftable; - unsigned char gap_08[4]; - int uiRef; - unsigned char gap_10[8]; - void* pointer_18; - void* pointer_20; - void* sharedState; - void* pointer_30; - SQObjectType _fileNameType; - SQString* _fileName; - SQObjectType _funcNameType; - SQString* _funcName; - SQObject obj_58; - unsigned char gap_68[12]; - int _stacksize; - unsigned char gap_78[48]; - int nParameters; - unsigned char gap_AC[60]; - int nDefaultParams; - unsigned char gap_EC[200]; -}; - -/* 152 */ -struct SQStructDef -{ - void* vtable; - int uiRef; - unsigned char padding_C[4]; - unsigned char unknown[24]; - SQSharedState* sharedState; - SQObjectType _nameType; - SQString* _name; - unsigned char gap_38[16]; - SQObjectType _variableNamesType; - SQTable* _variableNames; - unsigned char gap_[32]; -}; - -/* 157 */ -struct alignas(8) SQNativeClosure -{ - void* vftable; - int uiRef; - unsigned char gap_C[4]; - long long value_10; - long long value_18; - long long value_20; - SQSharedState* sharedState; - char unknown_30; - unsigned char padding_34[7]; - long long value_38; - long long value_40; - long long value_48; - long long value_50; - long long value_58; - SQObjectType _nameType; - SQString* _name; - long long value_70; - long long value_78; - unsigned char justInCaseGap_80[300]; -}; - -/* 162 */ -struct SQArray -{ - void* vftable; - int uiRef; - unsigned char gap_24[36]; - SQObject* _values; - int _usedSlots; - int _allocated; -}; - -/* 129 */ -struct alignas(8) HSquirrelVM -{ - void* vftable; - int uiRef; - unsigned char gap_8[12]; - void* _toString; - void* _roottable_pointer; - void* pointer_28; - CallInfo* ci; - CallInfo* _callstack; - int _callstacksize; - int _stackbase; - SQObject* _stackOfCurrentFunction; - SQSharedState* sharedState; - void* pointer_58; - void* pointer_60; - int _top; - SQObject* _stack; - unsigned char gap_78[8]; - SQObject* _vargvstack; - unsigned char gap_88[8]; - SQObject temp_reg; - unsigned char gapA0[8]; - void* pointer_A8; - unsigned char gap_B0[8]; - SQObject _roottable_object; - SQObject _lasterror; - SQObject _errorHandler; - long long field_E8; - int traps; - unsigned char gap_F4[12]; - int _nnativecalls; - int _suspended; - int _suspended_root; - int _unk; - int _suspended_target; - int trapAmount; - int _suspend_varargs; - int unknown_field_11C; - SQObject object_120; -}; - -/* 150 */ -struct SQStructInstance -{ - void* vftable; - __int32 uiRef; - BYTE gap_C[4]; - __int64 unknown_10; - void* pointer_18; - __int64 unknown_20; - SQSharedState* _sharedState; - unsigned int size; - BYTE gap_34[4]; - SQObject data[1]; // This struct is dynamically sized, so this size is unknown -}; - -/* 148 */ -struct SQSharedState -{ - unsigned char gap_0[72]; - void* unknown; - unsigned char gap_50[16344]; - SQObjectType _unknownTableType00; - long long _unknownTableValue00; - unsigned char gap_4038[16]; - StringTable* _stringTable; - unsigned char gap_4050[32]; - SQObjectType _unknownTableType0; - long long _unknownTableValue0; - SQObjectType _unknownObjectType1; - long long _unknownObjectValue1; - unsigned char gap_4090[8]; - SQObjectType _unknownArrayType2; - long long _unknownArrayValue2; - SQObjectType _gobalsArrayType; - SQStructInstance* _globalsArray; - unsigned char gap_40B8[16]; - SQObjectType _nativeClosuresType; - SQTable* _nativeClosures; - SQObjectType _typedConstantsType; - SQTable* _typedConstants; - SQObjectType _untypedConstantsType; - SQTable* _untypedConstants; - SQObjectType _globalsMaybeType; - SQTable* _globals; - SQObjectType _functionsType; - SQTable* _functions; - SQObjectType _structsType; - SQTable* _structs; - SQObjectType _typeDefsType; - SQTable* _typeDefs; - SQObjectType unknownTableType; - SQTable* unknownTable; - SQObjectType _squirrelFilesType; - SQTable* _squirrelFiles; - unsigned char gap_4158[80]; - SQObjectType _nativeClosures2Type; - SQTable* _nativeClosures2; - SQObjectType _entityTypesMaybeType; - SQTable* _entityTypesMaybe; - SQObjectType unknownTable2Type; - SQTable* unknownTable2; - unsigned char gap_41D8[72]; - SQObjectType _compilerKeywordsType; - SQTable* _compilerKeywords; - HSquirrelVM* _currentThreadMaybe; - unsigned char gap_4238[8]; - SQObjectType unknownTable3Type; - SQTable* unknownTable3; - unsigned char gap_4250[16]; - SQObjectType unknownThreadType; - SQTable* unknownThread; - SQObjectType _tableNativeFunctionsType; - SQTable* _tableNativeFunctions; - SQObjectType _unknownTableType4; - long long _unknownObjectValue4; - SQObjectType _unknownObjectType5; - long long _unknownObjectValue5; - SQObjectType _unknownObjectType6; - long long _unknownObjectValue6; - SQObjectType _unknownObjectType7; - long long _unknownObjectValue7; - SQObjectType _unknownObjectType8; - long long _unknownObjectValue8; - SQObjectType _unknownObjectType9; - long long _unknownObjectValue9; - SQObjectType _unknownObjectType10; - long long _unknownObjectValue10; - SQObjectType _unknownObjectType11; - long long _unknownObjectValue11; - SQObjectType _unknownObjectType12; - long long _unknownObjectValue12; - SQObjectType _unknownObjectType13; - long long _unknownObjectValue13; - SQObjectType _unknownObjectType14; - long long _unknownObjectValue14; - SQObjectType _unknownObjectType15; - long long _unknownObjectValue15; - unsigned char gap_4340[16]; - void* printFunction; - unsigned char gap_4358[16]; - void* logEntityFunction; - unsigned char gap_4370[40]; - SQObjectType _waitStringType; - SQString* _waitStringValue; - SQObjectType _SpinOffAndWaitForStringType; - SQString* _SpinOffAndWaitForStringValue; - SQObjectType _SpinOffAndWaitForSoloStringType; - SQString* _SpinOffAndWaitForSoloStringValue; - SQObjectType _SpinOffStringType; - SQString* _SpinOffStringValue; - SQObjectType _SpinOffDelayedStringType; - SQString* _SpinOffDelayedStringValue; - CSquirrelVM* cSquirrelVM; - bool enableDebugInfo; // functionality stripped - unsigned char gap_43F1[23]; -}; - -/* 165 */ -struct tableNode -{ - SQObject val; - SQObject key; - tableNode* next; -}; - -/* 136 */ -struct alignas(8) CallInfo -{ - long long ip; - SQObject* _literals; - SQObject obj10; - SQObject closure; - int _etraps[4]; - int _root; - short _vargs_size; - short _vargs_base; - unsigned char gap[16]; -}; - -/* 149 */ -struct StringTable -{ - unsigned char gap_0[12]; - int _numofslots; - unsigned char gap_10[200]; -}; - -/* 141 */ -struct alignas(8) SQStackInfos -{ - char* _name; - char* _sourceName; - int _line; -}; - -/* 151 */ -struct alignas(4) SQInstruction -{ - int op; - int arg1; - int output; - short arg2; - short arg3; -}; - -/* 154 */ -struct SQLexer -{ - unsigned char gap_0[112]; -}; - -/* 153 */ -struct SQCompiler -{ - unsigned char gap_0[4]; - int _token; - unsigned char gap_8[8]; - SQObject object_10; - SQLexer lexer; - unsigned char gap_90[752]; - HSquirrelVM* sqvm; - unsigned char gap_288[8]; -}; - -/* 155 */ -struct CSquirrelVM -{ - BYTE gap_0[8]; - HSquirrelVM* sqvm; - BYTE gap_10[8]; - SQObject unknownObject_18; - __int64 unknown_28; - BYTE gap_30[12]; - __int32 vmContext; - BYTE gap_40[648]; - char* (*formatString)(__int64 a1, const char* format, ...); - BYTE gap_2D0[24]; -}; - -struct SQUserData -{ - void* vftable; - int uiRef; - char gap_12[4]; - long long unknown_10; - long long unknown_18; - long long unknown_20; - long long sharedState; - long long unknown_30; - int size; - char padding1[4]; - releasehookType releaseHook; - long long typeId; - char data[1]; -}; diff --git a/NorthstarDLL/util/printcommands.cpp b/NorthstarDLL/util/printcommands.cpp deleted file mode 100644 index 34d56666..00000000 --- a/NorthstarDLL/util/printcommands.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "printcommands.h" -#include "core/convar/cvar.h" -#include "core/convar/convar.h" -#include "core/convar/concommand.h" - -void PrintCommandHelpDialogue(const ConCommandBase* command, const char* name) -{ - if (!command) - { - spdlog::info("unknown command {}", name); - return; - } - - // temp because command->IsCommand does not currently work - ConVar* cvar = g_pCVar->FindVar(command->m_pszName); - - // build string for flags if not FCVAR_NONE - std::string flagString; - if (command->GetFlags() != FCVAR_NONE) - { - flagString = "( "; - - for (auto& flagPair : g_PrintCommandFlags) - { - if (command->GetFlags() & flagPair.first) - { - // special case, slightly hacky: PRINTABLEONLY is for commands, GAMEDLL_FOR_REMOTE_CLIENTS is for concommands, both have the - // same value - if (flagPair.first == FCVAR_PRINTABLEONLY) - { - if (cvar && !strcmp(flagPair.second, "GAMEDLL_FOR_REMOTE_CLIENTS")) - continue; - - if (!cvar && !strcmp(flagPair.second, "PRINTABLEONLY")) - continue; - } - - flagString += flagPair.second; - flagString += " "; - } - } - - flagString += ") "; - } - - if (cvar) - spdlog::info("\"{}\" = \"{}\" {}- {}", cvar->GetBaseName(), cvar->GetString(), flagString, cvar->GetHelpText()); - else - spdlog::info("\"{}\" {} - {}", command->m_pszName, flagString, command->GetHelpText()); -} - -void TryPrintCvarHelpForCommand(const char* pCommand) -{ - // try to display help text for an inputted command string from the console - int pCommandLen = strlen(pCommand); - char* pCvarStr = new char[pCommandLen]; - strcpy(pCvarStr, pCommand); - - // trim whitespace from right - for (int i = pCommandLen - 1; i; i--) - { - if (isspace(pCvarStr[i])) - pCvarStr[i] = '\0'; - else - break; - } - - // check if we're inputting a cvar, but not setting it at all - ConVar* cvar = g_pCVar->FindVar(pCvarStr); - if (cvar) - PrintCommandHelpDialogue(&cvar->m_ConCommandBase, pCvarStr); - - delete[] pCvarStr; -} - -void ConCommand_help(const CCommand& arg) -{ - if (arg.ArgC() < 2) - { - spdlog::info("Usage: help <cvarname>"); - return; - } - - PrintCommandHelpDialogue(g_pCVar->FindCommandBase(arg.Arg(1)), arg.Arg(1)); -} - -void ConCommand_find(const CCommand& arg) -{ - if (arg.ArgC() < 2) - { - spdlog::info("Usage: find <string> [<string>...]"); - return; - } - - char pTempName[256]; - char pTempSearchTerm[256]; - - ConCommandBase* var; - CCVarIteratorInternal* itint = g_pCVar->FactoryInternalIterator(); - std::map<std::string, ConCommandBase*> sorted; - for (itint->SetFirst(); itint->IsValid(); itint->Next()) - { - var = itint->Get(); - if (!var->IsFlagSet(FCVAR_DEVELOPMENTONLY) && !var->IsFlagSet(FCVAR_HIDDEN)) - { - sorted.insert({var->m_pszName, var}); - } - } - delete itint; - - for (auto& map : sorted) - { - bool bPrintCommand = true; - for (int i = 0; i < arg.ArgC() - 1; i++) - { - // make lowercase to avoid case sensitivity - strncpy_s(pTempName, sizeof(pTempName), map.second->m_pszName, sizeof(pTempName) - 1); - strncpy_s(pTempSearchTerm, sizeof(pTempSearchTerm), arg.Arg(i + 1), sizeof(pTempSearchTerm) - 1); - - for (int i = 0; pTempName[i]; i++) - pTempName[i] = tolower(pTempName[i]); - - for (int i = 0; pTempSearchTerm[i]; i++) - pTempSearchTerm[i] = tolower(pTempSearchTerm[i]); - - if (!strstr(pTempName, pTempSearchTerm)) - { - bPrintCommand = false; - break; - } - } - - if (bPrintCommand) - PrintCommandHelpDialogue(map.second, map.second->m_pszName); - } -} - -void ConCommand_findflags(const CCommand& arg) -{ - if (arg.ArgC() < 2) - { - spdlog::info("Usage: findflags <string>"); - for (auto& flagPair : g_PrintCommandFlags) - spdlog::info(" - {}", flagPair.second); - - return; - } - - // convert input flag to uppercase - char* upperFlag = new char[strlen(arg.Arg(1))]; - strcpy(upperFlag, arg.Arg(1)); - - for (int i = 0; upperFlag[i]; i++) - upperFlag[i] = toupper(upperFlag[i]); - - // resolve flag name => int flags - int resolvedFlag = FCVAR_NONE; - for (auto& flagPair : g_PrintCommandFlags) - { - if (!strcmp(flagPair.second, upperFlag)) - { - resolvedFlag |= flagPair.first; - break; - } - } - - ConCommandBase* var; - CCVarIteratorInternal* itint = g_pCVar->FactoryInternalIterator(); - std::map<std::string, ConCommandBase*> sorted; - for (itint->SetFirst(); itint->IsValid(); itint->Next()) - { - var = itint->Get(); - if (!var->IsFlagSet(FCVAR_DEVELOPMENTONLY) && !var->IsFlagSet(FCVAR_HIDDEN)) - { - sorted.insert({var->m_pszName, var}); - } - } - delete itint; - - for (auto& map : sorted) - { - if (map.second->m_nFlags & resolvedFlag) - PrintCommandHelpDialogue(map.second, map.second->m_pszName); - } - - delete[] upperFlag; -} - -void ConCommand_list(const CCommand& arg) -{ - ConCommandBase* var; - CCVarIteratorInternal* itint = g_pCVar->FactoryInternalIterator(); - std::map<std::string, ConCommandBase*> sorted; - for (itint->SetFirst(); itint->IsValid(); itint->Next()) - { - var = itint->Get(); - if (!var->IsFlagSet(FCVAR_DEVELOPMENTONLY) && !var->IsFlagSet(FCVAR_HIDDEN)) - { - sorted.insert({var->m_pszName, var}); - } - } - delete itint; - - for (auto& map : sorted) - { - PrintCommandHelpDialogue(map.second, map.second->m_pszName); - } - spdlog::info("{} total convars/concommands", sorted.size()); -} - -void ConCommand_differences(const CCommand& arg) -{ - CCVarIteratorInternal* itint = g_pCVar->FactoryInternalIterator(); - std::map<std::string, ConCommandBase*> sorted; - - for (itint->SetFirst(); itint->IsValid(); itint->Next()) - { - ConCommandBase* var = itint->Get(); - if (!var->IsFlagSet(FCVAR_DEVELOPMENTONLY) && !var->IsFlagSet(FCVAR_HIDDEN)) - { - sorted.insert({var->m_pszName, var}); - } - } - delete itint; - - for (auto& map : sorted) - { - ConVar* cvar = g_pCVar->FindVar(map.second->m_pszName); - - if (!cvar) - { - continue; - } - - if (strcmp(cvar->GetString(), "FCVAR_NEVER_AS_STRING") == NULL) - { - continue; - } - - if (strcmp(cvar->GetString(), cvar->m_pszDefaultValue) == NULL) - { - continue; - } - - std::string formatted = - fmt::format("\"{}\" = \"{}\" ( def. \"{}\" )", cvar->GetBaseName(), cvar->GetString(), cvar->m_pszDefaultValue); - - if (cvar->m_bHasMin) - { - formatted.append(fmt::format(" min. {}", cvar->m_fMinVal)); - } - - if (cvar->m_bHasMax) - { - formatted.append(fmt::format(" max. {}", cvar->m_fMaxVal)); - } - - formatted.append(fmt::format(" - {}", cvar->GetHelpText())); - spdlog::info(formatted); - } -} - -void InitialiseCommandPrint() -{ - RegisterConCommand( - "convar_find", ConCommand_find, "Find convars/concommands with the specified string in their name/help text.", FCVAR_NONE); - - // these commands already exist, so we need to modify the preexisting command to use our func instead - // and clear the flags also - ConCommand* helpCommand = g_pCVar->FindCommand("help"); - helpCommand->m_nFlags = FCVAR_NONE; - helpCommand->m_pCommandCallback = ConCommand_help; - - ConCommand* findCommand = g_pCVar->FindCommand("convar_findByFlags"); - findCommand->m_nFlags = FCVAR_NONE; - findCommand->m_pCommandCallback = ConCommand_findflags; - - ConCommand* listCommand = g_pCVar->FindCommand("convar_list"); - listCommand->m_nFlags = FCVAR_NONE; - listCommand->m_pCommandCallback = ConCommand_list; - - ConCommand* diffCommand = g_pCVar->FindCommand("convar_differences"); - diffCommand->m_nFlags = FCVAR_NONE; - diffCommand->m_pCommandCallback = ConCommand_differences; -} diff --git a/NorthstarDLL/util/printcommands.h b/NorthstarDLL/util/printcommands.h deleted file mode 100644 index cb72e5cc..00000000 --- a/NorthstarDLL/util/printcommands.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "core/convar/concommand.h" - -void PrintCommandHelpDialogue(const ConCommandBase* command, const char* name); -void TryPrintCvarHelpForCommand(const char* pCommand); -void InitialiseCommandPrint(); diff --git a/NorthstarDLL/util/printmaps.cpp b/NorthstarDLL/util/printmaps.cpp deleted file mode 100644 index d3253605..00000000 --- a/NorthstarDLL/util/printmaps.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include "printmaps.h" -#include "core/convar/convar.h" -#include "core/convar/concommand.h" -#include "mods/modmanager.h" -#include "core/tier0.h" -#include "engine/r2engine.h" -#include "squirrel/squirrel.h" - -#include <filesystem> -#include <regex> - -AUTOHOOK_INIT() - -enum class MapSource_t -{ - VPK, - GAMEDIR, - MOD -}; - -const std::unordered_map<MapSource_t, const char*> PrintMapSource = { - {MapSource_t::VPK, "VPK"}, {MapSource_t::MOD, "MOD"}, {MapSource_t::GAMEDIR, "R2"}}; - -struct MapVPKInfo -{ - std::string name; - std::string parent; - MapSource_t source; -}; - -// our current list of maps in the game -std::vector<MapVPKInfo> vMapList; - -typedef void (*Host_Map_helperType)(const CCommand&, void*); -typedef void (*Host_Changelevel_fType)(const CCommand&); - -Host_Map_helperType Host_Map_helper; -Host_Changelevel_fType Host_Changelevel_f; - -void RefreshMapList() -{ - // Only update the maps list every 10 seconds max to we avoid constantly reading fs - static double fLastRefresh = -999; - - if (fLastRefresh + 10.0 > g_pGlobals->m_flRealTime) - return; - - fLastRefresh = g_pGlobals->m_flRealTime; - - // Rebuild map list - vMapList.clear(); - - // get modded maps - // TODO: could probably check mod vpks to get mapnames from there too? - for (auto& modFilePair : g_pModManager->m_ModFiles) - { - ModOverrideFile file = modFilePair.second; - if (file.m_Path.extension() == ".bsp" && file.m_Path.parent_path().string() == "maps") // only allow mod maps actually in /maps atm - { - MapVPKInfo& map = vMapList.emplace_back(); - map.name = file.m_Path.stem().string(); - map.parent = file.m_pOwningMod->Name; - map.source = MapSource_t::MOD; - } - } - - // get maps in vpk - { - const int iNumRetailNonMapVpks = 1; - static const char* const ppRetailNonMapVpks[] = { - "englishclient_frontend.bsp.pak000_dir.vpk"}; // don't include mp_common here as it contains mp_lobby - - // matches directory vpks, and captures their map name in the first group - static const std::regex rVpkMapRegex("englishclient_([a-zA-Z0-9_]+)\\.bsp\\.pak000_dir\\.vpk", std::regex::icase); - - for (fs::directory_entry file : fs::directory_iterator("./vpk")) - { - std::string pathString = file.path().filename().string(); - - bool bIsValidMapVpk = true; - for (int i = 0; i < iNumRetailNonMapVpks; i++) - { - if (!pathString.compare(ppRetailNonMapVpks[i])) - { - bIsValidMapVpk = false; - break; - } - } - - if (!bIsValidMapVpk) - continue; - - // run our map vpk regex on the filename - std::smatch match; - std::regex_match(pathString, match, rVpkMapRegex); - - if (match.length() < 2) - continue; - - std::string mapName = match[1].str(); - // special case: englishclient_mp_common contains mp_lobby, so hardcode the name here - if (mapName == "mp_common") - mapName = "mp_lobby"; - - MapVPKInfo& map = vMapList.emplace_back(); - map.name = mapName; - map.parent = pathString; - map.source = MapSource_t::VPK; - } - } - - // get maps in game dir - std::string gameDir = fmt::format("{}/maps", g_pModName); - if (!std::filesystem::exists(gameDir)) - { - return; - } - - for (fs::directory_entry file : fs::directory_iterator(gameDir)) - { - if (file.path().extension() == ".bsp") - { - MapVPKInfo& map = vMapList.emplace_back(); - map.name = file.path().stem().string(); - map.parent = "R2"; - map.source = MapSource_t::GAMEDIR; - } - } -} - -// clang-format off -AUTOHOOK(_Host_Map_f_CompletionFunc, engine.dll + 0x161AE0, -int, __fastcall, (const char *const cmdname, const char *const partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH])) -// clang-format on -{ - RefreshMapList(); - - // use a custom autocomplete func for all map loading commands - const int cmdLength = strlen(cmdname); - const char* query = partial + cmdLength; - const int queryLength = strlen(query); - - int numMaps = 0; - for (int i = 0; i < vMapList.size() && numMaps < COMMAND_COMPLETION_MAXITEMS; i++) - { - if (!strncmp(query, vMapList[i].name.c_str(), queryLength)) - { - strcpy(commands[numMaps], cmdname); - strncpy_s( - commands[numMaps++] + cmdLength, - COMMAND_COMPLETION_ITEM_LENGTH, - &vMapList[i].name[0], - COMMAND_COMPLETION_ITEM_LENGTH - cmdLength); - } - } - - return numMaps; -} - -ADD_SQFUNC( - "array<string>", - NSGetLoadedMapNames, - "", - "Returns a string array of loaded map file names", - ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) -{ - // Maybe we should call this on mods reload instead - RefreshMapList(); - - g_pSquirrel<context>->newarray(sqvm, 0); - - for (MapVPKInfo& map : vMapList) - { - g_pSquirrel<context>->pushstring(sqvm, map.name.c_str()); - g_pSquirrel<context>->arrayappend(sqvm, -2); - } - - return SQRESULT_NOTNULL; -} - -void ConCommand_maps(const CCommand& args) -{ - if (args.ArgC() < 2) - { - spdlog::info("Usage: maps <substring>"); - spdlog::info("maps * for full listing"); - return; - } - - RefreshMapList(); - - for (MapVPKInfo& map : vMapList) // need to figure out a nice way to include parent path without making the formatting awful - if ((*args.Arg(1) == '*' && !args.Arg(1)[1]) || strstr(map.name.c_str(), args.Arg(1))) - spdlog::info("({}) {}", PrintMapSource.at(map.source), map.name); -} - -// clang-format off -AUTOHOOK(Host_Map_f, engine.dll + 0x15B340, void, __fastcall, (const CCommand& args)) -// clang-format on -{ - RefreshMapList(); - - if (args.ArgC() > 1 && - std::find_if(vMapList.begin(), vMapList.end(), [&](MapVPKInfo map) -> bool { return map.name == args.Arg(1); }) == vMapList.end()) - { - spdlog::warn("Map load failed: {} not found or invalid", args.Arg(1)); - return; - } - else if (args.ArgC() == 1) - { - spdlog::warn("Map load failed: no map name provided"); - return; - } - - if (*g_pServerState >= server_state_t::ss_active) - return Host_Changelevel_f(args); - else - return Host_Map_helper(args, nullptr); -} - -void InitialiseMapsPrint() -{ - AUTOHOOK_DISPATCH() - - ConCommand* mapsCommand = g_pCVar->FindCommand("maps"); - mapsCommand->m_pCommandCallback = ConCommand_maps; -} - -ON_DLL_LOAD("engine.dll", Host_Map_f, (CModule module)) -{ - Host_Map_helper = module.Offset(0x15AEF0).RCast<Host_Map_helperType>(); - Host_Changelevel_f = module.Offset(0x15AAD0).RCast<Host_Changelevel_fType>(); -} diff --git a/NorthstarDLL/util/printmaps.h b/NorthstarDLL/util/printmaps.h deleted file mode 100644 index b01761c0..00000000 --- a/NorthstarDLL/util/printmaps.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseMapsPrint(); diff --git a/NorthstarDLL/util/utils.cpp b/NorthstarDLL/util/utils.cpp deleted file mode 100644 index c3f90cfa..00000000 --- a/NorthstarDLL/util/utils.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include <ctype.h> -#include "utils.h" - -bool skip_valid_ansi_csi_sgr(char*& str) -{ - if (*str++ != '\x1B') - return false; - if (*str++ != '[') // CSI - return false; - for (char* c = str; *c; c++) - { - if (*c >= '0' && *c <= '9') - continue; - if (*c == ';' || *c == ':') - continue; - if (*c == 'm') // SGR - break; - return false; - } - return true; -} - -void RemoveAsciiControlSequences(char* str, bool allow_color_codes) -{ - for (char *pc = str, c = *pc; c = *pc; pc++) - { - // skip UTF-8 characters - int bytesToSkip = 0; - if ((c & 0xE0) == 0xC0) - bytesToSkip = 1; // skip 2-byte UTF-8 sequence - else if ((c & 0xF0) == 0xE0) - bytesToSkip = 2; // skip 3-byte UTF-8 sequence - else if ((c & 0xF8) == 0xF0) - bytesToSkip = 3; // skip 4-byte UTF-8 sequence - else if ((c & 0xFC) == 0xF8) - bytesToSkip = 4; // skip 5-byte UTF-8 sequence - else if ((c & 0xFE) == 0xFC) - bytesToSkip = 5; // skip 6-byte UTF-8 sequence - - bool invalid = false; - char* orgpc = pc; - for (int i = 0; i < bytesToSkip; i++) - { - char next = pc[1]; - - // valid UTF-8 part - if ((next & 0xC0) == 0x80) - { - pc++; - continue; - } - - // invalid UTF-8 part or encountered \0 - invalid = true; - break; - } - if (invalid) - { - // erase the whole "UTF-8" sequence - for (char* x = orgpc; x <= pc; x++) - if (*x != '\0') - *x = ' '; - else - break; - } - if (bytesToSkip > 0) - continue; // this byte was already handled as UTF-8 - - // an invalid control character or an UTF-8 part outside of UTF-8 sequence - if ((iscntrl(c) && c != '\n' && c != '\r' && c != '\x1B') || (c & 0x80) != 0) - { - *pc = ' '; - continue; - } - - if (c == '\x1B') // separate handling for this escape sequence... - if (allow_color_codes && skip_valid_ansi_csi_sgr(pc)) // ...which we allow for color codes... - pc--; - else // ...but remove it otherwise - *pc = ' '; - } -} diff --git a/NorthstarDLL/util/utils.h b/NorthstarDLL/util/utils.h deleted file mode 100644 index 85922692..00000000 --- a/NorthstarDLL/util/utils.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void RemoveAsciiControlSequences(char* str, bool allow_color_codes); diff --git a/NorthstarDLL/util/version.cpp b/NorthstarDLL/util/version.cpp deleted file mode 100644 index a947cde1..00000000 --- a/NorthstarDLL/util/version.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "util/version.h" -#include "ns_version.h" -#include "dedicated/dedicated.h" - -char version[16]; -char NSUserAgent[256]; - -void InitialiseVersion() -{ - constexpr int northstar_version[4] {NORTHSTAR_VERSION}; - int ua_len = 0; - - // We actually use the rightmost integer do determine whether or not we're a debug/dev build - // If it is set to a non-zero value, we are a dev build - // On github CI, we set this to a 0 automatically as we replace the 0,0,0,1 with the real version number - if (northstar_version[3]) - { - sprintf(version, "%d.%d.%d.%d+dev", northstar_version[0], northstar_version[1], northstar_version[2], northstar_version[3]); - ua_len += snprintf( - NSUserAgent + ua_len, - sizeof(NSUserAgent) - ua_len, - "R2Northstar/%d.%d.%d+dev", - northstar_version[0], - northstar_version[1], - northstar_version[2]); - } - else - { - sprintf(version, "%d.%d.%d.%d", northstar_version[0], northstar_version[1], northstar_version[2], northstar_version[3]); - ua_len += snprintf( - NSUserAgent + ua_len, - sizeof(NSUserAgent) - ua_len, - "R2Northstar/%d.%d.%d", - northstar_version[0], - northstar_version[1], - northstar_version[2]); - } - - if (IsDedicatedServer()) - ua_len += snprintf(NSUserAgent + ua_len, sizeof(NSUserAgent) - ua_len, " (Dedicated)"); - - // Add the host platform info to the user agent. - // - // note: ntdll will always be loaded - HMODULE ntdll = GetModuleHandleA("ntdll"); - if (ntdll) - { - // real win32 version info (i.e., ignore manifest) - DWORD(WINAPI * RtlGetVersion)(LPOSVERSIONINFOEXW); - *(FARPROC*)(&RtlGetVersion) = GetProcAddress(ntdll, "RtlGetVersion"); - - // wine version (7.0-rc1, 7.0, 7.11, etc) - const char*(CDECL * wine_get_version)(void); - *(FARPROC*)(&wine_get_version) = GetProcAddress(ntdll, "wine_get_version"); - - // human-readable build string (e.g., "wine-7.22 (Staging)") - const char*(CDECL * wine_get_build_id)(void); - *(FARPROC*)(&wine_get_build_id) = GetProcAddress(ntdll, "wine_get_build_id"); - - // uname sysname (Darwin, Linux, etc) and release (kernel version string) - void(CDECL * wine_get_host_version)(const char** sysname, const char** release); - *(FARPROC*)(&wine_get_host_version) = GetProcAddress(ntdll, "wine_get_host_version"); - - OSVERSIONINFOEXW osvi = {}; - const char *wine_version = NULL, *wine_build_id = NULL, *wine_sysname = NULL, *wine_release = NULL; - if (RtlGetVersion) - RtlGetVersion(&osvi); - if (wine_get_version) - wine_version = wine_get_version(); - if (wine_get_build_id) - wine_build_id = wine_get_build_id(); - if (wine_get_host_version) - wine_get_host_version(&wine_sysname, &wine_release); - - // windows version - if (osvi.dwMajorVersion) - ua_len += snprintf( - NSUserAgent + ua_len, - sizeof(NSUserAgent) - ua_len, - " Windows/%d.%d.%d", - osvi.dwMajorVersion, - osvi.dwMinorVersion, - osvi.dwBuildNumber); - - // wine version - if (wine_version && wine_build_id) - ua_len += snprintf(NSUserAgent + ua_len, sizeof(NSUserAgent) - ua_len, " Wine/%s (%s)", wine_version, wine_build_id); - - // wine host system version - if (wine_sysname && wine_release) - ua_len += snprintf(NSUserAgent + ua_len, sizeof(NSUserAgent) - ua_len, " %s/%s", wine_sysname, wine_release); - } - - return; -} diff --git a/NorthstarDLL/util/version.h b/NorthstarDLL/util/version.h deleted file mode 100644 index a3dcf8c7..00000000 --- a/NorthstarDLL/util/version.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -extern char version[16]; -extern char NSUserAgent[256]; - -void InitialiseVersion(); diff --git a/NorthstarDLL/util/wininfo.cpp b/NorthstarDLL/util/wininfo.cpp deleted file mode 100644 index 4fd64369..00000000 --- a/NorthstarDLL/util/wininfo.cpp +++ /dev/null @@ -1,9 +0,0 @@ -AUTOHOOK_INIT() - -HWND* g_gameHWND; -HMODULE g_NorthstarModule = 0; - -ON_DLL_LOAD("engine.dll", WinInfo, (CModule module)) -{ - g_gameHWND = module.Offset(0x7d88a0).RCast<HWND*>(); -} diff --git a/NorthstarDLL/util/wininfo.h b/NorthstarDLL/util/wininfo.h deleted file mode 100644 index c56f7b87..00000000 --- a/NorthstarDLL/util/wininfo.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -extern HWND* g_gameHWND; -extern HMODULE g_NorthstarModule; |