[General]: Fix some things (#667)

This commit is contained in:
Edo 2022-12-25 18:23:53 +01:00 committed by GitHub
parent 79758e1752
commit 01a629d02b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 368 additions and 393 deletions

View File

@ -21,7 +21,6 @@
| `--copy-to=PATH` | Optional, copy the DLL to a custom folder after build, define the path here if wanted. |
| `--copy-pdb` | Copy debug information for binaries as well to the path given via --copy-to. |
| `--force-unit-tests` | Always compile unit tests. |
| `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. |
| `--disable-binary-check` | Do not perform integrity checks on the exe. |
## Command line arguments

View File

@ -76,11 +76,6 @@ newoption {
description = "Always compile unit tests."
}
newoption {
trigger = "force-exception-handler",
description = "Install custom unhandled exception handler even for Debug builds."
}
newoption {
trigger = "disable-binary-check",
description = "Do not perform integrity checks on the exe."
@ -257,9 +252,6 @@ workspace "iw4x"
if _OPTIONS["force-unit-tests"] then
defines {"FORCE_UNIT_TESTS"}
end
if _OPTIONS["force-exception-handler"] then
defines {"FORCE_EXCEPTION_HANDLER"}
end
if _OPTIONS["disable-binary-check"] then
defines {"DISABLE_BINARY_CHECK"}
end

View File

@ -9,128 +9,128 @@ namespace Components
bool Loader::IsPregame()
{
return Loader::Pregame;
return Pregame;
}
bool Loader::IsPostgame()
{
return Loader::Postgame;
return Postgame;
}
bool Loader::IsUninitializing()
{
return Loader::Uninitializing;
return Uninitializing;
}
void Loader::Initialize()
{
Loader::Pregame = true;
Loader::Postgame = false;
Loader::Uninitializing = false;
Pregame = true;
Postgame = false;
Uninitializing = false;
Utils::Memory::GetAllocator()->clear();
Loader::Register(new Flags());
Loader::Register(new Singleton());
// Install our exception handler as early as posssible to get better debug dumps from startup crashes
Loader::Register(new Exception());
Loader::Register(new Auth());
Loader::Register(new Bans());
Loader::Register(new Dvar());
Loader::Register(new Bots());
Loader::Register(new Lean());
Loader::Register(new Maps());
Loader::Register(new News());
Loader::Register(new Node());
Loader::Register(new RCon());
Loader::Register(new Stats());
Loader::Register(new Menus());
Loader::Register(new Toast());
Loader::Register(new Party());
Loader::Register(new Zones());
Loader::Register(new D3D9Ex());
Loader::Register(new Logger());
Loader::Register(new Weapon());
Loader::Register(new Window());
Loader::Register(new Command());
Loader::Register(new Console());
Loader::Register(new Friends());
Loader::Register(new IPCPipe());
Loader::Register(new MapDump());
Loader::Register(new ModList());
Loader::Register(new Network());
Loader::Register(new NetworkDebug());
Loader::Register(new Session());
Loader::Register(new Theatre());
Loader::Register(new ClanTags());
Loader::Register(new Download());
Loader::Register(new Playlist());
Loader::Register(new RawFiles());
Loader::Register(new Renderer());
Loader::Register(new UIFeeder());
Loader::Register(new UIScript());
Loader::Register(new Changelog());
Loader::Register(new Dedicated());
Loader::Register(new Discovery());
Loader::Register(new FastFiles());
Loader::Register(new Gametypes());
Loader::Register(new Materials());
Loader::Register(new Scheduler());
Loader::Register(new Threading());
Loader::Register(new CardTitles());
Loader::Register(new FileSystem());
Loader::Register(new ModelSurfs());
Loader::Register(new PlayerName());
Loader::Register(new QuickPatch());
Loader::Register(new Security());
Loader::Register(new ServerInfo());
Loader::Register(new ServerList());
Loader::Register(new SlowMotion());
Loader::Register(new ArenaLength());
Loader::Register(new StringTable());
Loader::Register(new ZoneBuilder());
Loader::Register(new AssetHandler());
Loader::Register(new Localization());
Loader::Register(new ServerCommands());
Loader::Register(new StructuredData());
Loader::Register(new ConnectProtocol());
Loader::Register(new StartupMessages());
Loader::Register(new SoundMutexFix());
Loader::Register(new Gamepad());
Loader::Register(new Chat());
Loader::Register(new TextRenderer());
Loader::Register(new PlayerMovement());
Loader::Register(new Elevators());
Loader::Register(new ClientCommand());
Loader::Register(new VisionFile());
Loader::Register(new Branding());
Loader::Register(new Debug());
Loader::Register(new RawMouse());
Loader::Register(new Bullet());
Loader::Register(new MapRotation());
Loader::Register(new Ceg());
Loader::Register(new UserInfo());
Loader::Register(new Events());
Loader::Register(new Voice());
Loader::Register(new Vote());
Register(new Auth());
Register(new Command());
Register(new Dvar());
Register(new Exception()); // Install our exception handler as early as posssible to get better debug dumps from startup crashes
Register(new Flags());
Register(new Network());
Register(new Logger());
Register(new Singleton());
Register(new UIScript());
Register(new ZoneBuilder());
Loader::Register(new GSC());
Register(new ArenaLength());
Register(new AssetHandler());
Register(new Bans());
Register(new Bots());
Register(new Branding());
Register(new Bullet());
Register(new CardTitles());
Register(new Ceg());
Register(new Changelog());
Register(new Chat());
Register(new ClanTags());
Register(new ClientCommand());
Register(new ConnectProtocol());
Register(new Console());
Register(new D3D9Ex());
Register(new Debug());
Register(new Dedicated());
Register(new Discovery());
Register(new Download());
Register(new Elevators());
Register(new Events());
Register(new FastFiles());
Register(new FileSystem());
Register(new Friends());
Register(new Gamepad());
Register(new Gametypes());
Register(new IPCPipe());
Register(new Lean());
Register(new Localization());
Register(new MapDump());
Register(new MapRotation());
Register(new Maps());
Register(new Materials());
Register(new Menus());
Register(new ModList());
Register(new ModelSurfs());
Register(new NetworkDebug());
Register(new News());
Register(new Node());
Register(new Party());
Register(new PlayerMovement());
Register(new PlayerName());
Register(new Playlist());
Register(new QuickPatch());
Register(new RCon());
Register(new RawFiles());
Register(new RawMouse());
Register(new Renderer());
Register(new Scheduler());
Register(new Security());
Register(new ServerCommands());
Register(new ServerInfo());
Register(new ServerList());
Register(new Session());
Register(new SlowMotion());
Register(new SoundMutexFix());
Register(new StartupMessages());
Register(new Stats());
Register(new StringTable());
Register(new StructuredData());
Register(new TextRenderer());
Register(new Theatre());
Register(new Threading());
Register(new Toast());
Register(new UIFeeder());
Register(new UserInfo());
Register(new VisionFile());
Register(new Voice());
Register(new Vote());
Register(new Weapon());
Register(new Window());
Register(new Zones());
Loader::Pregame = false;
Register(new GSC());
Pregame = false;
// Make sure preDestroy is called when the game shuts down
Scheduler::OnGameShutdown(Loader::PreDestroy);
Scheduler::OnGameShutdown(PreDestroy);
}
void Loader::Uninitialize()
{
Loader::Uninitializing = true;
Loader::PreDestroyNoPostGame();
Uninitializing = true;
PreDestroyNoPostGame();
std::reverse(Loader::Components.begin(), Loader::Components.end());
for (auto component : Loader::Components)
std::reverse(Components.begin(), Components.end());
for (auto& component : Components)
{
#ifdef DEBUG
if (!Loader::IsPerformingUnitTests())
if (!IsPerformingUnitTests())
{
Logger::Print("Unregister component: {}\n", component->getName());
}
@ -138,21 +138,21 @@ namespace Components
delete component;
}
Loader::Components.clear();
Components.clear();
Utils::Memory::GetAllocator()->clear();
Loader::Uninitializing = false;
Uninitializing = false;
}
void Loader::PreDestroy()
{
if (!Loader::Postgame)
if (!Postgame)
{
Loader::Postgame = true;
Postgame = true;
auto components = Loader::Components;
auto components = Components;
std::reverse(components.begin(), components.end());
for (auto component : components)
for (auto& component : components)
{
component->preDestroy();
}
@ -161,17 +161,17 @@ namespace Components
void Loader::PreDestroyNoPostGame()
{
if (!Loader::Postgame)
if (!Postgame)
{
auto components = Loader::Components;
auto components = Components;
std::reverse(components.begin(), components.end());
for (auto component : components)
for (auto& component : components)
{
component->preDestroy();
}
Loader::Postgame = true;
Postgame = true;
}
}
@ -181,7 +181,7 @@ namespace Components
Logger::Print("Performing unit tests for components:\n");
for (const auto component : Loader::Components)
for (const auto& component : Components)
{
#if defined(FORCE_UNIT_TESTS)
Logger::Debug("Testing '{}'...\n", component->getName());
@ -210,12 +210,12 @@ namespace Components
if (component)
{
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
if (!Loader::IsPerformingUnitTests())
if (!IsPerformingUnitTests())
{
Logger::Print("Component registered: {}\n", component->getName());
}
#endif
Loader::Components.push_back(component);
Components.push_back(component);
}
}
}

View File

@ -42,7 +42,7 @@ namespace Components
template <typename T>
static T* GetInstance()
{
for (auto& component : Loader::Components)
for (auto& component : Components)
{
if (typeid(*component) == typeid(T))
{
@ -61,87 +61,89 @@ namespace Components
};
}
#include "Modules/Scheduler.hpp"
// Priority
#include "Modules/Auth.hpp"
#include "Modules/Bans.hpp"
#include "Modules/Dvar.hpp"
#include "Modules/Bots.hpp"
#include "Modules/Lean.hpp"
#include "Modules/Maps.hpp"
#include "Modules/News.hpp"
#include "Modules/Flags.hpp"
#include "Modules/Menus.hpp"
#include "Modules/Toast.hpp"
#include "Modules/Zones.hpp"
#include "Modules/D3D9Ex.hpp"
#include "Modules/Weapon.hpp"
#include "Modules/Window.hpp"
#include "Modules/Command.hpp"
#include "Modules/Console.hpp"
#include "Modules/UIScript.hpp"
#include "Modules/ModList.hpp"
#include "Modules/Dvar.hpp"
#include "Modules/Exception.hpp"
#include "Modules/Flags.hpp"
#include "Modules/Network.hpp"
#include "Modules/NetworkDebug.hpp"
#include "Modules/Theatre.hpp"
#include "Modules/QuickPatch.hpp"
#include "Modules/Security.hpp"
#include "Modules/Node.hpp"
#include "Modules/RCon.hpp"
#include "Modules/Party.hpp" // Destroys the order, but requires network classes :D
#include "Modules/Logger.hpp"
#include "Modules/Friends.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/MapDump.hpp"
#include "Modules/Session.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/Download.hpp"
#include "Modules/Playlist.hpp"
#include "Modules/RawFiles.hpp"
#include "Modules/Renderer.hpp"
#include "Modules/UIFeeder.hpp"
#include "Modules/Singleton.hpp"
#include "Modules/UIScript.hpp"
#include "Modules/ZoneBuilder.hpp"
#include "Modules/ArenaLength.hpp"
#include "Modules/AssetHandler.hpp"
#include "Modules/Bans.hpp"
#include "Modules/Bots.hpp"
#include "Modules/Branding.hpp"
#include "Modules/Bullet.hpp"
#include "Modules/CardTitles.hpp"
#include "Modules/Ceg.hpp"
#include "Modules/Changelog.hpp"
#include "Modules/Chat.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/ClientCommand.hpp"
#include "Modules/ConnectProtocol.hpp"
#include "Modules/Console.hpp"
#include "Modules/D3D9Ex.hpp"
#include "Modules/Debug.hpp"
#include "Modules/Dedicated.hpp"
#include "Modules/Discovery.hpp"
#include "Modules/Exception.hpp"
#include "Modules/Download.hpp"
#include "Modules/Elevators.hpp"
#include "Modules/Events.hpp"
#include "Modules/FastFiles.hpp"
#include "Modules/Gametypes.hpp"
#include "Modules/Materials.hpp"
#include "Modules/Singleton.hpp"
#include "Modules/Threading.hpp"
#include "Modules/CardTitles.hpp"
#include "Modules/FileSystem.hpp"
#include "Modules/Friends.hpp"
#include "Modules/Gamepad.hpp"
#include "Modules/Gametypes.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/Lean.hpp"
#include "Modules/Localization.hpp"
#include "Modules/MapDump.hpp"
#include "Modules/MapRotation.hpp"
#include "Modules/Maps.hpp"
#include "Modules/Materials.hpp"
#include "Modules/Menus.hpp"
#include "Modules/ModList.hpp"
#include "Modules/ModelSurfs.hpp"
#include "Modules/NetworkDebug.hpp"
#include "Modules/News.hpp"
#include "Modules/Node.hpp"
#include "Modules/Party.hpp"
#include "Modules/PlayerMovement.hpp"
#include "Modules/PlayerName.hpp"
#include "Modules/Playlist.hpp"
#include "Modules/QuickPatch.hpp"
#include "Modules/RCon.hpp"
#include "Modules/RawFiles.hpp"
#include "Modules/RawMouse.hpp"
#include "Modules/Renderer.hpp"
#include "Modules/Scheduler.hpp"
#include "Modules/Security.hpp"
#include "Modules/ServerCommands.hpp"
#include "Modules/ServerInfo.hpp"
#include "Modules/ServerList.hpp"
#include "Modules/Session.hpp"
#include "Modules/SlowMotion.hpp"
#include "Modules/ArenaLength.hpp"
#include "Modules/StringTable.hpp"
#include "Modules/ZoneBuilder.hpp"
#include "Modules/AssetHandler.hpp"
#include "Modules/Localization.hpp"
#include "Modules/ServerCommands.hpp"
#include "Modules/StructuredData.hpp"
#include "Modules/ConnectProtocol.hpp"
#include "Modules/SoundMutexFix.hpp"
#include "Modules/StartupMessages.hpp"
#include "Modules/Stats.hpp"
#include "Modules/SoundMutexFix.hpp"
#include "Modules/Chat.hpp"
#include "Modules/StringTable.hpp"
#include "Modules/StructuredData.hpp"
#include "Modules/TextRenderer.hpp"
#include "Modules/PlayerMovement.hpp"
#include "Modules/Elevators.hpp"
#include "Modules/ClientCommand.hpp"
#include "Modules/VisionFile.hpp"
#include "Modules/Gamepad.hpp"
#include "Modules/Branding.hpp"
#include "Modules/Debug.hpp"
#include "Modules/RawMouse.hpp"
#include "Modules/Bullet.hpp"
#include "Modules/MapRotation.hpp"
#include "Modules/Ceg.hpp"
#include "Modules/Theatre.hpp"
#include "Modules/Threading.hpp"
#include "Modules/Toast.hpp"
#include "Modules/UIFeeder.hpp"
#include "Modules/UserInfo.hpp"
#include "Modules/Events.hpp"
#include "Modules/VisionFile.hpp"
#include "Modules/Voice.hpp"
#include "Modules/Vote.hpp"
#include "Modules/Weapon.hpp"
#include "Modules/Window.hpp"
#include "Modules/Zones.hpp"
#include "Modules/GSC/GSC.hpp"

View File

@ -2,7 +2,7 @@
namespace Components
{
std::string CardTitles::CustomTitles[18];
char CardTitles::CustomTitles[Game::MAX_CLIENTS][18];
Dvar::Var CardTitles::CustomTitle;
CClient* CardTitles::GetClientByIndex(std::uint32_t index)
@ -10,35 +10,34 @@ namespace Components
return &reinterpret_cast<CClient*>(0x8E77B0)[index];
}
std::int32_t CardTitles::GetPlayerCardClientInfo(std::int32_t lookupResult, Game::PlayerCardData* data)
int CardTitles::GetPlayerCardClientInfo(int lookupResult, Game::PlayerCardData* data)
{
std::int32_t returnResult = lookupResult;
auto result = lookupResult;
std::string username = Dvar::Var("name").get<std::string>();
if (data->name == username)
const auto* username = Dvar::Var("name").get<const char*>();
if (std::strcmp(data->name, username) == 0)
{
returnResult += 0xFE000000;
result += 0xFE000000;
}
else
{
for (std::size_t clientNum = 0; clientNum < Game::MAX_CLIENTS; ++clientNum)
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{
CClient* c = GetClientByIndex(clientNum);
CClient* c = GetClientByIndex(i);
if (c != nullptr)
{
if (!std::strcmp(data->name, c->Name))
{
// Since a 4 byte integer is overkill for a row num: We can use it to store the customprefix + clientNum and use a 2 byte integer for the row number
returnResult += 0xFF000000;
returnResult += clientNum * 0x10000;
result += 0xFF000000;
result += i * 0x10000;
break;
}
}
}
}
return returnResult;
return result;
}
void __declspec(naked) CardTitles::GetPlayerCardClientInfoStub()
@ -71,7 +70,7 @@ namespace Components
std::uint8_t prefix = (request->tableRow >> (8 * 3)) & 0xFF;
std::uint8_t data = (request->tableRow >> (8 * 2)) & 0xFF;
if (data >= ARRAYSIZE(CardTitles::CustomTitles)) return nullptr;
if (data >= Game::MAX_CLIENTS) return nullptr;
if (request->tablename == "mp/cardTitleTable.csv"s)
{
@ -82,10 +81,10 @@ namespace Components
{
if (prefix == 0xFE)
{
if (!CardTitles::CustomTitle.get<std::string>().empty())
if (!CustomTitle.get<std::string>().empty())
{
// 0xFF in front of the title to skip localization. Or else it will wait for a couple of seconds for the asset of type localize
const char* title = Utils::String::VA("\x15%s", CardTitles::CustomTitle.get<const char*>());
const auto* title = Utils::String::VA("\x15%s", CustomTitle.get<const char*>());
// prepare return value
operand->internals.stringVal.string = title;
@ -96,9 +95,9 @@ namespace Components
}
else if (prefix == 0xFF)
{
if (!CardTitles::CustomTitles[data].empty())
if (CustomTitles[data][0] != '\0')
{
const char* title = Utils::String::VA("\x15%s", CardTitles::CustomTitles[data].data());
const auto* title = Utils::String::VA("\x15%s", CustomTitles[data]);
// prepare return value
operand->internals.stringVal.string = title;
@ -156,11 +155,11 @@ namespace Components
{
std::string list;
for (std::size_t i = 0; i < Game::MAX_CLIENTS; i++)
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{
char playerTitle[18];
char playerTitle[18]{};
if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED)
if (Game::svs_clients[i].userinfo[0] != '\0')
{
strncpy_s(playerTitle, Game::Info_ValueForKey(Game::svs_clients[i].userinfo, "customTitle"), _TRUNCATE);
}
@ -180,10 +179,17 @@ namespace Components
{
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{
const char* playerTitle = Game::Info_ValueForKey(msg, std::to_string(i).c_str());
const auto index = std::to_string(i);
const auto* playerTitle = Game::Info_ValueForKey(msg, index.data());
if (playerTitle) CardTitles::CustomTitles[i] = playerTitle;
else CardTitles::CustomTitles[i].clear();
if (playerTitle[0] == '\0')
{
CustomTitles[i][0] = '\0';
}
else
{
Game::I_strncpyz(CustomTitles[i], playerTitle, sizeof(CustomTitles[0]) / sizeof(char));
}
}
}
@ -191,16 +197,18 @@ namespace Components
{
Scheduler::Once([]
{
CardTitles::CustomTitle = Dvar::Register<const char*>("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title");
CustomTitle = Dvar::Register<const char*>("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title");
}, Scheduler::Pipeline::MAIN);
std::memset(&CustomTitles, 0, sizeof(char[Game::MAX_CLIENTS][18]));
ServerCommands::OnCommand(21, [](Command::Params* params)
{
if (params->get(1) == "customTitles"s && !Dedicated::IsEnabled())
if (std::strcmp(params->get(1), "customTitles") == 0)
{
if (params->size() == 3)
{
CardTitles::ParseCustomTitles(params->get(2));
ParseCustomTitles(params->get(2));
return true;
}
}
@ -209,10 +217,10 @@ namespace Components
});
Utils::Hook(0x62EB26, CardTitles::GetPlayerCardClientInfoStub).install()->quick();
Utils::Hook(0x62EB26, GetPlayerCardClientInfoStub).install()->quick();
// Table lookup stuff
Utils::Hook(0x62DCC1, CardTitles::TableLookupByRowHookStub).install()->quick();
Utils::Hook(0x62DCC1, TableLookupByRowHookStub).install()->quick();
Utils::Hook::Nop(0x62DCC6, 1);
}
}

View File

@ -44,19 +44,20 @@ namespace Components
public:
AssertOffset(Game::PlayerCardData, Game::PlayerCardData::name, 0x1C);
static Dvar::Var CustomTitle;
static std::string CustomTitles[18];
static void SendCustomTitlesToClients();
static void ParseCustomTitles(const char* msg);
CardTitles();
private:
static Dvar::Var CustomTitle;
static char CustomTitles[Game::MAX_CLIENTS][18];
static CClient* GetClientByIndex(std::uint32_t index);
static std::int32_t GetPlayerCardClientInfo(std::int32_t lookupResult, Game::PlayerCardData* data);
static int GetPlayerCardClientInfo(int lookupResult, Game::PlayerCardData* data);
static void GetPlayerCardClientInfoStub();
static const char* TableLookupByRowHook(Game::Operand* operand, tablelookuprequest_s* request);
static void TableLookupByRowHookStub();
static void ParseCustomTitles(const char* msg);
};
}

View File

@ -186,24 +186,24 @@ namespace Components
}
}
template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Flag flag, const char* description)
template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, std::uint16_t flag, const char* description)
{
return Game::Dvar_RegisterBool(dvarName, value, flag.val, description);
return Game::Dvar_RegisterBool(dvarName, value, flag, description);
}
template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Flag flag, const char* description)
template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, std::uint16_t flag, const char* description)
{
return Game::Dvar_RegisterString(dvarName, value, flag.val, description);
return Game::Dvar_RegisterString(dvarName, value, flag, description);
}
template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Flag flag, const char* description)
template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, std::uint16_t flag, const char* description)
{
return Game::Dvar_RegisterInt(dvarName, value, min, max, flag.val, description);
return Game::Dvar_RegisterInt(dvarName, value, min, max, flag, description);
}
template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Flag flag, const char* description)
template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, std::uint16_t flag, const char* description)
{
return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description);
return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag, description);
}
void Dvar::ResetDvarsValue()

View File

@ -5,15 +5,6 @@ namespace Components
class Dvar : public Component
{
public:
class Flag
{
public:
Flag(Game::DvarFlags flag) : val(flag) {}
Flag(std::uint16_t flag) : Flag(static_cast<Game::DvarFlags>(flag)) {}
Game::DvarFlags val;
};
class Var
{
public:
@ -44,8 +35,8 @@ namespace Components
~Dvar();
// Only strings and bools use this type of declaration
template<typename T> static Var Register(const char* dvarName, T value, Flag flag, const char* description);
template<typename T> static Var Register(const char* dvarName, T value, T min, T max, Flag flag, const char* description);
template<typename T> static Var Register(const char* dvarName, T value, std::uint16_t flag, const char* description);
template<typename T> static Var Register(const char* dvarName, T value, T min, T max, std::uint16_t flag, const char* description);
static void ResetDvarsValue();

View File

@ -7,20 +7,10 @@ namespace Components
Utils::Hook Exception::SetFilterHook;
int Exception::MiniDumpType;
__declspec(noreturn) void Exception::ErrorLongJmp(jmp_buf _Buf, int _Value)
{
if (!*reinterpret_cast<DWORD*>(0x1AD7EB4))
{
TerminateProcess(GetCurrentProcess(), 1337);
}
longjmp(_Buf, _Value);
}
__declspec(noreturn) void Exception::LongJmp(jmp_buf _Buf, int _Value)
__declspec(noreturn) void Exception::LongJmp_Internal_Stub(jmp_buf env, int status)
{
AssetHandler::ResetBypassState();
longjmp(_Buf, _Value);
Game::longjmp_internal(env, status);
}
void Exception::SuspendProcess()
@ -76,7 +66,7 @@ namespace Components
return;
}
auto lock = GlobalLock(hMem);
auto* lock = GlobalLock(hMem);
if (lock != nullptr)
{
std::memcpy(lock, error.data(), error.size() + 1);
@ -107,17 +97,15 @@ namespace Components
errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
}
//Exception::SuspendProcess();
// Message should be copied to the keyboard if no button is pressed
if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES)
{
Exception::CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress));
CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress));
}
if (Flags::HasFlag("bigminidumps"))
{
Exception::SetMiniDumpType(true, false);
SetMiniDumpType(true, false);
}
// Current executable name
@ -134,23 +122,22 @@ namespace Components
_localtime64_s(&ltime, &time);
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime);
// Combine with queuedMinidumpsFolder
char filename[MAX_PATH] = { 0 };
Utils::IO::CreateDir("minidumps");
// Combine with queued MinidumpsFolder
char filename[MAX_PATH]{};
CreateDirectoryA("minidumps", nullptr);
PathCombineA(filename, "minidumps\\", Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime));
DWORD fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
constexpr auto fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE | GENERIC_READ, fileShare, nullptr, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, nullptr);
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE };
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(Exception::MiniDumpType), &ex, nullptr, nullptr))
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(MiniDumpType), &ex, nullptr, nullptr))
{
MessageBoxA(nullptr, Utils::String::VA("There was an error creating the minidump (%s)! Hit OK to close the program.", Utils::GetLastWindowsError().data()), "Minidump Error", MB_OK | MB_ICONERROR);
MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR);
OutputDebugStringA("Failed to create new minidump!");
Utils::OutputDebugLastError();
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
//if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
{
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
@ -158,54 +145,39 @@ namespace Components
return EXCEPTION_CONTINUE_SEARCH;
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
Exception::SetFilterHook.uninstall();
LPTOP_LEVEL_EXCEPTION_FILTER retval = SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
Exception::SetFilterHook.install();
return retval;
}
LPTOP_LEVEL_EXCEPTION_FILTER Exception::Hook()
{
return SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
}
void Exception::SetMiniDumpType(bool codeseg, bool dataseg)
{
Exception::MiniDumpType = MiniDumpIgnoreInaccessibleMemory;
Exception::MiniDumpType |= MiniDumpWithHandleData;
Exception::MiniDumpType |= MiniDumpScanMemory;
Exception::MiniDumpType |= MiniDumpWithProcessThreadData;
Exception::MiniDumpType |= MiniDumpWithFullMemoryInfo;
Exception::MiniDumpType |= MiniDumpWithThreadInfo;
//Exception::MiniDumpType |= MiniDumpWithModuleHeaders;
MiniDumpType = MiniDumpIgnoreInaccessibleMemory;
MiniDumpType |= MiniDumpWithHandleData;
MiniDumpType |= MiniDumpScanMemory;
MiniDumpType |= MiniDumpWithProcessThreadData;
MiniDumpType |= MiniDumpWithFullMemoryInfo;
MiniDumpType |= MiniDumpWithThreadInfo;
if (codeseg)
{
Exception::MiniDumpType |= MiniDumpWithCodeSegs;
MiniDumpType |= MiniDumpWithCodeSegs;
}
if (dataseg)
{
Exception::MiniDumpType |= MiniDumpWithDataSegs;
MiniDumpType |= MiniDumpWithDataSegs;
}
}
Exception::Exception()
{
Exception::SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps"));
#if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER)
Exception::SetFilterHook.initialize(SetUnhandledExceptionFilter, Exception::SetUnhandledExceptionFilterStub, HOOK_JUMP);
Exception::SetFilterHook.install();
SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps"));
SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
#endif
//Utils::Hook(0x4B241F, Exception::ErrorLongJmp, HOOK_CALL).install()->quick();
Utils::Hook(0x6B8898, Exception::LongJmp, HOOK_JUMP).install()->quick();
Utils::Hook(0x4B241F, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61DB44, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F17D, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F248, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F5E7, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
#ifdef _DEBUG
#ifdef MAP_TEST
Command::Add("mapTest", [](Command::Params* params)
{
Game::UI_UpdateArenas();
@ -227,6 +199,6 @@ namespace Components
Exception::~Exception()
{
Exception::SetFilterHook.uninstall();
SetFilterHook.uninstall();
}
}

View File

@ -15,9 +15,7 @@ namespace Components
private:
static void SuspendProcess();
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
static __declspec(noreturn) void ErrorLongJmp(jmp_buf _Buf, int _Value);
static __declspec(noreturn) void LongJmp(jmp_buf _Buf, int _Value);
static __declspec(noreturn) void LongJmp_Internal_Stub(jmp_buf env, int status);
static void CopyMessageToClipboard(const std::string& error);

View File

@ -319,7 +319,7 @@ namespace Components
auto clientCount = 0;
auto maxClientCount = *Game::svs_clientCount;
const auto securityLevel = Dvar::Var("sv_securityLevel").get<int>();
const auto password = Dvar::Var("g_password").get<std::string>();
const auto* password = (*Game::g_password)->current.string;
if (maxClientCount)
{
@ -352,7 +352,7 @@ namespace Components
info.set("shortversion", SHORTVERSION);
info.set("checksum", std::to_string(Game::Sys_Milliseconds()));
info.set("mapname", Dvar::Var("mapname").get<std::string>());
info.set("isPrivate", password.empty() ? "0" : "1");
info.set("isPrivate", *password ? "1" : "0");
info.set("hc", (Dvar::Var("g_hardcore").get<bool>() ? "1" : "0"));
info.set("securityLevel", std::to_string(securityLevel));
info.set("sv_running", (Dedicated::IsRunning() ? "1" : "0"));

View File

@ -9,26 +9,26 @@ namespace Components
void Playlist::LoadPlaylist()
{
// Check if playlist already loaded
if (Utils::Hook::Get<bool>(0x1AD3680)) return;
if (*Game::s_havePlaylists) return;
// Don't load playlists when dedi and no party
if (Dedicated::IsEnabled() && !Dvar::Var("party_enable").get<bool>())
{
Utils::Hook::Set<bool>(0x1AD3680, true); // Set received to true
*Game::s_havePlaylists = true;
Dvar::Var("xblive_privateserver").set(true);
return;
}
Dvar::Var("xblive_privateserver").set(false);
auto playlistFilename = Dvar::Var("playlistFilename").get<std::string>();
const auto playlistFilename = Dvar::Var("playlistFilename").get<std::string>();
FileSystem::File playlist(playlistFilename);
if (playlist.exists())
{
Logger::Print("Parsing playlist '{}'...\n", playlist.getName());
Game::Playlist_ParsePlaylists(playlist.getBuffer().data());
Utils::Hook::Set<bool>(0x1AD3680, true); // Playlist loaded
*Game::s_havePlaylists = true;
}
else
{
@ -36,18 +36,18 @@ namespace Components
}
}
DWORD Playlist::StorePlaylistStub(const char** buffer)
char* Playlist::Com_ParseOnLine_Hk(const char** data_p)
{
Playlist::MapRelocation.clear();
Playlist::CurrentPlaylistBuffer = Utils::Compression::ZLib::Compress(*buffer);
return Utils::Hook::Call<DWORD(const char**)>(0x4C0350)(buffer);
MapRelocation.clear();
CurrentPlaylistBuffer = Utils::Compression::ZLib::Compress(*data_p);
return Game::Com_ParseOnLine(data_p);
}
void Playlist::PlaylistRequest(const Network::Address& address, [[maybe_unused]] const std::string& data)
{
const auto password = Dvar::Var("g_password").get<std::string>();
const auto* password = (*Game::g_password)->current.string;
if (password.length())
if (*password)
{
if (password != data)
{
@ -58,7 +58,7 @@ namespace Components
Logger::Print("Received playlist request, sending currently stored buffer.\n");
std::string compressedList = Playlist::CurrentPlaylistBuffer;
std::string compressedList = CurrentPlaylistBuffer;
Proto::Party::Playlist list;
list.set_hash(Utils::Cryptography::JenkinsOneAtATime::Compute(compressedList));
@ -67,19 +67,26 @@ namespace Components
Network::SendCommand(address, "playlistResponse", list.SerializeAsString());
}
void Playlist::PlaylistReponse(const Network::Address& address, [[maybe_unused]] const std::string& data)
void Playlist::PlaylistResponse(const Network::Address& address, [[maybe_unused]] const std::string& data)
{
if (Party::PlaylistAwaiting())
if (!Party::PlaylistAwaiting())
{
if (address == Party::Target())
Logger::Print("Received stray playlist response, ignoring it.\n");
return;
}
if (address != Party::Target())
{
Logger::Print("Received playlist from someone else than our target host, ignoring it.\n");
return;
}
Proto::Party::Playlist list;
if (!list.ParseFromString(data))
{
Party::PlaylistError(Utils::String::VA("Received playlist response from %s, but it is invalid.", address.getCString()));
Playlist::ReceivedPlaylistBuffer.clear();
return;
Party::PlaylistError(std::format("Received playlist response from {}, but it is invalid.", address.getString()));
ReceivedPlaylistBuffer.clear();
}
else
{
@ -87,60 +94,50 @@ namespace Components
const auto& compressedData = list.buffer();
const auto hash = Utils::Cryptography::JenkinsOneAtATime::Compute(compressedData);
//Validate hashes
// Validate hashes
if (hash != list.hash())
{
Party::PlaylistError(Utils::String::VA("Received playlist response from %s, but the checksum did not match (%X != %X).", address.getCString(), list.hash(), hash));
Playlist::ReceivedPlaylistBuffer.clear();
Party::PlaylistError(std::format("Received playlist response from {}, but the checksum did not match ({} != {}).", address.getString(), list.hash(), hash));
ReceivedPlaylistBuffer.clear();
return;
}
// Decompress buffer
Playlist::ReceivedPlaylistBuffer = Utils::Compression::ZLib::Decompress(compressedData);
ReceivedPlaylistBuffer = Utils::Compression::ZLib::Decompress(compressedData);
// Load and continue connection
Logger::Print("Received playlist, loading and continuing connection...\n");
Game::Playlist_ParsePlaylists(Playlist::ReceivedPlaylistBuffer.data());
Game::Playlist_ParsePlaylists(ReceivedPlaylistBuffer.data());
Party::PlaylistContinue();
}
}
else
{
Logger::Print("Received playlist from someone else than our target host, ignoring it.\n");
}
}
else
{
Logger::Print("Received stray playlist response, ignoring it.\n");
}
}
void Playlist::PlaylistInvalidPassword([[maybe_unused]] const Network::Address& address, [[maybe_unused]] const std::string& data)
{
Party::PlaylistError("Error: Invalid Password for Party.");
}
void Playlist::MapNameCopy(char *dest, const char *src, int destsize)
void Playlist::MapNameCopy(char* dest, const char* src, int destsize)
{
Utils::Hook::Call<void(char*, const char*, int)>(0x4D6F80)(dest, src, destsize);
Playlist::MapRelocation[dest] = src;
MapRelocation[dest] = src;
}
void Playlist::SetMapName(const char* cvar, const char* value)
void Playlist::SetMapName(const char* dvarName, const char* value)
{
auto i = Playlist::MapRelocation.find(value);
if (i != Playlist::MapRelocation.end())
auto i = MapRelocation.find(value);
if (i != MapRelocation.end())
{
value = i->second.data();
}
Game::Dvar_SetStringByName(cvar, value);
Game::Dvar_SetStringByName(dvarName, value);
}
int Playlist::GetMapIndex(const char* mapname)
{
auto i = Playlist::MapRelocation.find(mapname);
if (i != Playlist::MapRelocation.end())
auto i = MapRelocation.find(mapname);
if (i != MapRelocation.end())
{
mapname = i->second.data();
}
@ -153,7 +150,7 @@ namespace Components
// Default playlists
Utils::Hook::Set<const char*>(0x60B06E, "playlists_default.info");
// disable playlist download function
// Disable playlist download function
Utils::Hook::Set<BYTE>(0x4D4790, 0xC3);
// Load playlist, but don't delete it
@ -161,34 +158,34 @@ namespace Components
Utils::Hook::Nop(0x4D6E67, 5);
Utils::Hook::Nop(0x4D6E71, 2);
// playlist dvar 'validity check'
// Disable Playlist_ValidatePlaylistNum
Utils::Hook::Set<BYTE>(0x4B1170, 0xC3);
// disable playlist checking
Utils::Hook::Set<BYTE>(0x5B69E9, 0xEB); // too new
Utils::Hook::Set<BYTE>(0x5B696E, 0xEB); // too old
// Disable playlist checking
Utils::Hook::Set<BYTE>(0x5B69E9, 0xEB); // Too new
Utils::Hook::Set<BYTE>(0x5B696E, 0xEB); // Too old
//Got playlists is true
// Got playlists is true
//Utils::Hook::Set<bool>(0x1AD3680, true);
Utils::Hook(0x497DB5, Playlist::GetMapIndex, HOOK_CALL).install()->quick();
Utils::Hook(0x42A19D, Playlist::MapNameCopy, HOOK_CALL).install()->quick();
Utils::Hook(0x4A6FEE, Playlist::SetMapName, HOOK_CALL).install()->quick();
Utils::Hook(0x497DB5, GetMapIndex, HOOK_CALL).install()->quick();
Utils::Hook(0x42A19D, MapNameCopy, HOOK_CALL).install()->quick();
Utils::Hook(0x4A6FEE, SetMapName, HOOK_CALL).install()->quick();
// Store playlist buffer on load
Utils::Hook(0x42961C, Playlist::StorePlaylistStub, HOOK_CALL).install()->quick();
Utils::Hook(0x42961C, Com_ParseOnLine_Hk, HOOK_CALL).install()->quick(); // Playlist_ParsePlaylists
//if (Dedicated::IsDedicated())
{
// Custom playlist loading
Utils::Hook(0x420B5A, Playlist::LoadPlaylist, HOOK_JUMP).install()->quick();
Utils::Hook(0x420B5A, LoadPlaylist, HOOK_JUMP).install()->quick();
// disable playlist.ff loading function
Utils::Hook::Set<BYTE>(0x4D6E60, 0xC3);
// disable playlist.ff loading function (Win_LoadPlaylistFastfile)
Utils::Hook::Set<std::uint8_t>(0x4D6E60, 0xC3);
}
Network::OnClientPacket("getPlaylist", PlaylistRequest);
Network::OnClientPacket("playlistResponse", PlaylistReponse);
Network::OnClientPacket("playlistResponse", PlaylistResponse);
Network::OnClientPacket("playlistInvalidPassword", PlaylistInvalidPassword);
}
}

View File

@ -17,14 +17,14 @@ namespace Components
static std::string CurrentPlaylistBuffer;
static std::unordered_map<const void*, std::string> MapRelocation;
static DWORD StorePlaylistStub(const char** buffer);
static char* Com_ParseOnLine_Hk(const char** data_p);
static void PlaylistRequest(const Network::Address& address, const std::string& data);
static void PlaylistReponse(const Network::Address& address, const std::string& data);
static void PlaylistResponse(const Network::Address& address, const std::string& data);
static void PlaylistInvalidPassword(const Network::Address& address, const std::string& data);
static void MapNameCopy(char *dest, const char *src, int destsize);
static void SetMapName(const char* cvar, const char* value);
static void MapNameCopy(char* dest, const char* src, int destsize);
static void SetMapName(const char* dvarName, const char* value);
static int GetMapIndex(const char* mapname);
};
}

View File

@ -131,7 +131,7 @@ namespace Components
Utils::InfoString ServerInfo::GetInfo()
{
auto maxClientCount = *Game::svs_clientCount;
const auto password = Dvar::Var("g_password").get<std::string>();
const auto* password = (*Game::g_password)->current.string;
if (!maxClientCount)
{
@ -145,7 +145,7 @@ namespace Components
info.set("shortversion", SHORTVERSION);
info.set("version", (*Game::version)->current.string);
info.set("mapname", (*Game::sv_mapname)->current.string);
info.set("isPrivate", password.empty() ? "0" : "1");
info.set("isPrivate", *password ? "1" : "0");
info.set("checksum", Utils::String::VA("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(Utils::String::VA("%u", Game::Sys_Milliseconds()))));
info.set("aimAssist", (Gamepad::sv_allowAimAssist.get<bool>() ? "1" : "0"));
info.set("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0"));

View File

@ -17,7 +17,7 @@ namespace Components
{
printf("%s", "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n");
printf("%d\n", REVISION);
ExitProcess(0);
std::exit(0);
}
Console::FreeNativeConsole();
@ -28,7 +28,7 @@ namespace Components
if (!FirstInstance && !ConnectProtocol::Used() && MessageBoxA(nullptr, "Do you want to start another instance?\nNot all features will be available!", "Game already running", MB_ICONEXCLAMATION | MB_YESNO) == IDNO)
{
ExitProcess(0);
std::exit(0);
}
}
}

View File

@ -49,7 +49,6 @@ BOOL APIENTRY DllMain(HINSTANCE /*hinstDLL*/, DWORD fdwReason, LPVOID /*lpvReser
if (fdwReason == DLL_PROCESS_ATTACH)
{
SetProcessDEPPolicy(PROCESS_DEP_ENABLE);
Steam::Proxy::RunMod();
std::srand(std::uint32_t(std::time(nullptr)) ^ ~(GetTickCount() * GetCurrentProcessId()));
@ -72,6 +71,7 @@ BOOL APIENTRY DllMain(HINSTANCE /*hinstDLL*/, DWORD fdwReason, LPVOID /*lpvReser
}
#endif
Steam::Proxy::RunMod();
// Install entry point hook
Utils::Hook(0x6BAC0F, Main::EntryPoint, HOOK_JUMP).install()->quick();
}

View File

@ -31,6 +31,8 @@ namespace Game
int* com_errorPrintsCount = reinterpret_cast<int*>(0x1AD7910);
int* errorcode = reinterpret_cast<int*>(0x1AD7EB4);
char* Com_GetParseThreadInfo()
{
if (Sys_IsMainThread())

View File

@ -35,7 +35,7 @@ namespace Game
typedef void(*Com_BeginParseSession_t)(const char* filename);
extern Com_BeginParseSession_t Com_BeginParseSession;
typedef char* (*Com_ParseOnLine_t)(const char** data_p);
typedef char*(*Com_ParseOnLine_t)(const char** data_p);
extern Com_ParseOnLine_t Com_ParseOnLine;
typedef void(*Com_SkipRestOfLine_t)(const char** data);
@ -71,6 +71,8 @@ namespace Game
extern int* com_errorPrintsCount;
extern int* errorcode;
extern char* Com_GetParseThreadInfo();
extern void Com_SetParseNegativeNumbers(int parse);

View File

@ -52,6 +52,7 @@ namespace Game
const dvar_t** g_allowVote = reinterpret_cast<const dvar_t**>(0x19BD644);
const dvar_t** g_oldVoting = reinterpret_cast<const dvar_t**>(0x1A45DEC);
const dvar_t** g_gametype = reinterpret_cast<const dvar_t**>(0x1A45DC8);
const dvar_t** g_password = reinterpret_cast<const dvar_t**>(0x18835C0);
const dvar_t** version = reinterpret_cast<const dvar_t**>(0x1AD7930);

View File

@ -108,6 +108,7 @@ namespace Game
extern const dvar_t** g_allowVote;
extern const dvar_t** g_oldVoting;
extern const dvar_t** g_gametype;
extern const dvar_t** g_password;
extern const dvar_t** version;

View File

@ -268,6 +268,8 @@ namespace Game
LargeLocalBeginRight_t LargeLocalBeginRight = LargeLocalBeginRight_t(0x644140);
LargeLocalReset_t LargeLocalReset = LargeLocalReset_t(0x430630);
longjmp_internal_t longjmp_internal = longjmp_internal_t(0x6B8898);
CmdArgs* cmd_args = reinterpret_cast<CmdArgs*>(0x1AAC5D0);
CmdArgs* sv_cmd_args = reinterpret_cast<CmdArgs*>(0x1ACF8A0);
@ -405,6 +407,8 @@ namespace Game
punctuation_s* default_punctuations = reinterpret_cast<punctuation_s*>(0x797F80);
bool* s_havePlaylists = reinterpret_cast<bool*>(0x1AD3680);
const char* TableLookup(StringTable* stringtable, int row, int column)
{
if (!stringtable || !stringtable->values || row >= stringtable->rowCount || column >= stringtable->columnCount) return "";

View File

@ -593,6 +593,9 @@ namespace Game
typedef void(*LargeLocalReset_t)();
extern LargeLocalReset_t LargeLocalReset;
typedef void(*longjmp_internal_t)(jmp_buf env, int status);
extern longjmp_internal_t longjmp_internal;
constexpr std::size_t STATIC_MAX_LOCAL_CLIENTS = 1;
constexpr std::size_t MAX_LOCAL_CLIENTS = 1;
constexpr std::size_t MAX_CLIENTS = 18;
@ -739,6 +742,8 @@ namespace Game
extern punctuation_s* default_punctuations;
extern bool* s_havePlaylists;
constexpr auto MAX_MSGLEN = 0x20000;
ScreenPlacement* ScrPlace_GetFullPlacement();