Use premake.
This commit is contained in:
55
src/Components/Loader.cpp
Normal file
55
src/Components/Loader.cpp
Normal 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
46
src/Components/Loader.hpp
Normal 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"
|
158
src/Components/Modules/AssetHandler.cpp
Normal file
158
src/Components/Modules/AssetHandler.cpp
Normal 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();
|
||||
}
|
||||
}
|
33
src/Components/Modules/AssetHandler.hpp
Normal file
33
src/Components/Modules/AssetHandler.hpp
Normal 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;
|
||||
};
|
||||
}
|
112
src/Components/Modules/Colors.cpp
Normal file
112
src/Components/Modules/Colors.cpp
Normal 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.");
|
||||
}
|
||||
}
|
20
src/Components/Modules/Colors.hpp
Normal file
20
src/Components/Modules/Colors.hpp
Normal 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);
|
||||
};
|
||||
}
|
77
src/Components/Modules/Command.cpp
Normal file
77
src/Components/Modules/Command.cpp
Normal 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?
|
||||
}
|
||||
}
|
36
src/Components/Modules/Command.hpp
Normal file
36
src/Components/Modules/Command.hpp
Normal 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();
|
||||
};
|
||||
}
|
38
src/Components/Modules/Console.cpp
Normal file
38
src/Components/Modules/Console.cpp
Normal 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();
|
||||
}
|
||||
}
|
13
src/Components/Modules/Console.hpp
Normal file
13
src/Components/Modules/Console.hpp
Normal 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);
|
||||
};
|
||||
}
|
113
src/Components/Modules/Dedicated.cpp
Normal file
113
src/Components/Modules/Dedicated.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
16
src/Components/Modules/Dedicated.hpp
Normal file
16
src/Components/Modules/Dedicated.hpp
Normal 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();
|
||||
};
|
||||
}
|
149
src/Components/Modules/Dvar.cpp
Normal file
149
src/Components/Modules/Dvar.cpp
Normal 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();
|
||||
}
|
||||
}
|
49
src/Components/Modules/Dvar.hpp
Normal file
49
src/Components/Modules/Dvar.hpp
Normal 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);
|
||||
};
|
||||
}
|
107
src/Components/Modules/FastFiles.cpp
Normal file
107
src/Components/Modules/FastFiles.cpp
Normal 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();
|
||||
}
|
||||
}
|
48
src/Components/Modules/FastFiles.hpp
Normal file
48
src/Components/Modules/FastFiles.hpp
Normal 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);
|
||||
};
|
||||
}
|
30
src/Components/Modules/FileSystem.cpp
Normal file
30
src/Components/Modules/FileSystem.cpp
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
27
src/Components/Modules/FileSystem.hpp
Normal file
27
src/Components/Modules/FileSystem.hpp
Normal 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"; };
|
||||
};
|
||||
}
|
44
src/Components/Modules/Localization.cpp
Normal file
44
src/Components/Modules/Localization.cpp
Normal 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();
|
||||
}
|
||||
}
|
17
src/Components/Modules/Localization.hpp
Normal file
17
src/Components/Modules/Localization.hpp
Normal 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;
|
||||
};
|
||||
}
|
57
src/Components/Modules/Logger.cpp
Normal file
57
src/Components/Modules/Logger.cpp
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
14
src/Components/Modules/Logger.hpp
Normal file
14
src/Components/Modules/Logger.hpp
Normal 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();
|
||||
};
|
||||
}
|
183
src/Components/Modules/Maps.cpp
Normal file
183
src/Components/Modules/Maps.cpp
Normal 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();
|
||||
}
|
||||
}
|
27
src/Components/Modules/Maps.hpp
Normal file
27
src/Components/Modules/Maps.hpp
Normal 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();
|
||||
};
|
||||
}
|
33
src/Components/Modules/Materials.cpp
Normal file
33
src/Components/Modules/Materials.cpp
Normal 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();
|
||||
}
|
||||
}
|
14
src/Components/Modules/Materials.hpp
Normal file
14
src/Components/Modules/Materials.hpp
Normal 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();
|
||||
};
|
||||
}
|
535
src/Components/Modules/Menus.cpp
Normal file
535
src/Components/Modules/Menus.cpp
Normal 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();
|
||||
}
|
||||
}
|
47
src/Components/Modules/Menus.hpp
Normal file
47
src/Components/Modules/Menus.hpp
Normal 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);
|
||||
};
|
||||
}
|
45
src/Components/Modules/MusicalTalent.cpp
Normal file
45
src/Components/Modules/MusicalTalent.cpp
Normal 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();
|
||||
}
|
||||
}
|
16
src/Components/Modules/MusicalTalent.hpp
Normal file
16
src/Components/Modules/MusicalTalent.hpp
Normal 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);
|
||||
};
|
||||
}
|
133
src/Components/Modules/Network.cpp
Normal file
133
src/Components/Modules/Network.cpp
Normal 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();
|
||||
}
|
||||
}
|
48
src/Components/Modules/Network.hpp
Normal file
48
src/Components/Modules/Network.hpp
Normal 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();
|
||||
};
|
||||
}
|
253
src/Components/Modules/Party.cpp
Normal file
253
src/Components/Modules/Party.cpp
Normal 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();
|
||||
}
|
||||
}
|
32
src/Components/Modules/Party.hpp
Normal file
32
src/Components/Modules/Party.hpp
Normal 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);
|
||||
};
|
||||
}
|
90
src/Components/Modules/QuickPatch.cpp
Normal file
90
src/Components/Modules/QuickPatch.cpp
Normal 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();
|
||||
}
|
||||
}
|
12
src/Components/Modules/QuickPatch.hpp
Normal file
12
src/Components/Modules/QuickPatch.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Components
|
||||
{
|
||||
class QuickPatch : public Component
|
||||
{
|
||||
public:
|
||||
QuickPatch();
|
||||
const char* GetName() { return "QuickPatch"; };
|
||||
|
||||
private:
|
||||
static _int64* GetStatsID();
|
||||
};
|
||||
}
|
24
src/Components/Modules/RawFiles.cpp
Normal file
24
src/Components/Modules/RawFiles.cpp
Normal 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);
|
||||
}
|
||||
}
|
11
src/Components/Modules/RawFiles.hpp
Normal file
11
src/Components/Modules/RawFiles.hpp
Normal 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);
|
||||
};
|
||||
}
|
41
src/Components/Modules/Renderer.cpp
Normal file
41
src/Components/Modules/Renderer.cpp
Normal 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();
|
||||
}
|
||||
}
|
21
src/Components/Modules/Renderer.hpp
Normal file
21
src/Components/Modules/Renderer.hpp
Normal 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;
|
||||
};
|
||||
}
|
272
src/Components/Modules/ServerList.cpp
Normal file
272
src/Components/Modules/ServerList.cpp
Normal 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();
|
||||
}
|
||||
}
|
94
src/Components/Modules/ServerList.hpp
Normal file
94
src/Components/Modules/ServerList.hpp
Normal 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;
|
||||
};
|
||||
}
|
23
src/Components/Modules/Singleton.cpp
Normal file
23
src/Components/Modules/Singleton.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
14
src/Components/Modules/Singleton.hpp
Normal file
14
src/Components/Modules/Singleton.hpp
Normal 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;
|
||||
};
|
||||
}
|
285
src/Components/Modules/UIFeeder.cpp
Normal file
285
src/Components/Modules/UIFeeder.cpp
Normal 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();
|
||||
}
|
||||
}
|
51
src/Components/Modules/UIFeeder.hpp
Normal file
51
src/Components/Modules/UIFeeder.hpp
Normal 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;
|
||||
};
|
||||
}
|
110
src/Components/Modules/UIScript.cpp
Normal file
110
src/Components/Modules/UIScript.cpp
Normal 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();
|
||||
}
|
||||
}
|
39
src/Components/Modules/UIScript.hpp
Normal file
39
src/Components/Modules/UIScript.hpp
Normal 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;
|
||||
};
|
||||
}
|
27
src/Components/Modules/Window.cpp
Normal file
27
src/Components/Modules/Window.cpp
Normal 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();
|
||||
}
|
||||
}
|
12
src/Components/Modules/Window.hpp
Normal file
12
src/Components/Modules/Window.hpp
Normal 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();
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user