#include "STDInclude.hpp" namespace Components { int QuickPatch::FrameTime = 0; void QuickPatch::UnlockStats() { if (Dedicated::IsEnabled()) return; if (Game::CL_IsCgameInitialized()) { Toast::Show("cardicon_locked", "^1Error", "Not allowed while ingame.", 3000); return; } Command::Execute("setPlayerData prestige 10"); Command::Execute("setPlayerData experience 2516000"); Command::Execute("setPlayerData iconUnlocked cardicon_prestige10_02 1"); // Unlock challenges Game::StringTable* challengeTable = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_STRINGTABLE, "mp/allchallengestable.csv").stringTable; if (challengeTable) { for (int i = 0; i < challengeTable->rowCount; ++i) { // Find challenge const char* challenge = Game::TableLookup(challengeTable, i, 0); int maxState = 0; int maxProgress = 0; // Find correct tier and progress for (int j = 0; j < 10; ++j) { int progress = atoi(Game::TableLookup(challengeTable, i, 6 + j * 2)); if (!progress) break; maxState = j + 2; maxProgress = progress; } Command::Execute(Utils::String::VA("setPlayerData challengeState %s %d", challenge, maxState)); Command::Execute(Utils::String::VA("setPlayerData challengeProgress %s %d", challenge, maxProgress)); } } } int QuickPatch::MsgReadBitsCompressCheckSV(const char *from, char *to, int size) { static char buffer[0x8000]; if (size > 0x800) return 0; size = Game::MSG_ReadBitsCompress(from, buffer, size); if (size > 0x800) return 0; std::memcpy(to, buffer, size); return size; } int QuickPatch::MsgReadBitsCompressCheckCL(const char *from, char *to, int size) { static char buffer[0x100000]; if (size > 0x20000) return 0; size = Game::MSG_ReadBitsCompress(from, buffer, size); if (size > 0x20000) return 0; std::memcpy(to, buffer, size); return size; } int QuickPatch::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) { // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them return -1; } long QuickPatch::AtolAdjustPlayerLimit(const char* string) { return std::min(atol(string), 18l); } void QuickPatch::SelectStringTableEntryInDvarStub() { Command::ClientParams args; if (args.length() >= 4) { std::string cmd = args[0]; std::string table = args[1]; std::string col = args[2]; std::string dvarName = args[3]; Game::dvar_t* dvar = Game::Dvar_FindVar(dvarName.data()); if (Command::Find(dvarName) || (dvar && (dvar->flags & (Game::DVAR_FLAG_WRITEPROTECTED | Game::DVAR_FLAG_CHEAT | Game::DVAR_FLAG_READONLY)))) { return; } } Game::CL_SelectStringTableEntryInDvar_f(); } __declspec(naked) void QuickPatch::JavelinResetHookStub() { __asm { mov eax, 577A10h; call eax; pop edi; mov dword ptr [esi+34h], 0; pop esi; pop ebx; retn; } } __declspec(naked) int QuickPatch::G_GetClientScore() { __asm { mov eax, [esp + 4] // index mov ecx, ds : 1A831A8h // level: &g_clients test ecx, ecx; jz invalid_ptr; imul eax, 366Ch mov eax, [eax + ecx + 3134h] ret invalid_ptr: xor eax, eax ret } } bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size) { strncpy(dest, source, size - 1); dest[size - 1] = 0; for (int i = 0; i < size - 1; i++) { if (!dest[i]) break; if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') { return false; } } return true; } __declspec(naked) void QuickPatch::InvalidNameStub() { static const char* kick_reason = "Invalid name detected."; __asm { call InvalidNameCheck; test al, al jnz returnSafe; pushad; push 1; push kick_reason; push edi; mov eax, 0x004D1600; call eax; add esp, 12; popad; returnSafe: push 0x00401988; retn; } } Game::dvar_t* QuickPatch::g_antilag; __declspec(naked) void QuickPatch::ClientEventsFireWeaponStub() { __asm { // check g_antilag dvar value mov eax, g_antilag; cmp byte ptr[eax + 16], 1; // do antilag if 1 je fireWeapon // do not do antilag if 0 mov eax, 0x1A83554 // level.time mov ecx, [eax] fireWeapon: push edx push ecx push edi mov eax, 0x4A4D50 // FireWeapon call eax add esp, 0Ch pop edi pop ecx retn } } __declspec(naked) void QuickPatch::ClientEventsFireWeaponMeleeStub() { __asm { // check g_antilag dvar value mov eax, g_antilag; cmp byte ptr[eax + 16], 1; // do antilag if 1 je fireWeaponMelee // do not do antilag if 0 mov eax, 0x1A83554 // level.time mov edx, [eax] fireWeaponMelee: push edx push edi mov eax, 0x4F2470 // FireWeaponMelee call eax add esp, 8 pop edi pop ecx retn } } Game::dvar_t* QuickPatch::sv_enableBounces; __declspec(naked) void QuickPatch::BounceStub() { __asm { // check the value of sv_enableBounces push eax; mov eax, sv_enableBounces; cmp byte ptr[eax + 16], 1; pop eax; // always bounce if sv_enableBounces is set to 1 je bounce; // original code cmp dword ptr[esp + 24h], 0; jnz dontBounce; bounce: push 0x004B1B34; retn; dontBounce: push 0x004B1B48; retn; } } Game::dvar_t* QuickPatch::r_customAspectRatio; Game::dvar_t* QuickPatch::Dvar_RegisterAspectRatioDvar(const char* name, char**, int defaultVal, int flags, const char* description) { static std::vector < char * > values = { const_cast("auto"), const_cast("standard"), const_cast("wide 16:10"), const_cast("wide 16:9"), const_cast("custom"), nullptr, }; // register custom aspect ratio dvar r_customAspectRatio = Game::Dvar_RegisterFloat("r_customAspectRatio", 16.0f / 9.0f, 4.0f / 3.0f, 63.0f / 9.0f, flags, "Screen aspect ratio. Divide the width by the height in order to get the aspect ratio value. For example: 16 / 9 = 1,77"); // register enumeration dvar return Game::Dvar_RegisterEnum(name, values.data(), defaultVal, flags, description); } void QuickPatch::SetAspectRatio() { // set the aspect ratio Utils::Hook::Set(0x66E1C78, r_customAspectRatio->current.value); } __declspec(naked) void QuickPatch::SetAspectRatioStub() { __asm { cmp eax, 4; ja goToDefaultCase; je useCustomRatio; // execute switch statement code push 0x005063FC; retn; goToDefaultCase: push 0x005064FC; retn; useCustomRatio: // set custom resolution pushad; call SetAspectRatio; popad; // set widescreen to 1 mov eax, 1; // continue execution push 0x00506495; retn; } } Game::dvar_t* QuickPatch::g_playerCollision; __declspec(naked) void QuickPatch::PlayerCollisionStub() { __asm { // check the value of g_playerCollision push eax; mov eax, g_playerCollision; cmp byte ptr[eax + 16], 0; pop eax; // dont collide if g_playerCollision is set to 0 je dontcollide; // original code mov eax, dword ptr[esp + 0xa0]; jmp collide; collide: push 0x00478376; retn; dontcollide: mov eax, dword ptr[esp + 0xa0]; mov ecx, dword ptr[esp + 9ch]; push eax; push ecx; lea edx, [esp + 48h]; push edx; mov eax, esi; push 0x0047838b; retn; } } Game::dvar_t* QuickPatch::g_playerEjection; __declspec(naked) void QuickPatch::PlayerEjectionStub() { __asm { // check the value of g_playerEjection push eax; mov eax, g_playerEjection; cmp byte ptr[eax + 16], 0; pop eax; // dont eject if g_playerEjection is set to 0 je donteject; // original code cmp dword ptr[ebx + 19ch], edi; jle eject; eject: push 0x005d8152; retn; donteject: push 0x005d815b; retn; } } template std::function < T > ImportFunction(const std::string& dll, const std::string& function) { auto dllHandle = GetModuleHandleA(&dll[0]); auto procAddr = GetProcAddress(dllHandle, &function[0]); return std::function < T >(reinterpret_cast(procAddr)); } QuickPatch::QuickPatch() { QuickPatch::FrameTime = 0; Scheduler::OnFrame([]() { QuickPatch::FrameTime = Game::Sys_Milliseconds(); }); // quit_hard Command::Add("quit_hard", [](Command::Params*) { typedef enum _HARDERROR_RESPONSE_OPTION { OptionAbortRetryIgnore, OptionOk, OptionOkCancel, OptionRetryCancel, OptionYesNo, OptionYesNoCancel, OptionShutdownSystem } HARDERROR_RESPONSE_OPTION, *PHARDERROR_RESPONSE_OPTION; typedef enum _HARDERROR_RESPONSE { ResponseReturnToCaller, ResponseNotHandled, ResponseAbort, ResponseCancel, ResponseIgnore, ResponseNo, ResponseOk, ResponseRetry, ResponseYes } HARDERROR_RESPONSE, *PHARDERROR_RESPONSE; BOOLEAN hasPerms; HARDERROR_RESPONSE response; auto result = ImportFunction("ntdll.dll", "RtlAdjustPrivilege") (19, true, false, &hasPerms); result = ImportFunction("ntdll.dll", "NtRaiseHardError") (0xC000007B /*0x0000000A*/, 0, nullptr, nullptr, OptionShutdownSystem, &response); }); // bounce dvar sv_enableBounces = Game::Dvar_RegisterBool("sv_enableBounces", false, Game::DVAR_FLAG_REPLICATED, "Enables bouncing on the server"); Utils::Hook(0x4B1B2D, QuickPatch::BounceStub, HOOK_JUMP).install()->quick(); // Intermission time dvar Game::Dvar_RegisterFloat("scr_intermissionTime", 10, 0, 120, Game::DVAR_FLAG_REPLICATED | Game::DVAR_FLAG_DEDISAVED, "Time in seconds before match server loads the next map"); // Player Collision dvar g_playerCollision = Game::Dvar_RegisterBool("g_playerCollision", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off"); Utils::Hook(0x47836F, QuickPatch::PlayerCollisionStub, HOOK_JUMP).install()->quick(); // Player Ejection dvar g_playerEjection = Game::Dvar_RegisterBool("g_playerEjection", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off"); Utils::Hook(0x5D814A, QuickPatch::PlayerEjectionStub, HOOK_JUMP).install()->quick(); g_antilag = Game::Dvar_RegisterBool("g_antilag", true, Game::DVAR_FLAG_REPLICATED, "Perform antilag"); Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick(); // Disallow invalid player names Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick(); // Javelin fix Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick(); // Add ultrawide support Utils::Hook(0x0051B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick(); Utils::Hook(0x005063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick(); // Make sure preDestroy is called when the game shuts down Scheduler::OnShutdown(Loader::PreDestroy); // protocol version (workaround for hacks) Utils::Hook::Set(0x4FB501, PROTOCOL); // protocol command Utils::Hook::Set(0x4D36A9, PROTOCOL); Utils::Hook::Set(0x4D36AE, PROTOCOL); Utils::Hook::Set(0x4D36B3, PROTOCOL); // internal version is 99, most servers should accept it Utils::Hook::Set(0x463C61, 208); // remove system pre-init stuff (improper quit, disk full) Utils::Hook::Set(0x411350, 0xC3); // remove STEAMSTART checking for DRM IPC Utils::Hook::Nop(0x451145, 5); Utils::Hook::Set(0x45114C, 0xEB); // LSP disabled Utils::Hook::Set(0x435950, 0xC3); // LSP HELLO Utils::Hook::Set(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP! Utils::Hook::Set(0x4BD900, 0xC3); // main LSP response func Utils::Hook::Set(0x682170, 0xC3); // Telling LSP that we're playing a private match Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket // Don't delete config files if corrupted Utils::Hook::Set(0x47DCB3, 0xEB); Utils::Hook::Set(0x4402B6, 0); // hopefully allow alt-tab during game, used at least in alt-enter handling Utils::Hook::Set(0x45ACE0, 0xC301B0); // fs_basegame Utils::Hook::Set(0x6431D1, BASEGAME); // UI version string Utils::Hook::Set(0x43F73B, "IW4x: " VERSION); // console version string Utils::Hook::Set(0x4B12BB, "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")"); // version string Utils::Hook::Set(0x60BD56, "IW4x (" VERSION ")"); // version string color static float buildLocColor[] = { 1.0f, 1.0f, 1.0f, 0.8f }; Utils::Hook::Set(0x43F710, buildLocColor); // Shift ui version string to the left (ui_buildlocation) Utils::Hook::Nop(0x6310A0, 5); // Don't register the initial dvar Utils::Hook::Nop(0x6310B8, 5); // Don't write the result Dvar::OnInit([]() { *reinterpret_cast(0x62E4B64) = Game::Dvar_RegisterVec2("ui_buildLocation", -60.0f, 474.0f, -10000.0, 10000.0, Game::DVAR_FLAG_READONLY, "Where to draw the build number"); }); // console title if (ZoneBuilder::IsEnabled()) { Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): ZoneBuilder"); } else if (Dedicated::IsEnabled()) { Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): Dedicated"); } else { Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): Console"); } // window title Utils::Hook::Set(0x5076A0, "IW4x: Multiplayer"); // sv_hostname Utils::Hook::Set(0x4D378B, "IW4Host"); // shortversion Utils::Hook::Set(0x60BD91, SHORTVERSION); // console logo Utils::Hook::Set(0x428A66, BASEGAME "/images/logo.bmp"); // splash logo Utils::Hook::Set(0x475F9E, BASEGAME "/images/splash.bmp"); Utils::Hook::Set(0x4876C6, "Successfully read stats data\n"); // Numerical ping (cg_scoreboardPingText 1) Utils::Hook::Set(0x45888E, 1); Utils::Hook::Set(0x45888C, Game::dvar_flag::DVAR_FLAG_CHEAT); // increase font sizes for chat on higher resolutions static float float13 = 13.0f; static float float10 = 10.0f; Utils::Hook::Set(0x5814AE, &float13); Utils::Hook::Set(0x5814C8, &float10); // Enable commandline arguments Utils::Hook::Set(0x464AE4, 0xEB); // remove limit on IWD file loading Utils::Hook::Set(0x642BF3, 0xEB); // dont run UPNP stuff on main thread Utils::Hook::Set(0x48A135, 0xC3); Utils::Hook::Set(0x48A151, 0xC3); Utils::Hook::Nop(0x684080, 5); // Don't spam the console // spawn upnp thread when UPNP_init returns Utils::Hook::Hook(0x47982B, []() { std::thread([]() { // check natpmpstate // state 4 is no more devices to query while (Utils::Hook::Get(0x66CE200) < 4) { Utils::Hook::Call(0x4D7030)(); std::this_thread::sleep_for(500ms); } }).detach(); }, HOOK_JUMP).install()->quick(); // disable the IWNet IP detection (default 'got ipdetect' flag to 1) Utils::Hook::Set(0x649D6F0, 1); // Fix stats sleeping Utils::Hook::Set(0x6832BA, 0xEB); Utils::Hook::Set(0x4BD190, 0xC3); // remove 'impure stats' checking Utils::Hook::Set(0x4BB250, 0x33); Utils::Hook::Set(0x4BB251, 0xC0); Utils::Hook::Set(0x4BB252, 0xC3909090); // default sv_pure to 0 Utils::Hook::Set(0x4D3A74, 0); // Force debug logging Utils::Hook::Set(0x60AE4A, 1); //Utils::Hook::Nop(0x60AE49, 8); //Utils::Hook::Set(0x6FF53C, 0); // remove activeAction execution (exploit in mods) Utils::Hook::Set(0x5A1D43, 0xEB); // disable bind protection Utils::Hook::Set(0x4DACA2, 0xEB); // require Windows 5 Utils::Hook::Set(0x467ADF, 5); Utils::Hook::Set(0x6DF5D6, '5'); // disable 'ignoring asset' notices Utils::Hook::Nop(0x5BB902, 5); // disable migration_dvarErrors Utils::Hook::Set(0x60BDA7, 0); // allow joining 'developer 1' servers Utils::Hook::Set(0x478BA2, 0xEB); // fs_game fixes Utils::Hook::Nop(0x4A5D74, 2); // remove fs_game profiles Utils::Hook::Set(0x4081FD, 0xEB); // defaultweapon Utils::Hook::Set(0x452C1D, 0xEB); // LoadObj weaponDefs // filesystem init default_mp.cfg check Utils::Hook::Nop(0x461A9E, 5); Utils::Hook::Nop(0x461AAA, 5); Utils::Hook::Set(0x461AB4, 0xEB); // vid_restart when ingame Utils::Hook::Nop(0x4CA1FA, 6); // Filter log (initially com_logFilter, but I don't see why that dvar is needed) // Seems like it's needed for B3, so there is a separate handling for dedicated servers in Dedicated.cpp if (!Dedicated::IsEnabled()) { Utils::Hook::Nop(0x647466, 5); // 'dvar set' lines Utils::Hook::Nop(0x5DF4F2, 5); // 'sending splash open' lines } // intro stuff Utils::Hook::Nop(0x60BEE9, 5); // Don't show legals Utils::Hook::Nop(0x60BEF6, 5); // Don't reset the intro dvar Utils::Hook::Set(0x60BED2, "unskippablecinematic IW_logo\n"); Utils::Hook::Set(0x51C2A4, "%s\\" BASEGAME "\\video\\%s.bik"); Utils::Hook::Set(0x51C2C2, 0x78A0AC); // Redirect logs Utils::Hook::Set(0x5E44D8, "logs/games_mp.log"); Utils::Hook::Set(0x60A90C, "logs/console_mp.log"); Utils::Hook::Set(0x60A918, "logs/console_mp.log"); // Rename config Utils::Hook::Set(0x461B4B, CLIENT_CONFIG); Utils::Hook::Set(0x47DCBB, CLIENT_CONFIG); Utils::Hook::Set(0x6098F8, CLIENT_CONFIG); Utils::Hook::Set(0x60B279, CLIENT_CONFIG); Utils::Hook::Set(0x60BBD4, CLIENT_CONFIG); // Disable profile system // Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Causes an error, when calling a harrier killstreak. // Utils::Hook::Nop(0x60BEB8, 5); // GamerProfile_LogInProfile // Utils::Hook::Nop(0x4059EA, 5); // GamerProfile_RegisterCommands Utils::Hook::Nop(0x4059EF, 5); // GamerProfile_RegisterDvars Utils::Hook::Nop(0x47DF9A, 5); // GamerProfile_UpdateSystemDvars Utils::Hook::Set(0x5AF0D0, 0xC3); // GamerProfile_SaveProfile Utils::Hook::Set(0x4E6870, 0xC3); // GamerProfile_UpdateSystemVarsFromProfile Utils::Hook::Set(0x4C37F0, 0xC3); // GamerProfile_UpdateProfileAndSaveIfNeeded Utils::Hook::Set(0x633CA0, 0xC3); // GamerProfile_SetPercentCompleteMP Utils::Hook::Nop(0x5AF1EC, 5); // Profile loading error Utils::Hook::Set(0x5AE212, 0xC3); // Profile reading // GamerProfile_RegisterCommands // Some random function used as nullsub :P Utils::Hook::Set(0x45B868, 0x5188FB); // profile_menuDvarsSetup Utils::Hook::Set(0x45B87E, 0x5188FB); // profile_menuDvarsFinish Utils::Hook::Set(0x45B894, 0x5188FB); // profile_toggleInvertedPitch Utils::Hook::Set(0x45B8AA, 0x5188FB); // profile_setViewSensitivity Utils::Hook::Set(0x45B8C3, 0x5188FB); // profile_setButtonsConfig Utils::Hook::Set(0x45B8D9, 0x5188FB); // profile_setSticksConfig Utils::Hook::Set(0x45B8EF, 0x5188FB); // profile_toggleAutoAim Utils::Hook::Set(0x45B905, 0x5188FB); // profile_SetHasEverPlayed_MainMenu Utils::Hook::Set(0x45B91E, 0x5188FB); // profile_SetHasEverPlayed_SP Utils::Hook::Set(0x45B934, 0x5188FB); // profile_SetHasEverPlayed_SO Utils::Hook::Set(0x45B94A, 0x5188FB); // profile_SetHasEverPlayed_MP Utils::Hook::Set(0x45B960, 0x5188FB); // profile_setVolume Utils::Hook::Set(0x45B979, 0x5188FB); // profile_setGamma Utils::Hook::Set(0x45B98F, 0x5188FB); // profile_setBlacklevel Utils::Hook::Set(0x45B9A5, 0x5188FB); // profile_toggleCanSkipOffensiveMissions // Patch SV_IsClientUsingOnlineStatsOffline Utils::Hook::Set(0x46B710, 0x90C3C033); // Fix mouse lag Utils::Hook::Nop(0x4731F5, 8); Scheduler::OnFrame([]() { SetThreadExecutionState(ES_DISPLAY_REQUIRED); }); // Fix mouse pitch adjustments Dvar::Register("ui_mousePitch", false, Game::DVAR_FLAG_SAVED, ""); UIScript::Add("updateui_mousePitch", [](UIScript::Token) { if (Dvar::Var("ui_mousePitch").get()) { Dvar::Var("m_pitch").set(-0.022f); } else { Dvar::Var("m_pitch").set(0.022f); } }); // Exploit fixes Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket Utils::Hook(0x414D92, QuickPatch::MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage Utils::Hook(0x407376, QuickPatch::SVCanReplaceServerCommand , HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand Utils::Hook(0x5B67ED, QuickPatch::AtolAdjustPlayerLimit , HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest // Patch selectStringTableEntryInDvar Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub); // Patch G_GetClientScore for uninitialised game Utils::Hook(0x469AC0, QuickPatch::G_GetClientScore, HOOK_JUMP).install()->quick(); // Ignore call to print 'Offhand class mismatch when giving weapon...' Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick(); Command::Add("unlockstats", [](Command::Params*) { QuickPatch::UnlockStats(); }); Command::Add("crash", [](Command::Params*) { throw new std::exception(); }); Command::Add("dumptechsets", [](Command::Params* param) { if (param->length() != 2) { Logger::Print("usage: dumptechsets | all\n"); return; } std::vector fastfiles; if (param->get(1) == "all"s) { for (std::string f : Utils::IO::ListFiles("zone/english")) fastfiles.push_back(f.substr(7, f.length() - 10)); for (std::string f : Utils::IO::ListFiles("zone/dlc")) fastfiles.push_back(f.substr(3, f.length() - 6)); for (std::string f : Utils::IO::ListFiles("zone/patch")) fastfiles.push_back(f.substr(5, f.length() - 8)); } else { fastfiles.push_back(param->get(1)); } int count = 0; AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) { // they're basically the same right? if (type == Game::ASSET_TYPE_PIXELSHADER || type == Game::ASSET_TYPE_VERTEXSHADER) { Utils::IO::CreateDir("userraw/shader_bin"); const char* formatString; if (type == Game::ASSET_TYPE_PIXELSHADER) { formatString = "userraw/shader_bin/%.ps"; } else { formatString = "userraw/shader_bin/%.vs"; } if (Utils::IO::FileExists(Utils::String::VA(formatString, name.data()))) return; Utils::Stream buffer(0x1000); Game::MaterialPixelShader* dest = buffer.dest(); buffer.save(asset.pixelShader); if (asset.pixelShader->prog.loadDef.program) { buffer.saveArray(asset.pixelShader->prog.loadDef.program, asset.pixelShader->prog.loadDef.programSize); Utils::Stream::ClearPointer(&dest->prog.loadDef.program); } Utils::IO::WriteFile(Utils::String::VA(formatString, name.data()), buffer.toBuffer()); } static std::map pointerMap; // Check if the given pointer has already been mapped std::function hasPointer = [](const void* pointer) { return (pointerMap.find(pointer) != pointerMap.end()); }; // Get stored offset for given file pointer std::function getPointer = [hasPointer](const void* pointer) { if (hasPointer(pointer)) { return pointerMap[pointer]; } return 0U; }; std::function storePointer = [hasPointer](const void* ptr, unsigned int offset) { if (hasPointer(ptr)) return; pointerMap[ptr] = offset; }; if (type == Game::ASSET_TYPE_TECHNIQUE_SET) { Utils::IO::CreateDir("userraw/techsets"); Utils::Stream buffer(0x1000); Game::MaterialTechniqueSet* dest = buffer.dest(); buffer.save(asset.techniqueSet); if (asset.techniqueSet->name) { buffer.saveString(asset.techniqueSet->name); Utils::Stream::ClearPointer(&dest->name); } for (int i = 0; i < ARRAYSIZE(Game::MaterialTechniqueSet::techniques); ++i) { Game::MaterialTechnique* technique = asset.techniqueSet->techniques[i]; if (technique) { dest->techniques[i] = reinterpret_cast(getPointer(technique)); if (!dest->techniques) { // Size-check is obsolete, as the structure is dynamic buffer.align(Utils::Stream::ALIGN_4); //storePointer(technique, buffer->); Game::MaterialTechnique* destTechnique = buffer.dest(); buffer.save(technique, 8); // Save_MaterialPassArray Game::MaterialPass* destPasses = buffer.dest(); buffer.saveArray(technique->passArray, technique->passCount); for (short j = 0; j < technique->passCount; ++j) { AssertSize(Game::MaterialPass, 20); Game::MaterialPass* destPass = &destPasses[j]; Game::MaterialPass* pass = &technique->passArray[j]; if (pass->vertexDecl) { } if (pass->args) { buffer.align(Utils::Stream::ALIGN_4); buffer.saveArray(pass->args, pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount); Utils::Stream::ClearPointer(&destPass->args); } } if (technique->name) { buffer.saveString(technique->name); Utils::Stream::ClearPointer(&destTechnique->name); } Utils::Stream::ClearPointer(&dest->techniques[i]); } } } } }); for (std::string fastfile : fastfiles) { if (!Game::DB_IsZoneLoaded(fastfile.data())) { Game::XZoneInfo info; info.name = fastfile.data(); info.allocFlags = 0x20; info.freeFlags = 0; Game::DB_LoadXAssets(&info, 1, true); } // unload the fastfiles so we don't run out of memory or asset pools if (count % 5) { Game::XZoneInfo info; info.name = nullptr; info.allocFlags = 0x0; info.freeFlags = 0x20; Game::DB_LoadXAssets(&info, 1, true); } count++; } }); AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& /*name*/, bool* /*restrict*/) { if (type == Game::XAssetType::ASSET_TYPE_GFXWORLD) { std::string buffer; for (unsigned int i = 0; i < asset.gfxWorld->dpvs.staticSurfaceCount; ++i) { buffer.append(Utils::String::VA("%s\n", asset.gfxWorld->dpvs.surfaces[asset.gfxWorld->dpvs.sortedSurfIndex[i]].material->info.name)); } Utils::IO::WriteFile("userraw/logs/matlog.txt", buffer); } }); Dvar::OnInit([] { Dvar::Register("r_drawAabbTrees", false, Game::DVAR_FLAG_USERCREATED, "Draw aabb trees"); }); Scheduler::OnFrame([]() { if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawAabbTrees").get()) return; float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; Game::clipMap_t* clipMap = *reinterpret_cast(0x7998E0); //Game::GfxWorld* gameWorld = *reinterpret_cast(0x66DEE94); if (!clipMap) return; for (unsigned short i = 0; i < clipMap->smodelNodeCount; ++i) { Game::R_AddDebugBounds(cyan, &clipMap->smodelNodes[i].bounds); } for (unsigned int i = 0; i < clipMap->numStaticModels; i += 2) { Game::R_AddDebugBounds(red, &clipMap->staticModelList[i].absBounds); } }); Dvar::OnInit([] { Dvar::Register("r_drawSceneModelBoundingBoxes", false, Game::DVAR_FLAG_CHEAT, "Draw scene model bounding boxes"); Dvar::Register("r_drawSceneModelCollisions", false, Game::DVAR_FLAG_CHEAT, "Draw scene model collisions"); }); Scheduler::OnFrame([]() { if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawSceneModelBoundingBoxes").get()) return; float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; float blue[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; auto* scene = Game::scene; for(auto i = 0; i < scene->sceneModelCount; i++) { if(!scene->sceneModel[i].model) continue; auto b = scene->sceneModel[i].model->bounds; b.midPoint[0] += scene->sceneModel[i].placement.base.origin[0]; b.midPoint[1] += scene->sceneModel[i].placement.base.origin[1]; b.midPoint[2] += scene->sceneModel[i].placement.base.origin[2]; b.halfSize[0] *= scene->sceneModel[i].placement.scale; b.halfSize[1] *= scene->sceneModel[i].placement.scale; b.halfSize[2] *= scene->sceneModel[i].placement.scale; Game::R_AddDebugBounds(red, &b, &scene->sceneModel[i].placement.base.quat); } for(auto i = 0; i < scene->sceneDObjCount; i++) { scene->sceneDObj[i].cull.bounds.halfSize[0] = std::abs(scene->sceneDObj[i].cull.bounds.halfSize[0]); scene->sceneDObj[i].cull.bounds.halfSize[1] = std::abs(scene->sceneDObj[i].cull.bounds.halfSize[1]); scene->sceneDObj[i].cull.bounds.halfSize[2] = std::abs(scene->sceneDObj[i].cull.bounds.halfSize[2]); if (scene->sceneDObj[i].cull.bounds.halfSize[0] < 0 || scene->sceneDObj[i].cull.bounds.halfSize[1] < 0 || scene->sceneDObj[i].cull.bounds.halfSize[2] < 0) { Components::Logger::Print("WARNING: Negative half size for DOBJ %s, this will cause culling issues!", scene->sceneDObj[i].obj->models[0]->name); } Game::R_AddDebugBounds(blue, &scene->sceneDObj[i].cull.bounds); } }); Scheduler::OnFrame([]() { if (!Game::CL_IsCgameInitialized()) return; if (!Dvar::Var("r_drawSceneModelCollisions").get()) return; float green[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; auto* scene = Game::scene; for (auto i = 0; i < scene->sceneModelCount; i++) { if (!scene->sceneModel[i].model) continue; for (auto j = 0; j < scene->sceneModel[i].model->numCollSurfs; j++) { auto b = scene->sceneModel[i].model->collSurfs[j].bounds; b.midPoint[0] += scene->sceneModel[i].placement.base.origin[0]; b.midPoint[1] += scene->sceneModel[i].placement.base.origin[1]; b.midPoint[2] += scene->sceneModel[i].placement.base.origin[2]; b.halfSize[0] *= scene->sceneModel[i].placement.scale; b.halfSize[1] *= scene->sceneModel[i].placement.scale; b.halfSize[2] *= scene->sceneModel[i].placement.scale; Game::R_AddDebugBounds(green, &b, &scene->sceneModel[i].placement.base.quat); } } }); Dvar::OnInit([] { Dvar::Register("r_drawTriggers", false, Game::DVAR_FLAG_CHEAT, "Draw triggers"); }); Scheduler::OnFrame([]() { if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawTriggers").get()) return; float hurt[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; float hurtTouch[4] = { 0.75f, 0.0f, 0.0f, 1.0f }; float damage[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; float once[4] = { 0.0f, 1.0f, 1.0f, 1.0f }; float multiple[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; auto* entities = Game::g_entities; for(auto i = 0u; i < 0x800; i++) { auto* ent = &entities[i]; if(ent->r.isInUse) { Game::Bounds b = ent->r.box; b.midPoint[0] += ent->r.currentOrigin[0]; b.midPoint[1] += ent->r.currentOrigin[1]; b.midPoint[2] += ent->r.currentOrigin[2]; switch(ent->handler) { case Game::ENT_HANDLER_TRIGGER_HURT: Game::R_AddDebugBounds(hurt, &b); break; case Game::ENT_HANDLER_TRIGGER_HURT_TOUCH: Game::R_AddDebugBounds(hurtTouch, &b); break; case Game::ENT_HANDLER_TRIGGER_DAMAGE: Game::R_AddDebugBounds(damage, &b); break; case Game::ENT_HANDLER_TRIGGER_MULTIPLE: if(ent->spawnflags & 0x40) Game::R_AddDebugBounds(once, &b); else Game::R_AddDebugBounds(multiple, &b); break; default: float rv = std::min((float)ent->handler, (float)5) / 5; float gv = std::clamp((float)ent->handler-5, (float)0, (float)5) / 5; float bv = std::clamp((float)ent->handler - 10, (float)0, (float)5) / 5; float color[4] = { rv, gv, bv, 1.0f }; Game::R_AddDebugBounds(color, &b); break; } } } }); // Dvars Dvar::Register("ui_streamFriendly", false, Game::DVAR_FLAG_SAVED, "Stream friendly UI"); // Debug patches #ifdef DEBUG // ui_debugMode 1 //Utils::Hook::Set(0x6312E0, true); // fs_debug 1 Utils::Hook::Set(0x643172, true); // developer 2 Utils::Hook::Set(0x4FA425, 2); Utils::Hook::Set(0x51B087, 2); Utils::Hook::Set(0x60AE13, 2); // developer_Script 1 Utils::Hook::Set(0x60AE2B, true); // Disable cheat protection for dvars Utils::Hook::Set(0x647682, 0xEB); // Constantly draw the mini console Utils::Hook::Set(0x412A45, 0xEB); Scheduler::OnFrame([]() { if (*reinterpret_cast(0x62E4BAC)) { Game::Con_DrawMiniConsole(0, 2, 4, (Game::CL_IsCgameInitialized() ? 1.0f : 0.4f)); } }, true); #else // Remove missing tag message Utils::Hook::Nop(0x4EBF1A, 5); #endif if (Flags::HasFlag("nointro")) { Utils::Hook::Set(0x60BECF, 0xEB); } } QuickPatch::~QuickPatch() { } bool QuickPatch::unitTest() { uint32_t randIntCount = 4'000'000; printf("Generating %d random integers...", randIntCount); auto startTime = std::chrono::high_resolution_clock::now(); for (uint32_t i = 0; i < randIntCount; ++i) { Utils::Cryptography::Rand::GenerateInt(); } auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); Logger::Print("took %llims\n", duration); printf("Testing ZLib compression..."); std::string test = Utils::String::VA("%c", Utils::Cryptography::Rand::GenerateInt()); for (int i = 0; i < 21; ++i) { std::string compressed = Utils::Compression::ZLib::Compress(test); std::string decompressed = Utils::Compression::ZLib::Decompress(compressed); if (test != decompressed) { printf("Error\n"); printf("Compressing %d bytes and decompressing failed!\n", test.size()); return false; } auto size = test.size(); for (unsigned int j = 0; j < size; ++j) { test.append(Utils::String::VA("%c", Utils::Cryptography::Rand::GenerateInt())); } } printf("Success\n"); printf("Testing trimming..."); std::string trim1 = " 1 "; std::string trim2 = " 1"; std::string trim3 = "1 "; Utils::String::Trim(trim1); Utils::String::LTrim(trim2); Utils::String::RTrim(trim3); if (trim1 != "1") return false; if (trim2 != "1") return false; if (trim3 != "1") return false; printf("Success\n"); return true; } }