Use premake.

This commit is contained in:
momo5502
2015-12-29 02:52:31 +01:00
parent 93d1380139
commit 87c1c36943
85 changed files with 85 additions and 519 deletions

55
src/Components/Loader.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "..\STDInclude.hpp"
namespace Components
{
std::vector<Component*> Loader::Components;
void Loader::Initialize()
{
Loader::Register(new Dedicated());
Loader::Register(new Dvar());
Loader::Register(new Maps());
Loader::Register(new Menus());
Loader::Register(new Party());
Loader::Register(new Colors());
Loader::Register(new Logger());
Loader::Register(new Window());
Loader::Register(new Command());
Loader::Register(new Console());
Loader::Register(new Network());
Loader::Register(new RawFiles());
Loader::Register(new Renderer());
Loader::Register(new UIFeeder());
Loader::Register(new UIScript());
Loader::Register(new FastFiles());
Loader::Register(new Materials());
Loader::Register(new Singleton());
Loader::Register(new FileSystem());
Loader::Register(new QuickPatch());
Loader::Register(new ServerList());
Loader::Register(new AssetHandler());
Loader::Register(new Localization());
Loader::Register(new MusicalTalent());
}
void Loader::Uninitialize()
{
for (auto component : Loader::Components)
{
Logger::Print("Unregistering component: %s", component->GetName());
delete component;
}
Loader::Components.clear();
}
void Loader::Register(Component* component)
{
if (component)
{
Logger::Print("Component registered: %s", component->GetName());
Loader::Components.push_back(component);
}
}
}

46
src/Components/Loader.hpp Normal file
View File

@ -0,0 +1,46 @@
namespace Components
{
class Component
{
public:
Component() {};
virtual ~Component() {};
virtual const char* GetName() { return "Unknown"; };
};
class Loader
{
public:
static void Initialize();
static void Uninitialize();
static void Register(Component* component);
private:
static std::vector<Component*> Components;
};
}
#include "Modules\Dvar.hpp"
#include "Modules\Maps.hpp"
#include "Modules\Menus.hpp"
#include "Modules\Colors.hpp"
#include "Modules\Logger.hpp"
#include "Modules\Window.hpp"
#include "Modules\Command.hpp"
#include "Modules\Console.hpp"
#include "Modules\Network.hpp"
#include "Modules\Party.hpp" // Destroys the order, but requires network classes :D
#include "Modules\RawFiles.hpp"
#include "Modules\Renderer.hpp"
#include "Modules\UIFeeder.hpp"
#include "Modules\UIScript.hpp"
#include "Modules\Dedicated.hpp"
#include "Modules\FastFiles.hpp"
#include "Modules\Materials.hpp"
#include "Modules\Singleton.hpp"
#include "Modules\FileSystem.hpp"
#include "Modules\QuickPatch.hpp"
#include "Modules\ServerList.hpp"
#include "Modules\AssetHandler.hpp"
#include "Modules\Localization.hpp"
#include "Modules\MusicalTalent.hpp"

View File

@ -0,0 +1,158 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
bool AssetHandler::BypassState = false;
std::map<Game::XAssetType, AssetHandler::Callback> AssetHandler::TypeCallbacks;
std::vector<AssetHandler::RestrictCallback> AssetHandler::RestrictCallbacks;
std::map<void*, void*> AssetHandler::Relocations;
Game::XAssetHeader AssetHandler::FindAsset(Game::XAssetType type, const char* filename)
{
Game::XAssetHeader header = { 0 };
// Allow call DB_FindXAssetHeader within the hook
AssetHandler::BypassState = true;
if (AssetHandler::TypeCallbacks.find(type) != AssetHandler::TypeCallbacks.end())
{
header = AssetHandler::TypeCallbacks[type](type, filename);
}
// Disallow calling DB_FindXAssetHeader ;)
AssetHandler::BypassState = false;
return header;
}
void __declspec(naked) AssetHandler::FindAssetStub()
{
__asm
{
push ecx
push ebx
push ebp
push esi
push edi
// Check if custom handler should be bypassed
xor eax, eax
mov al, AssetHandler::BypassState
test al, al
jnz finishOriginal
mov ecx, [esp + 18h] // Asset type
mov ebx, [esp + 1Ch] // Filename
push ebx
push ecx
call AssetHandler::FindAsset
add esp, 8h
test eax, eax
jnz finishFound
finishOriginal:
// Asset not found using custom handlers, redirect to DB_FindXAssetHeader
mov ebx, ds:6D7190h // InterlockedDecrement
mov eax, 40793Bh
jmp eax
finishFound:
pop edi
pop esi
pop ebp
pop ebx
pop ecx
retn
}
}
bool AssetHandler::IsAssetEligible(Game::XAssetType type, Game::XAssetHeader *asset)
{
const char* name = Game::DB_GetXAssetNameHandlers[type](asset);
if (!name) return false;
for (auto callback : AssetHandler::RestrictCallbacks)
{
if (!callback(type, *asset, name))
{
return false;
}
}
return true;
}
void __declspec(naked) AssetHandler::AddAssetStub()
{
__asm
{
push [esp + 8]
push [esp + 8]
call AssetHandler::IsAssetEligible
add esp, 08h
test al, al
jz doNotLoad
mov eax, [esp + 8]
sub esp, 14h
mov ecx, 5BB657h
jmp ecx
doNotLoad:
mov eax, [esp + 8]
retn
}
}
void AssetHandler::OnFind(Game::XAssetType type, AssetHandler::Callback callback)
{
AssetHandler::TypeCallbacks[type] = callback;
}
void AssetHandler::OnLoad(RestrictCallback callback)
{
AssetHandler::RestrictCallbacks.push_back(callback);
}
void AssetHandler::Relocate(void* start, void* to, DWORD size)
{
for (DWORD i = 0; i < size; i += 4)
{
AssetHandler::Relocations[reinterpret_cast<char*>(start) + i] = reinterpret_cast<char*>(to) + i;
}
}
void AssetHandler::OffsetToAlias(FastFiles::Offset* offset)
{
offset->fullPointer = *reinterpret_cast<void**>((*Game::g_streamBlocks)[offset->GetDecrementedStream()].data + offset->GetDecrementedPointer());
if (AssetHandler::Relocations.find(offset->fullPointer) != AssetHandler::Relocations.end())
{
offset->fullPointer = AssetHandler::Relocations[offset->fullPointer];
}
}
AssetHandler::AssetHandler()
{
// DB_FindXAssetHeader
Utils::Hook(Game::DB_FindXAssetHeader, AssetHandler::FindAssetStub).Install()->Quick();
// DB_ConvertOffsetToAlias
Utils::Hook(0x4FDFA0, AssetHandler::OffsetToAlias, HOOK_JUMP).Install()->Quick();
// DB_AddXAsset
Utils::Hook(0x5BB650, AssetHandler::AddAssetStub, HOOK_JUMP).Install()->Quick();
}
AssetHandler::~AssetHandler()
{
AssetHandler::TypeCallbacks.clear();
}
}

View File

@ -0,0 +1,33 @@
namespace Components
{
class AssetHandler : public Component
{
public:
typedef Game::XAssetHeader(*Callback)(Game::XAssetType, const char*);
typedef bool(*RestrictCallback)(Game::XAssetType type, Game::XAssetHeader asset, const char* name);
AssetHandler();
~AssetHandler();
const char* GetName() { return "AssetHandler"; };
static void OnFind(Game::XAssetType type, Callback callback);
static void OnLoad(RestrictCallback callback);
static void Relocate(void* start, void* to, DWORD size = 4);
private:
static bool BypassState;
static Game::XAssetHeader FindAsset(Game::XAssetType type, const char* filename);
static bool IsAssetEligible(Game::XAssetType type, Game::XAssetHeader* asset);
static void FindAssetStub();
static void AddAssetStub();
static void OffsetToAlias(FastFiles::Offset* offset);
static std::map<Game::XAssetType, Callback> TypeCallbacks;
static std::vector<RestrictCallback> RestrictCallbacks;
static std::map<void*, void*> Relocations;
};
}

View File

@ -0,0 +1,112 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Dvar::Var Colors::NewColors;
void Colors::Strip(const char* in, char* out, int max)
{
max--;
int current = 0;
while (*in != 0 && current < max)
{
if (!Q_IsColorString(in))
{
*out = *in;
out++;
current++;
}
else
{
*in++;
}
*in++;
}
*out = '\0';
}
void __declspec(naked) Colors::ClientUserinfoChanged(int length)
{
__asm
{
mov eax, [esp + 4h] // length
sub eax, 1
push eax
push ecx // name
push edx // buffer
call strncpy
add esp, 0Ch
retn
}
}
char* Colors::CL_GetClientName(int a1, int a2, char* buffer, size_t _length)
{
__asm
{
push _length
push buffer
push a2
push a1
mov eax, 4563D0h
call eax
add esp, 10h
}
// Remove the colors
char tempBuffer[100] = { 0 };
Colors::Strip(buffer, tempBuffer, _length);
strncpy(buffer, tempBuffer, _length);
return buffer;
}
void Colors::UpdateColorTable()
{
static int LastState = 2;
static DWORD DefaultTable[8] = { 0 };
DWORD* gColorTable = (DWORD*)0x78DC70;
if (LastState == 2)
{
memcpy(DefaultTable, gColorTable, sizeof(DefaultTable));
}
if (Colors::NewColors.Get<bool>() && (0xF & (int)Colors::NewColors.Get<bool>()) != LastState)
{
// Apply NTA's W<> colors :3 (slightly modified though^^)
gColorTable[1] = RGB(255, 49, 49);
gColorTable[2] = RGB(134, 192, 0);
gColorTable[3] = RGB(255, 173, 34);
gColorTable[4] = RGB(0, 135, 193);
gColorTable[5] = RGB(32, 197, 255);
gColorTable[6] = RGB(151, 80, 221);
LastState = Colors::NewColors.Get<bool>();
}
else if (!Colors::NewColors.Get<bool>() && (0xF & (int)Colors::NewColors.Get<bool>()) != LastState)
{
memcpy(gColorTable, DefaultTable, sizeof(DefaultTable));
LastState = Colors::NewColors.Get<bool>();
}
}
Colors::Colors()
{
// Allow colored names ingame
Utils::Hook(0x5D8B40, Colors::ClientUserinfoChanged, HOOK_JUMP).Install()->Quick();
// Though, don't apply that to overhead names.
Utils::Hook(0x581932, Colors::CL_GetClientName, HOOK_CALL).Install()->Quick();
// Set frame handler
Renderer::OnFrame(Colors::UpdateColorTable);
// Register dvar
Colors::NewColors = Dvar::Register<bool>("cg_newColors", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use Warfare<72> color code style.");
}
}

View File

@ -0,0 +1,20 @@
#define Q_IsColorString( p ) ( ( p ) && *( p ) == '^' && *( ( p ) + 1 ) && isdigit( *( ( p ) + 1 ) ) ) // ^[0-9]
namespace Components
{
class Colors : public Component
{
public:
Colors();
const char* GetName() { return "Colors"; };
static Dvar::Var NewColors;
static void ClientUserinfoChanged(int length);
static char* CL_GetClientName(int a1, int a2, char* buffer, size_t _length);
static void UpdateColorTable();
static void Strip(const char* in, char* out, int max);
};
}

View File

