diff --git a/.gitmodules b/.gitmodules index 3dc6214c..9b0ee066 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,7 +34,7 @@ [submodule "deps/protobuf"] path = deps/protobuf url = https://github.com/google/protobuf.git - branch = 3.2.x + branch = master [submodule "deps/WinToast"] path = deps/WinToast url = https://github.com/mohabouje/WinToast.git diff --git a/deps/libtomcrypt b/deps/libtomcrypt index ef023f33..c39390db 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit ef023f3329c44ec8afca29b9a56bae684daa88d9 +Subproject commit c39390dba1dc519988fe8f6b1af6fdec24f86fc5 diff --git a/deps/protobuf b/deps/protobuf index fa925b98..17174b54 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit fa925b98937bafee6b5d7244cce5c1ad39bf9d36 +Subproject commit 17174b54ddd040a326dec6db75d1bfb5e5b3caa9 diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 1c63c920..3a8a66f7 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -26,7 +26,7 @@ namespace Components char** Console::GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType) { - if (path == reinterpret_cast(0xBAADF00D) || path == reinterpret_cast(0xCDCDCDCD) || IsBadReadPtr(path, 1)) return nullptr; + if (path == reinterpret_cast(0xBAADF00D) || path == reinterpret_cast(0xCDCDCDCD) || ::Utils::Memory::IsBadReadPtr(path)) return nullptr; return Game::FS_GetFileList(path, extension, behavior, numfiles, allocTrackType); } diff --git a/src/Components/Modules/D3D9Ex.cpp b/src/Components/Modules/D3D9Ex.cpp index bb49ce30..6526cf30 100644 --- a/src/Components/Modules/D3D9Ex.cpp +++ b/src/Components/Modules/D3D9Ex.cpp @@ -569,6 +569,8 @@ namespace Components HRESULT D3D9Ex::D3D9Device::SetPixelShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) { + // Use real bad readptr check here, cause the query takes too long + // TODO: Fix the actual error! if (IsBadReadPtr(pConstantData, Vector4fCount * 16)) { //Logger::Print("Invalid shader constant array!\n"); diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index eeb06e8f..f31715be 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -251,7 +251,14 @@ namespace Components int FileSystem::ExecIsFSStub(const char* execFilename) { - return !File(execFilename).exists(); + bool result = !File(execFilename).exists(); + + if(execFilename =="mp/stats_init.cfg"s) + { + OutputDebugStringA(""); + } + + return result; } void FileSystem::FsStartupSync(const char* a1) @@ -290,6 +297,9 @@ namespace Components // Filesystem config checks Utils::Hook(0x6098FD, FileSystem::ExecIsFSStub, HOOK_CALL).install()->quick(); + // Don't strip the folders from the config name (otherwise our ExecIsFSStub fails) + Utils::Hook::Nop(0x6098F2, 5); + // Register additional folders Utils::Hook(0x482647, FileSystem::StartupStub, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index cb95e894..69789c9f 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -260,6 +260,25 @@ namespace Components Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick(); Localization::UseLocalization = Dvar::Register("ui_localize", true, Game::dvar_flag::DVAR_FLAG_NONE, "Use localization strings"); + + // Generate localized entries for custom classes above 10 + AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool* /*restrict*/) + { + if (type != Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY) return; + + if(name == "CLASS_SLOT1"s) + { + for(int i = 11; i <= NUM_CUSTOM_CLASSES; ++i) + { + std::string key = Utils::String::VA("CLASS_SLOT%i", i); + + std::string value = asset.localize->value; + Utils::String::Replace(value, "1", Utils::String::VA("%i", i)); // Pretty ugly, but it should work + + Localization::Set(key, value); + } + } + }); } Localization::~Localization() diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index ca7a00d6..465abc40 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -276,7 +276,17 @@ namespace Components { for (auto menu : Menus::CustomMenus) { - Utils::Merge(&menus, Menus::LoadMenu(menu)); + bool hasMenu = false; + for(auto &loadedMenu : menus) + { + if(loadedMenu->window.name == menu) + { + hasMenu = true; + break; + } + } + + if(!hasMenu) Utils::Merge(&menus, Menus::LoadMenu(menu)); } } @@ -710,6 +720,7 @@ namespace Components Menus::Add("ui_mp/startup_messages.menu"); Menus::Add("ui_mp/pc_store.menu"); Menus::Add("ui_mp/iw4x_credits.menu"); + Menus::Add("ui_mp/resetclass.menu"); } Menus::~Menus() diff --git a/src/Components/Modules/RawFiles.cpp b/src/Components/Modules/RawFiles.cpp index 6eaaf205..486b059e 100644 --- a/src/Components/Modules/RawFiles.cpp +++ b/src/Components/Modules/RawFiles.cpp @@ -17,6 +17,7 @@ namespace Components 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(); + //Utils::Hook(0x609832, 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); diff --git a/src/Components/Modules/StructuredData.cpp b/src/Components/Modules/StructuredData.cpp index ecf8b680..b883d4ef 100644 --- a/src/Components/Modules/StructuredData.cpp +++ b/src/Components/Modules/StructuredData.cpp @@ -116,9 +116,41 @@ namespace Components } } + bool StructuredData::UpdateVersionOffsets(Game::StructuredDataDefSet *set, Game::StructuredDataBuffer *buffer, Game::StructuredDataDef *whatever) + { + Game::StructuredDataDef* newDef = &set->defs[0]; + Game::StructuredDataDef* oldDef = &set->defs[0]; + + for(unsigned int i = 0; i < set->defCount; ++i) + { + if(newDef->version < set->defs[i].version) + { + newDef = &set->defs[i]; + } + + if(set->defs[i].version == *reinterpret_cast(buffer->data)) + { + oldDef = &set->defs[i]; + } + } + + if (newDef->version >= 159 && oldDef->version <= 158) + { + // this should move the data 320 bytes infront + std::memmove(&buffer->data[3963], &buffer->data[3643], oldDef->size - 3643); + } + + // StructuredData_UpdateVersion + return Utils::Hook::Call(0x456830)(set, buffer, whatever); + } + StructuredData::StructuredData() { - Utils::Hook::Set(0x60A2FE, 15); // 15 custom classes + // Correctly upgrade stats + Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick(); + + // 15 or more custom classes + Utils::Hook::Set(0x60A2FE, NUM_CUSTOM_CLASSES); // Only execute this when building zones if (!ZoneBuilder::IsEnabled()) return; @@ -126,7 +158,7 @@ namespace Components AssetHandler::OnLoad([] (Game::XAssetType type, Game::XAssetHeader asset, std::string filename, bool* /*restrict*/) { // Only intercept playerdatadef loading - if (filename != "mp/playerdata.def" || type != Game::XAssetType::ASSET_TYPE_STRUCTUREDDATADEF) return; + if (type != Game::XAssetType::ASSET_TYPE_STRUCTUREDDATADEF || filename != "mp/playerdata.def") return; // Store asset Game::StructuredDataDefSet* data = asset.structuredData; diff --git a/src/Components/Modules/StructuredData.hpp b/src/Components/Modules/StructuredData.hpp index 1cc39833..41bfd714 100644 --- a/src/Components/Modules/StructuredData.hpp +++ b/src/Components/Modules/StructuredData.hpp @@ -31,6 +31,8 @@ namespace Components #endif private: + static bool UpdateVersionOffsets(Game::StructuredDataDefSet *set, Game::StructuredDataBuffer *buffer, Game::StructuredDataDef *oldDef); + static void PatchPlayerDataEnum(Game::StructuredDataDef* data, PlayerDataType type, std::vector& entries); static void PatchAdditionalData(Game::StructuredDataDef* data, std::unordered_map& patches); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 9ad84fed..87450fa4 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1,6 +1,7 @@ #pragma once #define PROTOCOL 0x93 +#define NUM_CUSTOM_CLASSES 15 // This allows us to compile our structures in IDA, for easier reversing :3 #ifdef __cplusplus @@ -1553,6 +1554,12 @@ namespace Game StructuredDataDef *defs; }; + struct StructuredDataBuffer + { + char *data; + size_t size; // 8188 + }; + typedef struct { StructuredDataDef* data; diff --git a/src/STDInclude.cpp b/src/STDInclude.cpp index 7b06c170..8cbd4cb9 100644 --- a/src/STDInclude.cpp +++ b/src/STDInclude.cpp @@ -67,6 +67,10 @@ AssertSize(std::uint8_t, 1); // Ensure pointers are 4 bytes in size (32-bit) static_assert(sizeof(intptr_t) == 4 && sizeof(void*) == 4 && sizeof(size_t) == 4, "This doesn't seem to be a 32-bit environment!"); +#if !defined(_M_IX86) +#error "Invalid processor achritecture!" +#endif + extern "C" { // Disable telemetry data logging diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 8ee44da9..b0f8e8cf 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -49,6 +49,7 @@ template class Sizer { }; #pragma warning(disable: 4005) #pragma warning(disable: 4091) #pragma warning(disable: 4100) +#pragma warning(disable: 4244) #pragma warning(disable: 4389) #pragma warning(disable: 4702) #pragma warning(disable: 4996) // _CRT_SECURE_NO_WARNINGS diff --git a/src/Steam/Proxy.cpp b/src/Steam/Proxy.cpp index 58235c78..124cf253 100644 --- a/src/Steam/Proxy.cpp +++ b/src/Steam/Proxy.cpp @@ -28,63 +28,87 @@ namespace Steam std::function Proxy::SteamFreeLastCallback; std::function Proxy::SteamGetAPICallResult; - void* Interface::getMethod(std::string method) + std::pair Interface::getMethod(std::string method) { if(this->methodCache.find(method) != this->methodCache.end()) { return this->methodCache[method]; } - void* methodPtr = Interface::lookupMethod(method); - this->methodCache[method] = methodPtr; - return methodPtr; + auto methodData = Interface::lookupMethod(method); + this->methodCache[method] = methodData; + return methodData; } - void* Interface::lookupMethod(std::string method) + std::pair Interface::lookupMethod(std::string method) { - if (IsBadReadPtr(this->interfacePtr, 4)) return nullptr; - unsigned char** vftbl = *static_cast(this->interfacePtr); - - while (!IsBadReadPtr(vftbl, 4) && !IsBadCodePtr((FARPROC(*vftbl)))) + if (!::Utils::Memory::IsBadReadPtr(this->interfacePtr)) { - if(this->getMethodName(*vftbl) == method) return *vftbl; - ++vftbl; - } + unsigned char** vftbl = *static_cast(this->interfacePtr); - return nullptr; - } - - size_t Interface::getMethodParamSize(void* method) - { - unsigned char* methodPtr = static_cast(method); - for (; !IsBadReadPtr(methodPtr, 3); ++methodPtr) - { - if (methodPtr[0] == 0xC2 && methodPtr[2] == 0) // __stdcall return + while (!::Utils::Memory::IsBadReadPtr(vftbl) && !::Utils::Memory::IsBadCodePtr((FARPROC(*vftbl)))) { - return (static_cast(methodPtr[1])/* / sizeof(void*)*/); + std::string name; + uint16_t params; + + if (this->getMethodData(*vftbl, &name, ¶ms) && name == method) + { + return{ *vftbl, params }; + } + + ++vftbl; } } - return 0; + return { nullptr, 0 }; } - - std::string Interface::getMethodName(unsigned char* methodPtr) + bool Interface::getMethodData(unsigned char* methodPtr, std::string* name, uint16_t* params) { - for(;!IsBadReadPtr(methodPtr, 3); ++methodPtr) + name->clear(); + *params = 0; + if (::Utils::Memory::IsBadCodePtr(methodPtr)) return false; + + ud_t ud; + ud_init(&ud); + ud_set_mode(&ud, 32); + ud_set_pc(&ud, reinterpret_cast(methodPtr)); + ud_set_input_buffer(&ud, reinterpret_cast(methodPtr), INT32_MAX); + + while (true) { - if(methodPtr[0] == 0x68) // Push - { - char* name = *reinterpret_cast(&methodPtr[1]); - if(!IsBadReadPtr(name, 1)) return name; - } - else if(methodPtr[0] == 0xC2 && methodPtr[2] == 0) // __stdcall return + ud_disassemble(&ud); + + if (ud_insn_mnemonic(&ud) == UD_Iret) { + const ud_operand* operand = ud_insn_opr(&ud, 0); + if (!operand) break; + + if (operand->type == UD_OP_IMM && operand->size == 16) + { + *params = operand->lval.uword; + return true; + } + break; } + + if (ud_insn_mnemonic(&ud) == UD_Ipush && name->empty()) + { + auto operand = ud_insn_opr(&ud, 0); + if (operand->type == UD_OP_IMM && operand->size == 32) + { + char* operandPtr = reinterpret_cast(operand->lval.udword); + if (!::Utils::Memory::IsBadReadPtr(operandPtr)) + { + name->clear(); + name->append(operandPtr); + } + } + } } - return ""; + return false; } void Proxy::SetGame(uint32_t appId) @@ -123,57 +147,38 @@ namespace Steam char* modId = "IW4x"; gameID.modID = *reinterpret_cast(modId) | 0x80000000; + Interface clientUtils(Proxy::ClientEngine->GetIClientUtils(Proxy::SteamPipe, "CLIENTUTILS_INTERFACE_VERSION001")); + clientUtils.invoke("SetAppIDForCurrentPipe", Proxy::AppId, false); -// Interface clientApps(Proxy::ClientEngine->GetIClientApps(Proxy::SteamUser, Proxy::SteamPipe, "CLIENTAPPS_INTERFACE_VERSION001")); -// Interface clientShortcuts(Proxy::ClientEngine->GetIClientShortcuts(Proxy::SteamUser, Proxy::SteamPipe, "CLIENTSHORTCUTS_INTERFACE_VERSION001")); -// if (!clientApps || !clientShortcuts) return; -// -// KeyValuesBuilder builder; -// builder.packString("name", mod.data()); -// builder.packUint64("gameid", gameID.bits); -// builder.packString("installed", "1"); -// builder.packString("gamedir", "IW4x"); -// builder.packString("serverbrowsername", "IW4x"); -// builder.packEnd(); -// -// std::string str = builder.getString(); -// uint32_t uniqueId = clientShortcuts.invoke("GetUniqueLocalAppId"); -// if (clientApps.invoke("SetLocalAppConfig", uniqueId, str.data(), static_cast(str.size()))) + char ourPath[MAX_PATH] = { 0 }; + GetModuleFileNameA(GetModuleHandle(nullptr), ourPath, sizeof(ourPath)); + + char ourDirectory[MAX_PATH] = { 0 }; + GetCurrentDirectoryA(sizeof(ourDirectory), ourDirectory); + + std::string cmdline = ::Utils::String::VA("\"%s\" -proc %d", ourPath, GetCurrentProcessId()); + + // As of 02/19/2017, the SpawnProcess method doesn't require the app id anymore, + // but only for those who participate in the beta. + // Therefore we have to check how many bytes the method expects as arguments + // and adapt our call accordingly! + size_t expectedParams = Proxy::ClientUser.paramSize("SpawnProcess"); + if (expectedParams == 40) // Release { - Interface clientUtils(Proxy::ClientEngine->GetIClientUtils(Proxy::SteamPipe, "CLIENTUTILS_INTERFACE_VERSION001")); - clientUtils.invoke("SetAppIDForCurrentPipe", Proxy::AppId, false); - - char ourPath[MAX_PATH] = { 0 }; - GetModuleFileNameA(GetModuleHandle(nullptr), ourPath, sizeof(ourPath)); - - char ourDirectory[MAX_PATH] = { 0 }; - GetCurrentDirectoryA(sizeof(ourDirectory), ourDirectory); - - std::string cmdline = ::Utils::String::VA("\"%s\" -proc %d", ourPath, GetCurrentProcessId()); - - // As of 02/19/2017, the SpawnProcess method doesn't require the app id anymore, - // but only for those who participate in the beta. - // Therefore we have to check how many bytes the method expects as arguments - // and adapt our call accordingly! - size_t expectedParams = Proxy::ClientUser.paramSize("SpawnProcess"); - if(expectedParams == 40) // Release - { - Proxy::ClientUser.invoke("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0); - } - else if(expectedParams == 36) // Beta - { - Proxy::ClientUser.invoke("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, mod.data(), 0, 0); - } - else if (expectedParams == 48) // Legacy, expects VAC blob - { - char blob[8] = { 0 }; - - Proxy::ClientUser.invoke("SpawnProcess", blob, 0, ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0); - } - else - { - OutputDebugStringA("Steam proxy was unable to match the arguments for SpawnProcess!\n"); - } + Proxy::ClientUser.invoke("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0); + } + else if (expectedParams == 36) // Beta + { + Proxy::ClientUser.invoke("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, mod.data(), 0, 0); + } + else if (expectedParams == 48) // Legacy, expects VAC blob + { + char blob[8] = { 0 }; + Proxy::ClientUser.invoke("SpawnProcess", blob, 0, ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0); + } + else + { + OutputDebugStringA("Steam proxy was unable to match the arguments for SpawnProcess!\n"); } } @@ -322,11 +327,7 @@ namespace Steam void Proxy::StartSteamIfNecessary() { - if (Proxy::GetSteamDirectory().empty() -#ifndef DEBUG - || !Steam::Enabled() -#endif - ) return; + if (Proxy::GetSteamDirectory().empty() || !Steam::Enabled()) return; HKEY hRegKey; DWORD pid = 0; diff --git a/src/Steam/Proxy.hpp b/src/Steam/Proxy.hpp index 1c177541..2159384d 100644 --- a/src/Steam/Proxy.hpp +++ b/src/Steam/Proxy.hpp @@ -146,14 +146,14 @@ namespace Steam return T(); } - void* method = this->getMethod(methodName); - if (!method) + auto method = this->getMethod(methodName); + if (!method.first) { OutputDebugStringA(::Utils::String::VA("Steam interface method %s not found!\n", methodName.data())); return T(); } - size_t argc = this->getMethodParamSize(method); + size_t argc = method.second; constexpr size_t passedArgc = Interface::AddSizes::value; if(passedArgc != argc) { @@ -161,7 +161,7 @@ namespace Steam return T(); } - return reinterpret_cast(method)(this->interfacePtr, args...); + return reinterpret_cast(method.first)(this->interfacePtr, args...); } explicit operator bool() const @@ -171,14 +171,12 @@ namespace Steam size_t paramSize(std::string methodName) { - void* method = this->getMethod(methodName); - if (method) return this->getMethodParamSize(method); - - return 0; + auto method = this->getMethod(methodName); + return method.second; } private: - // TODO: Use fold expressions in C++17 once available + // TODO: Use fold expressions once available (C++17) template struct AddSizes : std::integral_constant {}; @@ -187,11 +185,10 @@ namespace Steam struct AddSizes : std::integral_constant::value + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1))> {}; void* interfacePtr; - std::unordered_map methodCache; - void* getMethod(std::string method); - void* lookupMethod(std::string method); - size_t getMethodParamSize(void* method); - std::string getMethodName(unsigned char* methodPtr); + std::unordered_map> methodCache; + std::pair getMethod(std::string method); + std::pair lookupMethod(std::string method); + bool getMethodData(unsigned char* methodPtr, std::string* name, uint16_t* params); }; class KeyValuesBuilder diff --git a/src/Utils/Memory.cpp b/src/Utils/Memory.cpp index 54192a9b..6b102fe4 100644 --- a/src/Utils/Memory.cpp +++ b/src/Utils/Memory.cpp @@ -64,4 +64,34 @@ namespace Utils return true; } + + bool Memory::IsBadReadPtr(const void* ptr) + { + MEMORY_BASIC_INFORMATION mbi = { nullptr }; + if (VirtualQuery(ptr, &mbi, sizeof(mbi))) + { + DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + bool b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true; + + return b; + } + return true; + } + + bool Memory::IsBadCodePtr(const void* ptr) + { + MEMORY_BASIC_INFORMATION mbi = { nullptr }; + if (VirtualQuery(ptr, &mbi, sizeof(mbi))) + { + DWORD mask = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + bool b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true; + + return b; + } + return true; + } } diff --git a/src/Utils/Memory.hpp b/src/Utils/Memory.hpp index 74505c18..acd5497d 100644 --- a/src/Utils/Memory.hpp +++ b/src/Utils/Memory.hpp @@ -130,5 +130,8 @@ namespace Utils static void FreeAlign(const void* data); static bool IsSet(void* mem, char chr, size_t length); + + static bool IsBadReadPtr(const void* ptr); + static bool IsBadCodePtr(const void* ptr); }; } diff --git a/tools/premake5.exe b/tools/premake5.exe index a4bd4724..ebe7ae7e 100644 Binary files a/tools/premake5.exe and b/tools/premake5.exe differ diff --git a/tools/protoc.exe b/tools/protoc.exe index 617d1437..bdaf708f 100644 Binary files a/tools/protoc.exe and b/tools/protoc.exe differ