@ -0,0 +1,77 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
std::vector<Game::cmd_function_t*> Command::Functions;
std::map<std::string, Command::Callback> Command::FunctionMap;
char* Command::Params::operator[](size_t index)
{
if (index >= this->Length())
{
return "";
}
return Game::cmd_argv[this->CommandId][index];
}
size_t Command::Params::Length()
{
return Game::cmd_argc[this->CommandId];
}
Command::~Command()
{
for (auto command : Command::Functions)
{
delete command;
}
Command::Functions.clear();
}
void Command::Add(const char* name, Command::Callback callback)
{
Command::FunctionMap[Utils::StrToLower(name)] = callback;
Game::Cmd_AddCommand(name, Command::MainCallback, Command::Allocate(), 0);
}
void Command::Execute(std::string command, bool sync)
{
command.append("\n"); // Make sure it's terminated
if (sync)
{
Game::Cmd_ExecuteSingleCommand(0, 0, command.data());
}
else
{
Game::Cbuf_AddText(0, command.data());
}
}
Game::cmd_function_t* Command::Allocate()
{
Game::cmd_function_t* cmd = new Game::cmd_function_t;
Command::Functions.push_back(cmd);
return cmd;
}
void Command::MainCallback()
{
Command::Params params(*Game::cmd_id);
std::string command = Utils::StrToLower(params[0]);
if (Command::FunctionMap.find(command) != Command::FunctionMap.end())
{
Command::FunctionMap[command](params);
}
}
Command::Command()
{
// TODO: Add commands here?
}
}

View File

@ -0,0 +1,36 @@
namespace Components
{
class Command : public Component
{
public:
class Params
{
public:
Params(DWORD id) : CommandId(id) {};
Params(const Params &obj) { this->CommandId = obj.CommandId; };
Params() : Params(*Game::cmd_id) {};
char* operator[](size_t index);
size_t Length();
private:
DWORD CommandId;
};
typedef void(*Callback)(Command::Params params);
Command();
~Command();
const char* GetName() { return "Command"; };
static void Add(const char* name, Callback callback);
static void Execute(std::string command, bool sync = true);
private:
static Game::cmd_function_t* Allocate();
static std::vector<Game::cmd_function_t*> Functions;
static std::map<std::string, Callback> FunctionMap;
static void MainCallback();
};
}

View File

@ -0,0 +1,38 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
char** Console::GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType)
{
if (path == (char*)0xBAADF00D || IsBadReadPtr(path, 1)) return nullptr;
return Game::FS_ListFiles(path, extension, behavior, numfiles, allocTrackType);
}
void Console::ToggleConsole()
{
// possibly cls.keyCatchers?
Utils::Hook::Xor<DWORD>(0xB2C538, 1);
// g_consoleField
Game::Field_Clear((void*)0xA1B6B0);
// show console output?
Utils::Hook::Set<BYTE>(0xA15F38, 0);
}
Console::Console()
{
// External console
Utils::Hook::Nop(0x60BB58, 11);
// Console '%s: %s> ' string
Utils::Hook::Set<char*>(0x5A44B4, "IW4x > ");
// Internal console
Utils::Hook(0x4F690C, Console::ToggleConsole, HOOK_CALL).Install()->Quick();
Utils::Hook(0x4F65A5, Console::ToggleConsole, HOOK_JUMP).Install()->Quick();
// Check for bad food ;)
Utils::Hook(0x4CB9F4, Console::GetAutoCompleteFileList, HOOK_CALL).Install()->Quick();
}
}

View File

@ -0,0 +1,13 @@
namespace Components
{
class Console : public Component
{
public:
Console();
const char* GetName() { return "Console"; };
private:
static void ToggleConsole();
static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType);
};
}

View File

@ -0,0 +1,113 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Dvar::Var Dedicated::Dedi;
bool Dedicated::IsDedicated()
{
return (Dedicated::Dedi.Get<int>() != 0);
}
void Dedicated::InitDedicatedServer()
{
const char* fastfiles[7] =
{
"code_post_gfx_mp",
"localized_code_post_gfx_mp",
"ui_mp",
"localized_ui_mp",
"common_mp",
"localized_common_mp",
"patch_mp"
};
memcpy((void*)0x66E1CB0, &fastfiles, sizeof(fastfiles));
Game::LoadInitialFF();
Utils::Hook::Call<void>(0x4F84C0);
}
Dedicated::Dedicated()
{
Dedicated::Dedi = Dvar::Register<int>("dedicated", 0, 0, 2, Game::dvar_flag::DVAR_FLAG_SERVERINFO | Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED, "Start as dedicated");
// TODO: Beautify!
char* cmd = GetCommandLineA();
char* value = strstr(cmd, " dedicated");
if (value)
{
value += 10;
while (*value == ' ' || *value == '"')
value++;
char num[2] = { 0, 0 };
num[0] = *value;
int dediVal = atoi(num);
if (dediVal && dediVal < 3)
{
Dedicated::Dedi.SetRaw(dediVal);
}
}
if (Dedicated::IsDedicated())
{
Utils::Hook(0x60BE98, Dedicated::InitDedicatedServer, HOOK_CALL).Install()->Quick();
Utils::Hook::Set<BYTE>(0x683370, 0xC3); // steam sometimes doesn't like the server
Utils::Hook::Set<BYTE>(0x5B4FF0, 0xC3); // self-registration on party
Utils::Hook::Set<BYTE>(0x426130, 0xC3); // other party stuff?
Utils::Hook::Set<BYTE>(0x4D7030, 0xC3); // upnp stuff
Utils::Hook::Set<BYTE>(0x4B0FC3, 0x04); // make CL_Frame do client packets, even for game state 9
Utils::Hook::Set<BYTE>(0x4F5090, 0xC3); // init sound system (1)
Utils::Hook::Set<BYTE>(0x507B80, 0xC3); // start render thread
Utils::Hook::Set<BYTE>(0x4F84C0, 0xC3); // R_Init caller
Utils::Hook::Set<BYTE>(0x46A630, 0xC3); // init sound system (2)
Utils::Hook::Set<BYTE>(0x41FDE0, 0xC3); // Com_Frame audio processor?
Utils::Hook::Set<BYTE>(0x41B9F0, 0xC3); // called from Com_Frame, seems to do renderer stuff
Utils::Hook::Set<BYTE>(0x41D010, 0xC3); // CL_CheckForResend, which tries to connect to the local server constantly
Utils::Hook::Set<BYTE>(0x62B6C0, 0xC3); // UI expression 'DebugPrint', mainly to prevent some console spam
Utils::Hook::Set<BYTE>(0x468960, 0xC3); // some mixer-related function called on shutdown
Utils::Hook::Set<BYTE>(0x60AD90, 0); // masterServerName flags
Utils::Hook::Nop(0x4DCEC9, 2); // some check preventing proper game functioning
Utils::Hook::Nop(0x507C79, 6); // another similar bsp check
Utils::Hook::Nop(0x414E4D, 6); // unknown check in SV_ExecuteClientMessage (0x20F0890 == 0, related to client->f_40)
Utils::Hook::Nop(0x4DCEE9, 5); // some deinit renderer function
Utils::Hook::Nop(0x59A896, 5); // warning message on a removed subsystem
Utils::Hook::Nop(0x4B4EEF, 5); // same as above
Utils::Hook::Nop(0x64CF77, 5); // function detecting video card, causes Direct3DCreate9 to be called
Utils::Hook::Nop(0x60BC52, 0x15); // recommended settings check
// isHost script call return 0
Utils::Hook::Set<DWORD>(0x5DEC04, 0);
// map_rotate func
//*(DWORD*)0x4152E8 = (DWORD)SV_MapRotate_f;
// sv_network_fps max 1000, and uncheat
Utils::Hook::Set<BYTE>(0x4D3C67, 0); // ?
Utils::Hook::Set<DWORD>(0x4D3C69, 1000);
// r_loadForRenderer default to 0
Utils::Hook::Set<BYTE>(0x519DDF, 0);
// disable cheat protection on onlinegame
Utils::Hook::Set<BYTE>(0x404CF7, 0x80);
// some d3d9 call on error
Utils::Hook::Set<BYTE>(0x508470, 0xC3);
// stop saving a config_mp.cfg
Utils::Hook::Set<BYTE>(0x60B240, 0xC3);
}
}
}

View File

@ -0,0 +1,16 @@
namespace Components
{
class Dedicated : public Component
{
public:
Dedicated();
const char* GetName() { return "Dedicated"; };
static bool IsDedicated();
private:
static Dvar::Var Dedi;
static void InitDedicatedServer();
};
}

View File

@ -0,0 +1,149 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Dvar::Var::Var(std::string dvarName) : Var()
{
this->dvar = Game::Dvar_FindVar(dvarName.data());
if (!this->dvar)
{
// Quick-register the dvar
Game::SetConsole(dvarName.data(), "");
this->dvar = Game::Dvar_FindVar(dvarName.data());
}
}
template <> Game::dvar_t* Dvar::Var::Get()
{
return this->dvar;
}
template <> char* Dvar::Var::Get()
{
if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING && this->dvar->current.string)
{
return this->dvar->current.string;
}
return "";
}
template <> const char* Dvar::Var::Get()
{
return this->Get<char*>();
}
template <> int Dvar::Var::Get()
{
if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_INT)
{
return this->dvar->current.integer;
}
return 0;
}
template <> float Dvar::Var::Get()
{
if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT)
{
return this->dvar->current.value;
}
return 0;
}
template <> float* Dvar::Var::Get()
{
static float val[4] = { 0 };
if (this->dvar && (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4))
{
return this->dvar->current.vec4;
}
return val;
}
template <> bool Dvar::Var::Get()
{
if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_BOOL)
{
return this->dvar->current.boolean;
}
return false;
}
void Dvar::Var::Set(char* string)
{
this->Set(reinterpret_cast<const char*>(string));
}
void Dvar::Var::Set(const char* string)
{
if (this->dvar && this->dvar->name)
{
Game::Dvar_SetCommand(this->dvar->name, string);
}
}
void Dvar::Var::Set(std::string string)
{
this->Set(string.data());
}
void Dvar::Var::Set(int integer)
{
if (this->dvar && this->dvar->name)
{
Game::Dvar_SetCommand(this->dvar->name, Utils::VA("%i", integer));
}
}
void Dvar::Var::Set(float value)
{
if (this->dvar && this->dvar->name)
{
Game::Dvar_SetCommand(this->dvar->name, Utils::VA("%f", value));
}
}
void Dvar::Var::SetRaw(int integer)
{
if (this->dvar)
{
this->dvar->current.integer = integer;
}
}
template<> static Dvar::Var Dvar::Register(const char* name, bool value, Dvar::Flag flag, const char* description)
{
return Game::Dvar_RegisterBool(name, value, flag.val, description);
}
template<> static Dvar::Var Dvar::Register(const char* name, const char* value, Dvar::Flag flag, const char* description)
{
return Game::Dvar_RegisterString(name, value, flag.val, description);
}
template<> static Dvar::Var Dvar::Register(const char* name, int value, int min, int max, Dvar::Flag flag, const char* description)
{
return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description);
}
Game::dvar_t* Dvar::RegisterName(const char* name, const char* default, Game::dvar_flag flag, const char* description)
{
// TODO: Register string dvars here
return Dvar::Register<const char*>(name, "Unknown Soldier", Dvar::Flag(flag | Game::dvar_flag::DVAR_FLAG_SAVED).val, description).Get<Game::dvar_t*>();
}
Dvar::Dvar()
{
// set flags of cg_drawFPS to archive
Utils::Hook::Or<BYTE>(0x4F8F69, Game::dvar_flag::DVAR_FLAG_SAVED);
// un-cheat cg_fov and add archive flags
Utils::Hook::Xor<BYTE>(0x4F8E35, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED);
// set flags of cg_drawFPS to archive
Utils::Hook::Or<BYTE>(0x4F8F69, Game::dvar_flag::DVAR_FLAG_SAVED);
// set cg_fov max to 90.0
static float cgFov90 = 90.0f;
Utils::Hook::Set<float*>(0x4F8E28, &cgFov90);
// Hook dvar 'name' registration
Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).Install()->Quick();
}
}

View File

@ -0,0 +1,49 @@
namespace Components
{
class Dvar : public Component
{
public:
struct Flag
{
Flag(Game::dvar_flag flag) : val(flag){};
Flag(int flag) : Flag((Game::dvar_flag)flag) {};
Game::dvar_flag val;
};
class Var
{
public:
Var() : dvar(0) {};
Var(const Var &obj) { this->dvar = obj.dvar; };
Var(Game::dvar_t* _dvar) : dvar(_dvar) {};
Var(std::string dvarName);
Var(std::string dvarName, std::string value);
template<typename T> T Get();
void Set(char* string);
void Set(const char* string);
void Set(std::string string);
void Set(int integer);
void Set(float value);
// TODO: Add others
void SetRaw(int integer);
private:
Game::dvar_t* dvar;
};
Dvar();
const char* GetName() { return "Dvar"; };
// Only strings and bools use this type of declaration
template<typename T> static Var Register(const char* name, T value, Flag flag, const char* description);
template<typename T> static Var Register(const char* name, T value, T min, T max, Flag flag, const char* description);
private:
static Game::dvar_t* RegisterName(const char* name, const char* default, Game::dvar_flag flag, const char* description);
};
}

View File

@ -0,0 +1,107 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
std::vector<std::string> FastFiles::ZonePaths;
void FastFiles::LoadDLCUIZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync)
{
Game::XZoneInfo* data = new Game::XZoneInfo[zoneCount + 2];
memcpy(data, zoneInfo, sizeof(Game::XZoneInfo) * zoneCount);
data[zoneCount].name = "dlc1_ui_mp";
data[zoneCount].allocFlags = 2;
data[zoneCount].freeFlags = 0;
zoneCount++;
data[zoneCount].name = "dlc2_ui_mp";
data[zoneCount].allocFlags = 2;
data[zoneCount].freeFlags = 0;
zoneCount++;
Game::DB_LoadXAssets(data, zoneCount, sync);
delete[] data;
}
const char* FastFiles::GetZoneLocation(const char* file)
{
const char* dir = Dvar::Var("fs_basepath").Get<const char*>();
for (auto &path : FastFiles::ZonePaths)
{
std::string absoluteFile = Utils::VA("%s\\%s%s", dir, path.data(), file);
// No ".ff" appended, append it manually
if (!Utils::EndsWith(file, ".ff"))
{
absoluteFile.append(".ff");
}
// Check if FastFile exists
if (GetFileAttributes(absoluteFile.data()) != INVALID_FILE_ATTRIBUTES)
{
return Utils::VA("%s", path.data());
}
}
return Utils::VA("zone\\%s\\", Game::Win_GetLanguage());
}
void FastFiles::AddZonePath(std::string path)
{
FastFiles::ZonePaths.push_back(path);
}
std::string FastFiles::Current()
{
const char* file = (Utils::Hook::Get<char*>(0x112A680) + 4);
if ((int)file == 4)
{
return "";
}
return file;
}
FastFiles::FastFiles()
{
// Redirect zone paths
Utils::Hook(0x44DA90, FastFiles::GetZoneLocation, HOOK_JUMP).Install()->Quick();
// Allow dlc ui zone loading
Utils::Hook(0x506BC7, FastFiles::LoadDLCUIZones, HOOK_CALL).Install()->Quick();
Utils::Hook(0x60B4AC, FastFiles::LoadDLCUIZones, HOOK_CALL).Install()->Quick();
// basic checks (hash jumps, both normal and playlist)
Utils::Hook::Nop(0x5B97A3, 2);
Utils::Hook::Nop(0x5BA493, 2);
Utils::Hook::Nop(0x5B991C, 2);
Utils::Hook::Nop(0x5BA60C, 2);
Utils::Hook::Nop(0x5B97B4, 2);
Utils::Hook::Nop(0x5BA4A4, 2);
// allow loading of IWffu (unsigned) files
Utils::Hook::Set<BYTE>(0x4158D9, 0xEB); // main function
Utils::Hook::Nop(0x4A1D97, 2); // DB_AuthLoad_InflateInit
// some other, unknown, check
Utils::Hook::Set<BYTE>(0x5B9912, 0xB8);
Utils::Hook::Set<DWORD>(0x5B9913, 1);
Utils::Hook::Set<BYTE>(0x5BA602, 0xB8);
Utils::Hook::Set<DWORD>(0x5BA603, 1);
// Add custom zone paths
FastFiles::AddZonePath("zone\\patch\\");
FastFiles::AddZonePath("zone\\dlc\\");
}
FastFiles::~FastFiles()
{
FastFiles::ZonePaths.clear();
}
}

View File

@ -0,0 +1,48 @@
namespace Components
{
class FastFiles : public Component
{
public:
class Offset
{
public:
union
{
struct
{
uint32_t pointer : 28;
int stream : 4;
};
uint32_t fullValue;
void* fullPointer;
};
uint32_t GetDecrementedPointer()
{
Offset offset = *this;
offset.fullValue--;
return offset.pointer;
};
int GetDecrementedStream()
{
Offset offset = *this;
offset.fullValue--;
return offset.stream;
};
};
FastFiles();
~FastFiles();
const char* GetName() { return "FastFiles"; };
static void AddZonePath(std::string path);
static std::string Current();
private:
static std::vector<std::string> ZonePaths;
static const char* GetZoneLocation(const char* file);
static void LoadDLCUIZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync);
};
}

View File

@ -0,0 +1,30 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
void FileSystem::File::Read()
{
char* buffer = nullptr;
int size = Game::FS_ReadFile(this->FilePath.data(), &buffer);
this->Buffer.clear();
if (size < 0)
{
if (buffer)
{
Game::FS_FreeFile(buffer);
}
}
else
{
this->Buffer.append(buffer, size);
Game::FS_FreeFile(buffer);
}
}
FileSystem::FileSystem()
{
}
}

View File

@ -0,0 +1,27 @@
namespace Components
{
class FileSystem : public Component
{
public:
class File
{
public:
//File() {};
File(std::string file) : FilePath(file) { this->Read(); };
bool Exists() { return this->Buffer.size() > 0; };
std::string GetName() { return this->FilePath; };
std::string& GetBuffer() { return this->Buffer; };
private:
std::string FilePath;
std::string Buffer;
void Read();
};
FileSystem();
const char* GetName() { return "FileSystem"; };
};
}

View File

@ -0,0 +1,44 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Dvar::Var Localization::UseLocalization;
std::map<std::string, std::string> Localization::LocalizeMap;
void Localization::Set(const char* key, const char* value)
{
Localization::LocalizeMap[key] = value;
}
const char* Localization::Get(const char* key)
{
if (!Localization::UseLocalization.Get<bool>()) return key;
if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end())
{
return Localization::LocalizeMap[key].data();
}
Game::localizedEntry_s* entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize;
if (entry)
{
return entry->value;
}
return key;
}
Localization::Localization()
{
Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).Install()->Quick();
//Localization::Set("MENU_MULTIPLAYER_CAPS", "^5Fotze");
Localization::UseLocalization = Dvar::Register<bool>("ui_localize", true, Game::dvar_flag::DVAR_FLAG_NONE, "Use localization strings");
}
Localization::~Localization()
{
Localization::LocalizeMap.clear();
}
}

View File

@ -0,0 +1,17 @@
namespace Components
{
class Localization : public Component
{
public:
Localization();
~Localization();
const char* GetName() { return "Localization"; };
static void Set(const char* key, const char* value);
static const char* Get(const char* key);
private:
static std::map<std::string, std::string> LocalizeMap;
static Dvar::Var UseLocalization;
};
}

View File

@ -0,0 +1,57 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
bool Logger::IsConsoleReady()
{
return (IsWindow(*(HWND*)0x64A3288) != FALSE);
}
void Logger::Print(const char* message, ...)
{
char buffer[0x1000] = { 0 };
va_list ap;
va_start(ap, message);
vsprintf_s(buffer, message, ap);
va_end(ap);
if (Logger::IsConsoleReady())
{
Game::Com_Printf(0, "%s", buffer);
}
else
{
OutputDebugStringA(buffer);
}
}
void Logger::Error(const char* message, ...)
{
char buffer[0x1000] = { 0 };
va_list ap;
va_start(ap, message);
vsprintf_s(buffer, message, ap);
va_end(ap);
Game::Com_Error(0, "%s", buffer);
}
void Logger::SoftError(const char* message, ...)
{
char buffer[0x1000] = { 0 };
va_list ap;
va_start(ap, message);
vsprintf_s(buffer, message, ap);
va_end(ap);
Game::Com_Error(2, "%s", buffer);
}
Logger::Logger()
{
}
}

View File

@ -0,0 +1,14 @@
namespace Components
{
class Logger : public Component
{
public:
Logger();
const char* GetName() { return "Logger"; };
static void Print(const char* message, ...);
static void Error(const char* message, ...);
static void SoftError(const char* message, ...);
static bool IsConsoleReady();
};
}

View File

@ -0,0 +1,183 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
void* Maps::WorldMP = 0;
void* Maps::WorldSP = 0;
std::map<std::string, std::string> Maps::DependencyList;
std::vector<std::string> Maps::CurrentDependencies;
std::vector<Game::XAssetEntry> Maps::EntryPool;
void Maps::LoadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync)
{
Maps::CurrentDependencies.clear();
for (auto i = Maps::DependencyList.begin(); i != Maps::DependencyList.end(); i++)
{
if (std::regex_match(zoneInfo->name, std::regex(i->first)))
{
if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), i->second) == Maps::CurrentDependencies.end())
{
Maps::CurrentDependencies.push_back(i->second);
}
}
}
Game::XZoneInfo* data = new Game::XZoneInfo[zoneCount + Maps::CurrentDependencies.size()];
memcpy(data, zoneInfo, sizeof(Game::XZoneInfo) * zoneCount);
for (unsigned int i = 0; i < Maps::CurrentDependencies.size(); i++)
{
data[zoneCount + i].name = (&Maps::CurrentDependencies[i])->data();
data[zoneCount + i].allocFlags = data->allocFlags;
data[zoneCount + i].freeFlags = data->freeFlags;
}
Game::DB_LoadXAssets(data, zoneCount + Maps::CurrentDependencies.size(), sync);
delete[] data;
}
bool Maps::LoadAssetRestrict(Game::XAssetType type, Game::XAssetHeader asset, const char* name)
{
if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), FastFiles::Current()) != Maps::CurrentDependencies.end())
{
if (type == Game::XAssetType::ASSET_TYPE_GAME_MAP_MP || type == Game::XAssetType::ASSET_TYPE_COL_MAP_MP || type == Game::XAssetType::ASSET_TYPE_GFX_MAP || type == Game::XAssetType::ASSET_TYPE_MAP_ENTS || type == Game::XAssetType::ASSET_TYPE_COM_MAP || type == Game::XAssetType::ASSET_TYPE_FX_MAP)
{
return false;
}
}
if (type == Game::XAssetType::ASSET_TYPE_MAP_ENTS)
{
static std::string mapEntities;
FileSystem::File ents(Utils::VA("%s.ents", name));
if (ents.Exists())
{
mapEntities = ents.GetBuffer();
asset.mapEnts->entitystring = mapEntities.data();
}
}
return true;
}
void Maps::GetBSPName(char* buffer, size_t size, const char* format, const char* mapname)
{
if (_strnicmp("mp_", mapname, 3))
{
format = "maps/%s.d3dbsp";
// Adjust pointer to GameMap_Data
Utils::Hook::Set<void*>(0x4D90B7, Maps::WorldSP);
}
else
{
// Adjust pointer to GameMap_Data
Utils::Hook::Set<void*>(0x4D90B7, Maps::WorldMP);
}
_snprintf(buffer, size, format, mapname);
}
void Maps::AddDependency(std::string expression, std::string zone)
{
// Test expression before adding it
try
{
std::regex _(expression);
}
catch (std::exception e)
{
MessageBoxA(0, Utils::VA("Invalid regular expression: %s", expression.data()), "Warning", MB_ICONEXCLAMATION);
return;
}
Maps::DependencyList[expression] = zone;
}
void Maps::ReallocateEntryPool()
{
static_assert(sizeof(Game::XAssetEntry) == 16, "XAssetEntry size mismatch");
Maps::EntryPool.clear();
Maps::EntryPool.resize(789312);
// Apply new size
Utils::Hook::Set<DWORD>(0x5BAEB0, Maps::EntryPool.size());
// Apply new pool
Utils::Hook::Set<Game::XAssetEntry*>(0x48E6F4, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x4C67E4, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x4C8584, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BAEA8, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB0C4, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB0F5, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB1D4, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB235, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB278, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB34C, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB484, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB570, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB6B7, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB844, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BB98D, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBA66, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBB8D, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBCB1, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBD9B, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBE4C, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBF14, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBF54, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BBFB8, Maps::EntryPool.data());
Utils::Hook::Set<Game::XAssetEntry*>(0x5BAE91, Maps::EntryPool.data() + 1);
Utils::Hook::Set<Game::XAssetEntry*>(0x5BAEA2, Maps::EntryPool.data() + 1);
}
Maps::Maps()
{
// Restrict asset loading
AssetHandler::OnLoad(Maps::LoadAssetRestrict);
// hunk size (was 300 MiB)
Utils::Hook::Set<DWORD>(0x64A029, 0x1C200000); // 450 MiB
Utils::Hook::Set<DWORD>(0x64A057, 0x1C200000);
// Intercept BSP name resolving
Utils::Hook(0x4C5979, Maps::GetBSPName, HOOK_CALL).Install()->Quick();
// Intercept map zone loading
Utils::Hook(0x42C2AF, Maps::LoadMapZones, HOOK_CALL).Install()->Quick();
Maps::WorldSP = reinterpret_cast<char*>(Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_GAME_MAP_SP, 1)) + 52; // Skip name and other padding to reach world data
Maps::WorldMP = Utils::Hook::Get<char*>(0x4D90B7);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, 7168);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOCALIZE, 14000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIM, 8192);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PHYSPRESET, 128);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PIXELSHADER, 10000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, 3072);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MATERIAL, 8192);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXDECL, 196);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_WEAPON, 2400);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_STRINGTABLE, 800);
Maps::ReallocateEntryPool();
// Dependencies
Maps::AddDependency("oilrig", "mp_subbase");
Maps::AddDependency("gulag", "mp_subbase");
Maps::AddDependency("^(?!mp_).*", "mp_subbase"); // All maps not starting with "mp_"
}
Maps::~Maps()
{
Maps::EntryPool.clear();
}
}

View File

@ -0,0 +1,27 @@
namespace Components
{
class Maps : public Component
{
public:
Maps();
~Maps();
const char* GetName() { return "Maps"; };
static void AddDependency(std::string expression, std::string zone);
private:
static void* WorldMP;
static void* WorldSP;
static std::vector<Game::XAssetEntry> EntryPool;
static std::map<std::string, std::string> DependencyList;
static std::vector<std::string> CurrentDependencies;
static void GetBSPName(char* buffer, size_t size, const char* format, const char* mapname);
static bool LoadAssetRestrict(Game::XAssetType type, Game::XAssetHeader asset, const char* name);
static void LoadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync);
void ReallocateEntryPool();
};
}

View File

@ -0,0 +1,33 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Utils::Hook Materials::ImageVersionCheckHook;
void __declspec(naked) Materials::ImageVersionCheck()
{
__asm
{
cmp eax, 9
je returnSafely
jmp Materials::ImageVersionCheckHook.Original
returnSafely:
mov al, 1
add esp, 18h
retn
}
}
Materials::Materials()
{
// Allow codo images
Materials::ImageVersionCheckHook.Initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->Install();
}
Materials::~Materials()
{
Materials::ImageVersionCheckHook.Uninstall();
}
}

View File

@ -0,0 +1,14 @@
namespace Components
{
class Materials : public Component
{
public:
Materials();
~Materials();
const char* GetName() { return "Materials"; };
private:
static Utils::Hook ImageVersionCheckHook;
static void ImageVersionCheck();
};
}

View File

@ -0,0 +1,535 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
std::vector<Game::menuDef_t*> Menus::MenuList;
std::vector<Game::MenuList*> Menus::MenuListList;
int Menus::ReserveSourceHandle()
{
// Check if a free slot is available
int i = 1;
for (; i < MAX_SOURCEFILES; i++)
{
if (!Game::sourceFiles[i])
break;
}
if (i >= MAX_SOURCEFILES)
return 0;
// Reserve it, if yes
Game::sourceFiles[i] = (Game::source_t*)1;
return i;
}
Game::script_t* Menus::LoadMenuScript(std::string name, std::string buffer)
{
Game::script_t* script = Game::Script_Alloc(sizeof(Game::script_t) + 1 + buffer.length());
strcpy_s(script->filename, sizeof(script->filename), name.data());
script->buffer = (char*)(script + 1);
*((char*)(script + 1) + buffer.length()) = '\0';
script->script_p = script->buffer;
script->lastscript_p = script->buffer;
script->length = buffer.length();
script->end_p = &script->buffer[buffer.length()];
script->line = 1;
script->lastline = 1;
script->tokenavailable = 0;
Game::Script_SetupTokens(script, (void*)0x797F80);
script->punctuations = (Game::punctuation_t*)0x797F80;
strcpy(script->buffer, buffer.data());
script->length = Game::Script_CleanString(script->buffer);
return script;
}
int Menus::LoadMenuSource(std::string name, std::string buffer)
{
int handle = Menus::ReserveSourceHandle();
if (!Menus::IsValidSourceHandle(handle)) return 0; // No free source slot!
Game::source_t *source = nullptr;
Game::script_t *script = Menus::LoadMenuScript(name, buffer);
if (!script)
{
Game::sourceFiles[handle] = nullptr; // Free reserved slot
return 0;
}
script->next = NULL;
source = (Game::source_t *)calloc(1, sizeof(Game::source_t));
strncpy(source->filename, "string", 64);
source->scriptstack = script;
source->tokens = NULL;
source->defines = NULL;
source->indentstack = NULL;
source->skip = 0;
source->definehash = (Game::define_t**)calloc(1, 4096);
Game::sourceFiles[handle] = source;
return handle;
}
bool Menus::IsValidSourceHandle(int handle)
{
return (handle > 0 && handle < MAX_SOURCEFILES && Game::sourceFiles[handle]);
}
int Menus::KeywordHash(char* key)
{
// patch this function on-the-fly, as it's some ugly C.
Utils::Hook::Set<DWORD>(0x63FE9E, 3523);
Utils::Hook::Set<DWORD>(0x63FECB, 0x7F);
int var = 0x63FE90;
__asm
{
mov eax, key
call var
mov var, eax
}
Utils::Hook::Set<DWORD>(0x63FE9E, 531);
Utils::Hook::Set<DWORD>(0x63FECB, 0x1FF);
return var;
}
Game::menuDef_t* Menus::ParseMenu(int handle)
{
Game::menuDef_t* menu = (Game::menuDef_t*)calloc(1, sizeof(Game::menuDef_t));
menu->items = (Game::itemDef_t**)calloc(512, sizeof(Game::itemDef_t*));
Menus::MenuList.push_back(menu);
Game::pc_token_t token;
Game::keywordHash_t *key;
if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] != '{')
{
return menu;
}
while (true)
{
ZeroMemory(&token, sizeof(token));
if (!Game::PC_ReadTokenHandle(handle, &token))
{
Game::PC_SourceError(handle, "end of file inside menu\n");
break; // Fail
}
if (*token.string == '}')
{
break; // Success
}
int idx = Menus::KeywordHash(token.string);
key = Game::menuParseKeywordHash[idx];
if (!key)
{
Game::PC_SourceError(handle, "unknown menu keyword %s", token.string);
continue;
}
if (!key->func((Game::itemDef_t*)menu, handle))
{
Game::PC_SourceError(handle, "couldn't parse menu keyword %s", token.string);
break; // Fail
}
}
return menu;
}
std::vector<Game::menuDef_t*> Menus::LoadMenu(std::string menu)
{
std::vector<Game::menuDef_t*> menus;
FileSystem::File menuFile(menu);
if (menuFile.Exists())
{
Game::pc_token_t token;
int handle = Menus::LoadMenuSource(menu, menuFile.GetBuffer());
if (Menus::IsValidSourceHandle(handle))
{
while (true)
{
ZeroMemory(&token, sizeof(token));
if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] == '}')
{
break;
}
if (!_stricmp(token.string, "loadmenu"))
{
Game::PC_ReadTokenHandle(handle, &token);
std::vector<Game::menuDef_t*> newMenus = Menus::LoadMenu(Utils::VA("ui_mp\\%s.menu", token.string));
for (auto newMenu : newMenus)
{
menus.push_back(newMenu);
}
}
if (!_stricmp(token.string, "menudef"))
{
menus.push_back(Menus::ParseMenu(handle));
}
}
Menus::FreeMenuSource(handle);
}
}
return menus;
}
std::vector<Game::menuDef_t*> Menus::LoadMenu(Game::menuDef_t* menudef)
{
std::vector<Game::menuDef_t*> menus = Menus::LoadMenu(Utils::VA("ui_mp\\%s.menu", menudef->window.name));
if (!menus.size())
{
menus.push_back(menudef);
}
return menus;
}
Game::MenuList* Menus::LoadScriptMenu(const char* menu)
{
std::vector<Game::menuDef_t*> menus = Menus::LoadMenu(menu);
if (!menus.size()) return nullptr;
// Allocate new menu list
Game::MenuList* newList = (Game::MenuList*)calloc(1, sizeof(Game::MenuList));
newList->name = _strdup(menu);
newList->menus = (Game::menuDef_t **)calloc(menus.size(), sizeof(Game::menuDef_t *));
newList->menuCount = menus.size();
// Copy new menus
memcpy(newList->menus, menus.data(), menus.size() * sizeof(Game::menuDef_t *));
Menus::MenuListList.push_back(newList);
return newList;
}
Game::MenuList* Menus::LoadMenuList(Game::MenuList* menuList)
{
std::vector<Game::menuDef_t*> menus;
for (int i = 0; i < menuList->menuCount; i++)
{
if (!menuList->menus[i])
{
continue;
}
std::vector<Game::menuDef_t*> newMenus = Menus::LoadMenu(menuList->menus[i]);
for (auto newMenu : newMenus)
{
menus.push_back(newMenu);
}
}
// Allocate new menu list
Game::MenuList* newList = (Game::MenuList*)calloc(1, sizeof(Game::MenuList));
newList->name = _strdup(menuList->name);
newList->menus = (Game::menuDef_t **)calloc(menus.size(), sizeof(Game::menuDef_t *));
newList->menuCount = menus.size();
// Copy new menus
memcpy(newList->menus, menus.data(), menus.size() * sizeof(Game::menuDef_t *));
Menus::MenuListList.push_back(newList);
return newList;
}
void Menus::FreeMenuSource(int handle)
{
if (!Menus::IsValidSourceHandle(handle)) return;
Game::script_t *script;
Game::token_t *token;
Game::define_t *define;
Game::indent_t *indent;
Game::source_t *source = Game::sourceFiles[handle];
while (source->scriptstack)
{
script = source->scriptstack;
source->scriptstack = source->scriptstack->next;
Game::FreeMemory(script);
}
while (source->tokens)
{
token = source->tokens;
source->tokens = source->tokens->next;
Game::FreeMemory(token);
}
while (source->defines)
{
define = source->defines;
source->defines = source->defines->next;
Game::FreeMemory(define);
}
while (source->indentstack)
{
indent = source->indentstack;
source->indentstack = source->indentstack->next;
free(indent);
}
if (source->definehash) free(source->definehash);
free(source);
Game::sourceFiles[handle] = nullptr;
}
void Menus::FreeMenu(Game::menuDef_t* menudef)
{
// Do i need to free expressions and strings?
// Or does the game take care of it?
// Seems like it does...
if (menudef->items)
{
// Seems like this is obsolete as well,
// as the game handles the memory
//for (int i = 0; i < menudef->itemCount; i++)
//{
// Game::Menu_FreeItemMemory(menudef->items[i]);
//}
free(menudef->items);
}
free(menudef);
}
void Menus::FreeMenuList(Game::MenuList* menuList)
{
if (menuList)
{
if (menuList->name)
{
free((void*)menuList->name);
}
if (menuList->menus)
{
free(menuList->menus);
}
free(menuList);
}
}
void Menus::RemoveMenu(Game::menuDef_t* menudef)
{
for (auto i = Menus::MenuList.begin(); i != Menus::MenuList.end(); i++)
{
if ((*i) == menudef)
{
Menus::FreeMenu(menudef);
Menus::MenuList.erase(i);
break;
}
}
}
void Menus::RemoveMenuList(Game::MenuList* menuList)
{
if (!menuList) return;
for (auto i = Menus::MenuListList.begin(); i != Menus::MenuListList.end(); i++)
{
if ((*i)->name == menuList->name)
{
for (auto j = 0; j < menuList->menuCount; j++)
{
Menus::RemoveMenu(menuList->menus[j]);
}
Menus::FreeMenuList(menuList);
Menus::MenuListList.erase(i);
break;
}
}
}
void Menus::FreeEverything()
{
for (auto menu : Menus::MenuList)
{
Menus::FreeMenu(menu);
}
Menus::MenuList.clear();
for (auto menuList : Menus::MenuListList)
{
Menus::FreeMenuList(menuList);
}
Menus::MenuListList.clear();
}
Game::XAssetHeader Menus::MenuFileLoad(Game::XAssetType type, const char* filename)
{
Game::XAssetHeader header = { 0 };
// Check if we already loaded it
for (auto menuList : Menus::MenuListList)
{
if (!_stricmp(menuList->name, filename))
{
// Free it, seems like the game deallocated it
Menus::RemoveMenuList(menuList);
break;
}
}
Game::MenuList* menuList = Game::DB_FindXAssetHeader(type, filename).menuList;
header.menuList = menuList;
if (menuList)
{
// Parse scriptmenus!
if (!strcmp(menuList->menus[0]->window.name, "default_menu") || Utils::EndsWith(filename, ".menu"))
{
if (FileSystem::File(filename).Exists())
{
header.menuList = Menus::LoadScriptMenu(filename);
// Reset, if we didn't find scriptmenus
if (!header.menuList)
{
header.menuList = menuList;
}
}
}
else
{
header.menuList = Menus::LoadMenuList(menuList);
}
}
return header;
}
void Menus::AddMenuListHook(Game::UiContext *dc, Game::MenuList *menuList, int close)
{
Game::MenuList* menus = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENUFILE, "ui_mp/menus.txt").menuList;
Game::UI_AddMenuList(dc, menus, close);
Game::UI_AddMenuList(dc, menuList, close);
}
Game::menuDef_t* Menus::FindMenuByName(Game::UiContext* dc, const char* name)
{
for (int i = 0; i < dc->menuCount; i++)
{
Game::menuDef_t* menu = dc->Menus[i];
if (menu && menu->window.name && !IsBadReadPtr(menu->window.name, 1)) // Sanity checks
{
if (!_strnicmp(name, menu->window.name, 0x7FFFFFFF))
{
return menu;
}
}
else
{
// TODO: Remove menu from stack and free if custom menu
}
}
return nullptr;
}
Menus::Menus()
{
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENUFILE, Menus::MenuFileLoad);
//Utils::Hook(0x63FE80, Menus::MenuFileLoad, HOOK_JUMP).Install()->Quick();
// Custom Menus_FindByName
Utils::Hook(0x487240, Menus::FindMenuByName, HOOK_JUMP).Install()->Quick();
// Load menus ingame
Utils::Hook(0x41C178, Menus::AddMenuListHook, HOOK_CALL).Install()->Quick();
// disable the 2 new tokens in ItemParse_rect
Utils::Hook::Set<BYTE>(0x640693, 0xEB);
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5);
//make Com_Error and similar go back to main_text instead of menu_xboxlive.
strcpy((char*)0x6FC790, "main_text");
Command::Add("openmenu", [] (Command::Params params)
{
if (params.Length() != 2)
{
Logger::Print("USAGE: openmenu <menu name>\n");
return;
}
Game::Menus_OpenByName(Game::uiContext, params[1]);
});
Command::Add("reloadmenus", [] (Command::Params params)
{
// Close all menus
Game::Menus_CloseAll(Game::uiContext);
// Free custom menus
Menus::FreeEverything();
// Only disconnect if in-game, context is updated automatically!
if (Game::CL_IsCgameInitialized())
{
Game::Cbuf_AddText(0, "disconnect\n");
}
else
{
// Reinitialize ui context
((void(*)())0x401700)();
// Reopen main menu
Game::Menus_OpenByName(Game::uiContext, "main_text");
}
});
}
Menus::~Menus()
{
Menus::FreeEverything();
}
}

View File

@ -0,0 +1,47 @@
#define MAX_SOURCEFILES 64
namespace Components
{
class Menus : public Component
{
public:
Menus();
~Menus();
const char* GetName() { return "Menus"; };
static void FreeEverything();
private:
static std::vector<Game::menuDef_t*> MenuList;
static std::vector<Game::MenuList*> MenuListList;
static Game::XAssetHeader MenuFileLoad(Game::XAssetType type, const char* filename);
static Game::MenuList* LoadMenuList(Game::MenuList* menuList);
static Game::MenuList* LoadScriptMenu(const char* menu);
static std::vector<Game::menuDef_t*> LoadMenu(Game::menuDef_t* menudef);
static std::vector<Game::menuDef_t*> LoadMenu(std::string file);
static Game::script_t* LoadMenuScript(std::string name, std::string buffer);
static int LoadMenuSource(std::string name, std::string buffer);
static int ReserveSourceHandle();
static bool IsValidSourceHandle(int handle);
static Game::menuDef_t* ParseMenu(int handle);
static void FreeMenuSource(int handle);
static void FreeMenuList(Game::MenuList* menuList);
static void FreeMenu(Game::menuDef_t* menudef);
static void RemoveMenu(Game::menuDef_t* menudef);
static void RemoveMenuList(Game::MenuList* menuList);
static void AddMenuListHook(Game::UiContext *dc, Game::MenuList *menuList, int close);
static Game::menuDef_t* FindMenuByName(Game::UiContext* dc, const char* name);
// Ugly!
static int KeywordHash(char* key);
};
}

View File

@ -0,0 +1,45 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
std::map<std::string, const char*> MusicalTalent::SoundAliasList;
void MusicalTalent::Replace(std::string sound, const char* file)
{
MusicalTalent::SoundAliasList[Utils::StrToLower(sound)] = file;
}
Game::XAssetHeader MusicalTalent::ManipulateAliases(Game::XAssetType type, const char* filename)
{
Game::XAssetHeader header = { 0 };
if (MusicalTalent::SoundAliasList.find(Utils::StrToLower(filename)) != MusicalTalent::SoundAliasList.end())
{
Game::snd_alias_list_t* aliases = Game::DB_FindXAssetHeader(type, filename).aliasList;
if (aliases)
{
if (aliases->aliases->stream->type == 2)
{
aliases->aliases->stream->file = MusicalTalent::SoundAliasList[Utils::StrToLower(filename)];
}
header.aliasList = aliases;
}
}
return header;
}
MusicalTalent::MusicalTalent()
{
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_SOUND, MusicalTalent::ManipulateAliases);
MusicalTalent::Replace("music_mainmenu_mp", "hz_boneyard_intro_LR_1.mp3");
}
MusicalTalent::~MusicalTalent()
{
MusicalTalent::SoundAliasList.clear();
}
}

View File

@ -0,0 +1,16 @@
namespace Components
{
class MusicalTalent : public Component
{
public:
MusicalTalent();
~MusicalTalent();
const char* GetName() { return "MusicalTalent"; };
static void Replace(std::string sound, const char* file);
private:
static std::map<std::string, const char*> SoundAliasList;
static Game::XAssetHeader ManipulateAliases(Game::XAssetType type, const char* filename);
};
}

View File

@ -0,0 +1,133 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
std::string Network::SelectedPacket;
std::map<std::string, Network::Callback> Network::PacketHandlers;
Network::Address::Address(std::string addrString)
{
Game::NET_StringToAdr(addrString.data(), &this->address);
}
bool Network::Address::operator==(const Network::Address &obj)
{
return Game::NET_CompareAdr(this->address, obj.address);
}
void Network::Address::SetPort(unsigned short port)
{
this->address.port = port;
};
unsigned short Network::Address::GetPort()
{
return this->address.port;
}
void Network::Address::SetIP(DWORD ip)
{
*(DWORD*)this->address.ip = ip;
}
DWORD Network::Address::GetIP()
{
return *(DWORD*)this->address.ip;
}
Game::netadr_t* Network::Address::Get()
{
return &this->address;
}
const char* Network::Address::GetString()
{
return Game::NET_AdrToString(this->address);
}
void Network::Handle(std::string packet, Network::Callback callback)
{
Network::PacketHandlers[Utils::StrToLower(packet)] = callback;
}
void Network::Send(Game::netsrc_t type, Address target, std::string data)
{
Game::OOBPrintT(type, *target.Get(), data.data());
}
void Network::Send(Address target, std::string data)
{
Network::Send(Game::netsrc_t::NS_CLIENT, target, data);
}
int Network::PacketInterceptionHandler(const char* packet)
{
// Check if custom handler exists
for (auto i = Network::PacketHandlers.begin(); i != Network::PacketHandlers.end(); i++)
{
if (!_strnicmp(i->first.data(), packet, i->first.size()))
{
Network::SelectedPacket = i->first;
return 0;
}
}
// No interception
return 1;
}
void Network::DeployPacket(Game::netadr_t from, Game::msg_t* msg)
{
if (Network::PacketHandlers.find(Network::SelectedPacket) != Network::PacketHandlers.end())
{
size_t offset = Network::SelectedPacket.size() + 4 + 1;
Network::PacketHandlers[Network::SelectedPacket](from, std::string(msg->data + offset, msg->cursize - offset));
}
else
{
Logger::Print("Error: Network packet intercepted, but handler is missing!\n");
}
}
void __declspec(naked) Network::DeployPacketStub()
{
__asm
{
push ebp //C54
// esp = C54h?
mov eax, [esp + 0C54h + 14h]
push eax
mov eax, [esp + 0C58h + 10h]
push eax
mov eax, [esp + 0C5Ch + 0Ch]
push eax
mov eax, [esp + 0C60h + 08h]
push eax
mov eax, [esp + 0C64h + 04h]
push eax
call Network::DeployPacket
add esp, 14h
add esp, 4h
mov al, 1
//C50
pop edi //C4C
pop esi //C48
pop ebp //C44
pop ebx //C40
add esp, 0C40h
retn
}
}
Network::Network()
{
// maximum size in NET_OutOfBandPrint
Utils::Hook::Set<DWORD>(0x4AEF08, 0x1FFFC);
Utils::Hook::Set<DWORD>(0x4AEFA3, 0x1FFFC);
// Install interception handler
Utils::Hook(0x5AA709, Network::PacketInterceptionHandler, HOOK_CALL).Install()->Quick();
// Install packet deploy hook
Utils::Hook::Set<int>(0x5AA715, (DWORD)Network::DeployPacketStub - 0x5AA713 - 6);
}
Network::~Network()
{
Network::SelectedPacket.clear();
Network::PacketHandlers.clear();
}
}

View File

@ -0,0 +1,48 @@
namespace Components
{
class Network : public Component
{
public:
class Address
{
public:
Address() {};
Address(std::string addrString);
Address(Game::netadr_t addr) : address(addr) {}
Address(Game::netadr_t* addr) : Address(*addr) {}
Address(const Address& obj) { this->address = obj.address; };
bool operator!=(const Address &obj) { return !(*this == obj); };
bool operator==(const Address &obj);
void SetPort(unsigned short port);
unsigned short GetPort();
void SetIP(DWORD ip);
DWORD GetIP();
Game::netadr_t* Get();
const char* GetString();
private:
Game::netadr_t address;
};
typedef void(*Callback)(Address address, std::string data);
Network();
~Network();
const char* GetName() { return "Network"; };
static void Handle(std::string packet, Callback callback);
static void Send(Address target, std::string data);
static void Send(Game::netsrc_t type, Address target, std::string data);
private:
static std::string SelectedPacket;
static std::map<std::string, Callback> PacketHandlers;
static int PacketInterceptionHandler(const char* packet);
static void DeployPacket(Game::netadr_t from, Game::msg_t* msg);
static void DeployPacketStub();
};
}

View File

@ -0,0 +1,253 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Party::JoinContainer Party::Container;
std::map<uint64_t, Network::Address> Party::LobbyMap;
SteamID Party::GenerateLobbyId()
{
SteamID id;
id.AccountID = Game::Com_Milliseconds();
id.Universe = 1;
id.AccountType = 8;
id.AccountInstance = 0x40000;
return id;
}
void Party::Connect(Network::Address target)
{
Party::Container.Valid = true;
Party::Container.JoinTime = Game::Com_Milliseconds();
Party::Container.Target = target;
Party::Container.Challenge = Utils::VA("%X", Party::Container.JoinTime);
Network::Send(Party::Container.Target, Utils::VA("getinfo %s\n", Party::Container.Challenge.data()));
Command::Execute("openmenu popup_reconnectingtoparty");
}
const char* Party::GetLobbyInfo(SteamID lobby, std::string key)
{
if (Party::LobbyMap.find(lobby.Bits) != Party::LobbyMap.end())
{
Network::Address address = Party::LobbyMap[lobby.Bits];
if (key == "addr")
{
return Utils::VA("%d", address.Get()->ip[0] | (address.Get()->ip[1] << 8) | (address.Get()->ip[2] << 16) | (address.Get()->ip[3] << 24));
}
else if (key =="port")
{
return Utils::VA("%d", htons(address.GetPort()));
}
}
return "212";
}
void Party::RemoveLobby(SteamID lobby)
{
if (Party::LobbyMap.find(lobby.Bits) != Party::LobbyMap.end())
{
Party::LobbyMap.erase(Party::LobbyMap.find(lobby.Bits));
}
}
void Party::ConnectError(std::string message)
{
Command::Execute("closemenu popup_reconnectingtoparty");
Dvar::Var("partyend_reason").Set(message);
Command::Execute("openmenu menu_xboxlive_partyended");
}
Game::dvar_t* Party::RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description)
{
return Dvar::Register<int>(name, 1, 1, max, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED | flag, description).Get<Game::dvar_t*>();
}
Party::Party()
{
// various changes to SV_DirectConnect-y stuff to allow non-party joinees
Utils::Hook::Set<WORD>(0x460D96, 0x90E9);
Utils::Hook::Set<BYTE>(0x460F0A, 0xEB);
Utils::Hook::Set<BYTE>(0x401CA4, 0xEB);
Utils::Hook::Set<BYTE>(0x401C15, 0xEB);
// disable configstring checksum matching (it's unreliable at most)
Utils::Hook::Set<BYTE>(0x4A75A7, 0xEB); // SV_SpawnServer
Utils::Hook::Set<BYTE>(0x5AC2CF, 0xEB); // CL_ParseGamestate
Utils::Hook::Set<BYTE>(0x5AC2C3, 0xEB); // CL_ParseGamestate
// AnonymousAddRequest
Utils::Hook::Set<BYTE>(0x5B5E18, 0xEB);
Utils::Hook::Set<BYTE>(0x5B5E64, 0xEB);
Utils::Hook::Nop(0x5B5E5C, 2);
// HandleClientHandshake
Utils::Hook::Set<BYTE>(0x5B6EA5, 0xEB);
Utils::Hook::Set<BYTE>(0x5B6EF3, 0xEB);
Utils::Hook::Nop(0x5B6EEB, 2);
// Allow local connections
Utils::Hook::Set<BYTE>(0x4D43DA, 0xEB);
// LobbyID mismatch
Utils::Hook::Nop(0x4E50D6, 2);
Utils::Hook::Set<BYTE>(0x4E50DA, 0xEB);
// causes 'does current Steam lobby match' calls in Steam_JoinLobby to be ignored
Utils::Hook::Set<BYTE>(0x49D007, 0xEB);
// functions checking party heartbeat timeouts, cause random issues
Utils::Hook::Nop(0x4E532D, 5);
// Steam_JoinLobby call causes migration
Utils::Hook::Nop(0x5AF851, 5);
Utils::Hook::Set<BYTE>(0x5AF85B, 0xEB);
// Allow xpartygo in public lobbies
Utils::Hook::Set<BYTE>(0x5A969E, 0xEB);
// Patch party_minplayers to 1 and protect it
Utils::Hook(0x4D5D51, Party::RegisterMinPlayers, HOOK_CALL).Install()->Quick();
Command::Add("connect", [] (Command::Params params)
{
if (params.Length() < 2)
{
return;
}
Party::Connect(Network::Address(params[1]));
});
Renderer::OnFrame([] ()
{
if (!Party::Container.Valid) return;
if ((Game::Com_Milliseconds() - Party::Container.JoinTime) > 5000)
{
Party::Container.Valid = false;
Party::ConnectError("Server connection timed out.");
}
});
// Basic info handler
Network::Handle("getInfo", [] (Network::Address address, std::string data)
{
int clientCount = 0;
for (int i = 0; i < *Game::svs_numclients; i++)
{
if (Game::svs_clients[i].state >= 3)
{
clientCount++;
}
}
Utils::InfoString info;
info.Set("challenge", data.substr(0, data.find_first_of("\n")).data());
info.Set("gamename", "IW4");
info.Set("hostname", Dvar::Var("sv_hostname").Get<const char*>());
info.Set("mapname", Dvar::Var("mapname").Get<const char*>());
info.Set("gametype", Dvar::Var("g_gametype").Get<const char*>());
info.Set("fs_game", Dvar::Var("fs_game").Get<const char*>());
info.Set("xuid", Utils::VA("%llX", Steam::SteamUser()->GetSteamID().Bits));
info.Set("clients", Utils::VA("%i", clientCount));
info.Set("sv_maxclients", Utils::VA("%i", *Game::svs_numclients));
// Set matchtype
// 0 - No match, connecting not possible
// 1 - Party, use Steam_JoinLobby to connect
// 2 - Match, use CL_ConnectFromParty to connect
if (Dvar::Var("party_host").Get<bool>()) // Party hosting
{
info.Set("matchtype", "1");
}
else if (Dvar::Var("sv_running").Get<bool>()) // Match hosting
{
info.Set("matchtype", "2");
}
else
{
info.Set("matchtype", "0");
}
Network::Send(address, Utils::VA("infoResponse\n%s\n", info.Build().data()));
});
Network::Handle("infoResponse", [] (Network::Address address, std::string data)
{
Utils::InfoString info(data);
// Handle connection
if (Party::Container.Valid)
{
if (Party::Container.Target == address)
{
// Invalidate handler for future packets
Party::Container.Valid = false;
int matchType = atoi(info.Get("matchtype").data());
if (info.Get("challenge") != Party::Container.Challenge)
{
Party::ConnectError("Invalid join response: Challenge mismatch.");
}
else if (!matchType)
{
Party::ConnectError("Server is not hosting a match.");
}
// Connect
else if (matchType == 1) // Party
{
SteamID id = Party::GenerateLobbyId();
Party::LobbyMap[id.Bits] = address;
Game::Steam_JoinLobby(id, 0);
// Callback not registered on first try
// TODO: Fix :D
if (Party::LobbyMap.size() <= 1) Game::Steam_JoinLobby(id, 0);
}
else if (matchType == 2) // Match
{
if (atoi(info.Get("clients").data()) >= atoi(info.Get("sv_maxclients").data()))
{
Party::ConnectError("@EXE_SERVERISFULL");
}
if (info.Get("mapname") == "" || info.Get("gametype") == "")
{
Party::ConnectError("Invalid map or gametype.");
}
else
{
Dvar::Var("xblive_privatematch").Set(1);
Game::Menus_CloseAll(Game::uiContext);
char xnaddr[32];
Game::CL_ConnectFromParty(0, xnaddr, *address.Get(), 0, 0, info.Get("mapname").data(), info.Get("gametype").data());
}
}
else
{
Party::ConnectError("Invalid join response: Unknown matchtype");
}
}
}
ServerList::Insert(address, info);
});
}
Party::~Party()
{
Party::LobbyMap.clear();
}
}

View File

@ -0,0 +1,32 @@
namespace Components
{
class Party : public Component
{
public:
Party();
~Party();
const char* GetName() { return "Party"; };
static void Connect(Network::Address target);
static const char* GetLobbyInfo(SteamID lobby, std::string key);
static void RemoveLobby(SteamID lobby);
private:
struct JoinContainer
{
Network::Address Target;
std::string Challenge;
DWORD JoinTime;
bool Valid;
};
static JoinContainer Container;
static std::map<uint64_t, Network::Address> LobbyMap;
static SteamID GenerateLobbyId();
static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description);
static void ConnectError(std::string message);
};
}

View File

@ -0,0 +1,90 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
__int64* QuickPatch::GetStatsID()
{
static __int64 id = 0x110000100001337;
return &id;
}
QuickPatch::QuickPatch()
{
// remove system pre-init stuff (improper quit, disk full)
Utils::Hook::Set<BYTE>(0x411350, 0xC3);
// remove STEAMSTART checking for DRM IPC
Utils::Hook::Nop(0x451145, 5);
Utils::Hook::Set<BYTE>(0x45114C, 0xEB);
// Apply new playlist
char* playlist = "mp_playlists_dlc2";
Utils::Hook::Set<char*>(0x494803, playlist);
Utils::Hook::Set<char*>(0x4C6EC1, playlist);
Utils::Hook::Set<char*>(0x4CF7F9, playlist);
Utils::Hook::Set<char*>(0x4D6E63, playlist);
Utils::Hook::Set<char*>(0x4D7358, playlist);
Utils::Hook::Set<char*>(0x4D73C8, playlist);
Utils::Hook::Set<char*>(0x4F4EA1, playlist);
Utils::Hook::Set<char*>(0x4D47FB, "mp_playlists_dlc2.ff");
Utils::Hook::Set<char*>(0x60B06E, "playlists.patch2");
// disable playlist download function
Utils::Hook::Set<BYTE>(0x4D4790, 0xC3);
// disable playlist.ff loading function
//Utils::Hook::Set<BYTE>(0x4D6E60, 0xC3);
// Load playlist, but don't delete it
Utils::Hook::Nop(0x4D6EBB, 5);
Utils::Hook::Nop(0x4D6E67, 5);
Utils::Hook::Nop(0x4D6E71, 2);
// playlist dvar 'validity check'
Utils::Hook::Set<BYTE>(0x4B1170, 0xC3);
//Got playlists is true
//Utils::Hook::Set<bool>(0x1AD3680, true);
// LSP disabled
Utils::Hook::Set<BYTE>(0x435950, 0xC3); // LSP HELLO
Utils::Hook::Set<BYTE>(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP!
Utils::Hook::Set<BYTE>(0x4BD900, 0xC3); // main LSP response func
Utils::Hook::Set<BYTE>(0x682170, 0xC3); // Telling LSP that we're playing a private match
// Don't delete config files if corrupted
Utils::Hook::Set<BYTE>(0x47DCB3, 0xEB);
// hopefully allow alt-tab during game, used at least in alt-enter handling
Utils::Hook::Set<DWORD>(0x45ACE0, 0xC301B0);
// fs_basegame
Utils::Hook::Set<char*>(0x6431D1, "data");
// remove limit on IWD file loading
Utils::Hook::Set<BYTE>(0x642BF3, 0xEB);
// Disable UPNP
Utils::Hook::Nop(0x60BE24, 5);
// disable the IWNet IP detection (default 'got ipdetect' flag to 1)
Utils::Hook::Set<BYTE>(0x649D6F0, 1);
// Fix stats sleeping
Utils::Hook::Set<BYTE>(0x6832BA, 0xEB);
Utils::Hook::Set<BYTE>(0x4BD190, 0xC3);
// default sv_pure to 0
Utils::Hook::Set<BYTE>(0x4D3A74, 0);
// Force debug logging
Utils::Hook::Nop(0x4AA89F, 2);
Utils::Hook::Nop(0x4AA8A1, 6);
// Patch stats steamid
Utils::Hook::Nop(0x682EBF, 20);
Utils::Hook::Nop(0x6830B1, 20);
Utils::Hook(0x682EBF, QuickPatch::GetStatsID, HOOK_CALL).Install()->Quick();
Utils::Hook(0x6830B1, QuickPatch::GetStatsID, HOOK_CALL).Install()->Quick();
}
}

View File

@ -0,0 +1,12 @@
namespace Components
{
class QuickPatch : public Component
{
public:
QuickPatch();
const char* GetName() { return "QuickPatch"; };
private:
static _int64* GetStatsID();
};
}

View File

@ -0,0 +1,24 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
void* RawFiles::LoadModdableRawfileFunc(const char* filename)
{
return Game::LoadModdableRawfile(0, filename);
}
RawFiles::RawFiles()
{
Utils::Hook(0x632155, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x5FA46C, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x5FA4D6, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x6321EF, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x630A88, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x59A6F8, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x57F1E6, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
Utils::Hook(0x57ED36, RawFiles::LoadModdableRawfileFunc, HOOK_CALL).Install()->Quick();
// remove fs_game check for moddable rawfiles - allows non-fs_game to modify rawfiles
Utils::Hook::Nop(0x61AB76, 2);
}
}

View File

@ -0,0 +1,11 @@
namespace Components
{
class RawFiles : public Component
{
public:
RawFiles();
const char* GetName() { return "RawFiles"; };
static void* RawFiles::LoadModdableRawfileFunc(const char* filename);
};
}

View File

@ -0,0 +1,41 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Utils::Hook Renderer::DrawFrameHook;
std::vector<Renderer::Callback> Renderer::FrameCallbacks;
void __declspec(naked) Renderer::FrameHook()
{
__asm
{
call Renderer::FrameHandler
jmp Renderer::DrawFrameHook.Original
}
}
void Renderer::FrameHandler()
{
for (auto callback : Renderer::FrameCallbacks)
{
callback();
}
}
void Renderer::OnFrame(Renderer::Callback callback)
{
Renderer::FrameCallbacks.push_back(callback);
}
Renderer::Renderer()
{
// Frame hook
Renderer::DrawFrameHook.Initialize(0x5ACB99, Renderer::FrameHook, HOOK_CALL)->Install();
}
Renderer::~Renderer()
{
Renderer::DrawFrameHook.Uninstall();
Renderer::FrameCallbacks.clear();
}
}

View File

@ -0,0 +1,21 @@
namespace Components
{
class Renderer : public Component
{
public:
typedef void(*Callback)();
Renderer();
~Renderer();
const char* GetName() { return "Renderer"; };
static void OnFrame(Callback callback);
private:
static void FrameHook();
static void FrameHandler();
static std::vector<Callback> FrameCallbacks;
static Utils::Hook DrawFrameHook;
};
}

View File

@ -0,0 +1,272 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
unsigned int ServerList::CurrentServer = 0;
ServerList::Container ServerList::RefreshContainer;
std::vector<ServerList::ServerInfo> ServerList::OnlineList;
int ServerList::GetServerCount()
{
return ServerList::OnlineList.size();
}
const char* ServerList::GetServerText(int index, int column)
{
if ((unsigned int)index >= ServerList::OnlineList.size()) return "";
ServerList::ServerInfo* Server = &ServerList::OnlineList[index];
switch (column)
{
case Column::Password:
{
return (Server->Password ? "X" : "");
}
case Column::Hostname:
{
return Server->Hostname.data();
}
case Column::Mapname:
{
return Game::UI_LocalizeMapName(Server->Mapname.data());
}
case Column::Players:
{
return Utils::VA("%i (%i)", Server->Clients, Server->MaxClients);
}
case Column::Gametype:
{
if (Server->Mod != "")
{
return (Server->Mod.data() + 5);
}
return Game::UI_LocalizeGameType(Server->Gametype.data());
}
case Column::Ping:
{
return Utils::VA("%i", Server->Ping);
}
}
return "";
}
void ServerList::SelectServer(int index)
{
ServerList::CurrentServer = (unsigned int)index;
}
void ServerList::Refresh()
{
ServerList::OnlineList.clear();
ServerList::RefreshContainer.Mutex.lock();
ServerList::RefreshContainer.Servers.clear();
ServerList::RefreshContainer.Mutex.unlock();
ServerList::RefreshContainer.SendCount = 0;
ServerList::RefreshContainer.SentCount = 0;
ServerList::RefreshContainer.AwatingList = true;
ServerList::RefreshContainer.AwaitTime = Game::Com_Milliseconds();
int masterPort = Dvar::Var("masterPort").Get<int>();
const char* masterServerName = Dvar::Var("masterServerName").Get<const char*>();
ServerList::RefreshContainer.Host = Network::Address(Utils::VA("%s:%u", masterServerName, masterPort));
Logger::Print("Sending serverlist request to master: %s:%u\n", masterServerName, masterPort);
Network::Send(ServerList::RefreshContainer.Host, "getservers IW4 145 full empty");
//Network::Send(ServerList::RefreshContainer.Host, "getservers 0 full empty\n");
}
void ServerList::Insert(Network::Address address, Utils::InfoString info)
{
ServerList::RefreshContainer.Mutex.lock();
for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end(); i++)
{
// Our desired server
if (i->Target == address && i->Sent)
{
// Challenge did not match
if (i->Challenge != info.Get("challenge"))
{
// Shall we remove the server from the queue?
// Better not, it might send a second response with the correct challenge.
// This might happen when users refresh twice (or more often) in a short period of time
break;
}
// TODO: Implement deeper check like version and game
ServerInfo server;
server.Hostname = info.Get("hostname");
server.Mapname = info.Get("mapname");
server.Gametype = info.Get("gametype");
server.Mod = info.Get("fs_game");
server.MatchType = atoi(info.Get("matchtype").data());
server.Clients = atoi(info.Get("clients").data());
server.MaxClients = atoi(info.Get("sv_maxclients").data());
server.Password = 0; // No info yet
server.Ping = (Game::Com_Milliseconds() - i->SendTime);
server.Addr = address;
// Check if already inserted and remove
for (auto j = ServerList::OnlineList.begin(); j != ServerList::OnlineList.end(); j++)
{
if (j->Addr == address)
{
ServerList::OnlineList.erase(j);
break;
}
}
ServerList::OnlineList.push_back(server);
ServerList::RefreshContainer.Servers.erase(i);
break;
}
}
ServerList::RefreshContainer.Mutex.unlock();
}
void ServerList::Frame()
{
ServerList::RefreshContainer.Mutex.lock();
if (ServerList::RefreshContainer.AwatingList)
{
// Check if we haven't got a response within 10 seconds
if (Game::Com_Milliseconds() - ServerList::RefreshContainer.AwaitTime > 5000)
{
ServerList::RefreshContainer.AwatingList = false;
Logger::Print("We haven't received a response from the master within %d seconds!\n", (Game::Com_Milliseconds() - ServerList::RefreshContainer.AwaitTime) / 1000);
}
}
// Send requests to 10 servers each frame
int SendServers = 10;
for (unsigned int i = 0; i < ServerList::RefreshContainer.Servers.size(); i++)
{
ServerList::Container::ServerContainer* server = &ServerList::RefreshContainer.Servers[i];
if (server->Sent) continue;
// Found server we can send a request to
server->Sent = true;
SendServers--;
server->SendTime = Game::Com_Milliseconds();
server->Challenge = Utils::VA("%d", server->SendTime);
ServerList::RefreshContainer.SentCount++;
Network::Send(server->Target, Utils::VA("getinfo %s\n", server->Challenge.data()));
// Display in the menu, like in COD4
//Logger::Print("Sent %d/%d\n", ServerList::RefreshContainer.SentCount, ServerList::RefreshContainer.SendCount);
if (SendServers <= 0) break;
}
ServerList::RefreshContainer.Mutex.unlock();
}
ServerList::ServerList()
{
ServerList::OnlineList.clear();
Network::Handle("getServersResponse", [] (Network::Address address, std::string data)
{
if (ServerList::RefreshContainer.Host != address) return; // Only parse from host we sent to
ServerList::RefreshContainer.AwatingList = false;
ServerList::RefreshContainer.Mutex.lock();
int offset = 0;
int count = ServerList::RefreshContainer.Servers.size();
ServerList::MasterEntry* entry = nullptr;
// Find first entry
do
{
entry = (ServerList::MasterEntry*)(data.data() + offset++);
}
while (!entry->HasSeparator() && !entry->IsEndToken());
for (int i = 0; !entry[i].IsEndToken() && entry[i].HasSeparator(); i++)
{
Network::Address serverAddr = address;
serverAddr.SetIP(entry[i].IP);
serverAddr.SetPort(entry[i].Port);
serverAddr.Get()->type = Game::NA_IP;
ServerList::Container::ServerContainer container;
container.Sent = false;
container.Target = serverAddr;
bool alreadyInserted = false;
for (auto &server : ServerList::RefreshContainer.Servers)
{
if (server.Target == container.Target)
{
alreadyInserted = true;
break;
}
}
if (!alreadyInserted)
{
ServerList::RefreshContainer.Servers.push_back(container);
ServerList::RefreshContainer.SendCount++;
}
}
Logger::Print("Parsed %d servers from master\n", ServerList::RefreshContainer.Servers.size() - count);
ServerList::RefreshContainer.Mutex.unlock();
});
// Set default masterServerName + port and save it
Utils::Hook::Set<const char*>(0x60AD92, "localhost");
Utils::Hook::Set<BYTE>(0x60AD90, Game::dvar_flag::DVAR_FLAG_SAVED); // masterServerName
Utils::Hook::Set<BYTE>(0x60ADC6, Game::dvar_flag::DVAR_FLAG_SAVED); // masterPort
// Add server list feeder
UIFeeder::Add(2.0f, ServerList::GetServerCount, ServerList::GetServerText, ServerList::SelectServer);
// Add required UIScripts
UIScript::Add("RefreshServers", ServerList::Refresh);
UIScript::Add("JoinServer", [] ()
{
if (ServerList::OnlineList.size() > ServerList::CurrentServer)
{
Party::Connect(ServerList::OnlineList[ServerList::CurrentServer].Addr);
}
});
UIScript::Add("ServerSort", [] (UIScript::Token token)
{
Logger::Print("Server list sorting by token: %d\n", token.Get<int>());
});
// Add frame callback
Renderer::OnFrame(ServerList::Frame);
}
ServerList::~ServerList()
{
ServerList::OnlineList.clear();
}
}

View File

@ -0,0 +1,94 @@
namespace Components
{
class ServerList : public Component
{
public:
struct ServerInfo
{
Network::Address Addr;
bool Visible;
std::string Hostname;
std::string Mapname;
std::string Gametype;
std::string Mod;
int Clients;
int MaxClients;
bool Password;
int Ping;
int MatchType;
bool Hardcore;
};
ServerList();
~ServerList();
const char* GetName() { return "ServerList"; };
static void Refresh();
static void Insert(Network::Address address, Utils::InfoString info);
private:
enum Column
{
Password,
Hostname,
Mapname,
Players,
Gametype,
Ping,
};
#pragma pack(push, 1)
union MasterEntry
{
char Token[7];
struct
{
uint32_t IP;
uint16_t Port;
};
bool IsEndToken()
{
// End of transmission or file token
return (Token[0] == 'E' && Token[1] == 'O' && (Token[2] == 'T' || Token[2] == 'F'));
}
bool HasSeparator()
{
return (Token[6] == '\\');
}
};
#pragma pack(pop)
struct Container
{
struct ServerContainer
{
bool Sent;
int SendTime;
std::string Challenge;
Network::Address Target;
};
bool AwatingList;
int AwaitTime;
int SentCount;
int SendCount;
Network::Address Host;
std::vector<ServerContainer> Servers;
std::mutex Mutex;
};
static int GetServerCount();
static const char* GetServerText(int index, int column);
static void SelectServer(int index);
static void Frame();
static unsigned int CurrentServer;
static Container RefreshContainer;
static std::vector<ServerInfo> OnlineList;
};
}

View File

@ -0,0 +1,23 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
bool Singleton::FirstInstance = true;
bool Singleton::IsFirstInstance()
{
return Singleton::FirstInstance;
}
Singleton::Singleton()
{
if (Dedicated::IsDedicated()) return;
Singleton::FirstInstance = (CreateMutex(NULL, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS);
if (!Singleton::FirstInstance && MessageBoxA(0, "Do you want to start a second instance?", "Game already running", MB_ICONEXCLAMATION | MB_YESNO) == IDNO)
{
ExitProcess(0);
}
}
}

View File

@ -0,0 +1,14 @@
namespace Components
{
class Singleton : public Component
{
public:
Singleton();
const char* GetName() { return "Singleton"; };
static bool IsFirstInstance();
private:
static bool FirstInstance;
};
}

View File

@ -0,0 +1,285 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
UIFeeder::Container UIFeeder::Current;
std::map<float, UIFeeder::Callbacks> UIFeeder::Feeders;
void UIFeeder::Add(float feeder, UIFeeder::GetItemCount_t itemCountCb, UIFeeder::GetItemText_t itemTextCb, UIFeeder::Select_t selectCb)
{
UIFeeder::Feeders[feeder] = { itemCountCb, itemTextCb, selectCb };
}
const char* UIFeeder::GetItemText()
{
if (UIFeeder::Feeders.find(UIFeeder::Current.Feeder) != UIFeeder::Feeders.end())
{
return UIFeeder::Feeders[UIFeeder::Current.Feeder].GetItemText(UIFeeder::Current.Index, UIFeeder::Current.Column);
}
return nullptr;
}
int UIFeeder::GetItemCount()
{
if (UIFeeder::Feeders.find(UIFeeder::Current.Feeder) != UIFeeder::Feeders.end())
{
return UIFeeder::Feeders[UIFeeder::Current.Feeder].GetItemCount();
}
return 0;
}
bool UIFeeder::SetItemSelection()
{
if (UIFeeder::Feeders.find(UIFeeder::Current.Feeder) != UIFeeder::Feeders.end())
{
UIFeeder::Feeders[UIFeeder::Current.Feeder].Select(UIFeeder::Current.Index);
return true;
}
return false;
}
bool UIFeeder::CheckFeeder()
{
if (UIFeeder::Current.Feeder == 15.0f) return false;
return (UIFeeder::Feeders.find(UIFeeder::Current.Feeder) != UIFeeder::Feeders.end());
}
void __declspec(naked) UIFeeder::SetItemSelectionStub()
{
__asm
{
mov eax, [esp + 08h]
mov UIFeeder::Current.Feeder, eax
mov eax, [esp + 0Ch]
mov UIFeeder::Current.Index, eax
call UIFeeder::SetItemSelection
test al, al
jz continue
retn
continue:
fld ds : 739FD0h
mov eax, 4C25D6h
jmp eax
}
}
void __declspec(naked) UIFeeder::GetItemTextStub()
{
__asm
{
mov eax, [esp + 0Ch]
mov UIFeeder::Current.Feeder, eax
mov eax, [esp + 10h]
mov UIFeeder::Current.Index, eax
mov eax, [esp + 14h]
mov UIFeeder::Current.Column, eax
call UIFeeder::GetItemText
test eax, eax
jz continue
push ebx
mov ebx, [esp + 4 + 28h]
mov dword ptr[ebx], 0
pop ebx
retn
continue:
push ecx
fld ds:739FD0h
mov eax, 4CE9E7h
jmp eax
}
}
void __declspec(naked) UIFeeder::GetItemCountStub()
{
__asm
{
mov eax, [esp + 8h]
mov UIFeeder::Current.Feeder, eax
call UIFeeder::GetItemCount
test eax, eax
jz continue
retn
continue:
push ecx
fld ds:739FD0h
mov eax, 41A0D7h
jmp eax;
}
}
void __declspec(naked) UIFeeder::HandleKeyStub()
{
static int NextClickTime = 0;
__asm
{
mov ebx, ebp
mov eax, [ebp + 12Ch]
mov UIFeeder::Current.Feeder, eax
push ebx
call UIFeeder::CheckFeeder
pop ebx
test al, al
jz continueOriginal
// Get current milliseconds
call Game::Com_Milliseconds
// Check if allowed to click
cmp eax, NextClickTime
jl continueOriginal
// Set next allowed click time to current time + 300ms
add eax, 300
mov NextClickTime, eax
// Get item cursor position ptr
mov ecx, ebx
add ecx, Game::itemDef_t::cursorPos
// Get item listbox ptr
mov edx, ebx
add edx, Game::itemDef_t::typeData
// Get listbox cursor pos
mov edx, [edx]
add edx, Game::listBoxDef_s::startPos
mov edx, [edx]
// Resolve item cursor pos pointer
mov ebx, [ecx]
// Check if item cursor pos equals listbox cursor pos
cmp ebx, edx
je returnSafe
// Update indices if not
mov [ecx], edx
mov UIFeeder::Current.Index, edx
call UIFeeder::SetItemSelection
returnSafe:
retn
continueOriginal:
mov eax, 635570h
jmp eax
}
}
void __declspec(naked) UIFeeder::MouseEnterStub()
{
__asm
{
mov eax, [edi + 12Ch]
mov UIFeeder::Current.Feeder, eax
call UIFeeder::CheckFeeder
test al, al
jnz continue
mov[edi + 130h], esi
continue:
mov eax, 639D75h
jmp eax
}
}
void __declspec(naked) UIFeeder::MouseSelectStub()
{
__asm
{
mov eax, [esp + 08h]
mov UIFeeder::Current.Feeder, eax
call UIFeeder::CheckFeeder
test al, al
jnz continue
mov eax, 4C25D0h
jmp eax
continue:
retn
}
}
void __declspec(naked) UIFeeder::PlaySoundStub()
{
__asm
{
mov eax, [edi + 12Ch]
mov UIFeeder::Current.Feeder, eax
call UIFeeder::CheckFeeder
test al, al
jnz continue
mov eax, 685E10h
jmp eax
continue:
retn
}
}
UIFeeder::UIFeeder()
{
// Get feeder item count
Utils::Hook(0x41A0D0, UIFeeder::GetItemCountStub, HOOK_JUMP).Install()->Quick();
// Get feeder item text
Utils::Hook(0x4CE9E0, UIFeeder::GetItemTextStub, HOOK_JUMP).Install()->Quick();
// Select feeder item
Utils::Hook(0x4C25D0, UIFeeder::SetItemSelectionStub, HOOK_JUMP).Install()->Quick();
// Mouse enter check
Utils::Hook(0x639D6E, UIFeeder::MouseEnterStub, HOOK_JUMP).Install()->Quick();
// Handle key event
Utils::Hook(0x63C5BC, UIFeeder::HandleKeyStub, HOOK_CALL).Install()->Quick();
// Mouse select check
Utils::Hook(0x639D31, UIFeeder::MouseSelectStub, HOOK_CALL).Install()->Quick();
// Play mouse over sound check
Utils::Hook(0x639D66, UIFeeder::PlaySoundStub, HOOK_CALL).Install()->Quick();
// some thing overwriting feeder 2's data
Utils::Hook::Set<BYTE>(0x4A06A9, 0xEB);
}
UIFeeder::~UIFeeder()
{
UIFeeder::Feeders.clear();
}
}

View File

@ -0,0 +1,51 @@
namespace Components
{
class UIFeeder : public Component
{
public:
typedef int(__cdecl * GetItemCount_t)();
typedef const char* (__cdecl * GetItemText_t)(int index, int column);
typedef void(__cdecl * Select_t)(int index);
struct Callbacks
{
GetItemCount_t GetItemCount;
GetItemText_t GetItemText;
Select_t Select;
};
UIFeeder();
~UIFeeder();
const char* GetName() { return "UIFeeder"; };
static void Add(float feeder, GetItemCount_t itemCountCb, GetItemText_t itemTextCb, Select_t selectCb);
private:
struct Container
{
float Feeder;
int Index;
int Column;
};
static Container Current;
static void GetItemCountStub();
static int GetItemCount();
static void GetItemTextStub();
static const char* GetItemText();
static void SetItemSelectionStub();
static bool SetItemSelection();
static bool CheckFeeder();
static void MouseEnterStub();
static void MouseSelectStub();
static void HandleKeyStub();
static void PlaySoundStub();
static std::map<float, Callbacks> Feeders;
};
}

View File

@ -0,0 +1,110 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
std::map<std::string, UIScript::Callback> UIScript::UIScripts;
template<> int UIScript::Token::Get()
{
if (this->IsValid())
{
return atoi(this->token);
}
return 0;
}
template<> char* UIScript::Token::Get()
{
if (this->IsValid())
{
return this->token;
}
return "";
}
template<> const char* UIScript::Token::Get()
{
return this->Get<char*>();
}
template<> std::string UIScript::Token::Get()
{
return this->Get<const char*>();
}
bool UIScript::Token::IsValid()
{
return (token && token[0]);
}
void UIScript::Token::Parse(const char** args)
{
if (args)
{
this->token = Game::Com_ParseExt(args);
}
}
void UIScript::Add(std::string name, UIScript::Callback callback)
{
UIScript::UIScripts[name] = callback;
}
void UIScript::Add(std::string name, UIScript::CallbackRaw callback)
{
UIScript::Add(name, reinterpret_cast<UIScript::Callback>(callback));
}
bool UIScript::RunMenuScript(const char* name, const char** args)
{
if (UIScript::UIScripts.find(name) != UIScript::UIScripts.end())
{
UIScript::UIScripts[name](UIScript::Token(args));
return true;
}
return false;
}
void __declspec(naked) UIScript::RunMenuScriptStub()
{
__asm
{
mov eax, esp
add eax, 8h
mov edx, eax // UIScript name
mov eax, [esp + 0C10h] // UIScript args
push eax
push edx
call UIScript::RunMenuScript
add esp, 8h
test al, al
jz continue
// if returned
pop edi
pop esi
add esp, 0C00h
retn
continue:
mov eax, 45ED00h
jmp eax
}
}
UIScript::UIScript()
{
// Install handler
Utils::Hook::Set<int>(0x45EC5B, (DWORD)UIScript::RunMenuScriptStub - 0x45EC59 - 6);
}
UIScript::~UIScript()
{
UIScript::UIScripts.clear();
}
}

View File

@ -0,0 +1,39 @@
namespace Components
{
class UIScript : public Component
{
public:
UIScript();
~UIScript();
const char* GetName() { return "UIScript"; };
class Token
{
public:
Token(const char** args) : token(0) { this->Parse(args); };
Token(const Token &obj) { this->token = obj.token; };
template<typename T> T Get();
bool IsValid();
private:
char* token;
void Parse(const char** args);
};
typedef void(*Callback)(Token token);
typedef void(*CallbackRaw)();
static void Add(std::string name, Callback callback);
static void Add(std::string name, CallbackRaw callback);
private:
static bool RunMenuScript(const char* name, const char** args);
static void RunMenuScriptStub();
static std::map<std::string, Callback> UIScripts;
};
}

View File

@ -0,0 +1,27 @@
#include "..\..\STDInclude.hpp"
namespace Components
{
Dvar::Var Window::NoBorder;
void __declspec(naked) Window::StyleHookStub()
{
if (Window::NoBorder.Get<bool>())
{
__asm mov ebp, WS_VISIBLE | WS_POPUP
}
else
{
__asm mov ebp, WS_VISIBLE | WS_SYSMENU | WS_CAPTION
}
__asm retn
}
Window::Window()
{
// Borderless window
Window::NoBorder = Dvar::Register<bool>("r_noborder", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Do not use a border in windowed mode");
Utils::Hook(0x507643, Window::StyleHookStub, HOOK_CALL).Install()->Quick();
}
}

View File

@ -0,0 +1,12 @@
namespace Components
{
class Window : public Component
{
public:
Window();
const char* GetName() { return "Window"; };
static Dvar::Var NoBorder;
static void Window::StyleHookStub();
};
}