diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 9d0e6fc9..bb972dc5 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -12,6 +12,11 @@ namespace Components return Loader::Pregame; } + bool Loader::IsPostgame() + { + return Loader::Postgame; + } + void Loader::Initialize() { Loader::Pregame = true; @@ -93,7 +98,7 @@ namespace Components void Loader::Uninitialize() { - Loader::PreDestroy(); + Loader::PreDestroyNoPostGame(); std::reverse(Loader::Components.begin(), Loader::Components.end()); for (auto component : Loader::Components) @@ -119,14 +124,32 @@ namespace Components { Loader::Postgame = true; - std::reverse(Loader::Components.begin(), Loader::Components.end()); - for (auto component : Loader::Components) + auto components = Loader::Components; + + std::reverse(components.begin(), components.end()); + for (auto component : components) { component->preDestroy(); } } } + void Loader::PreDestroyNoPostGame() + { + if (!Loader::Postgame) + { + auto components = Loader::Components; + + std::reverse(components.begin(), components.end()); + for (auto component : components) + { + component->preDestroy(); + } + + Loader::Postgame = true; + } + } + bool Loader::PerformUnitTests() { bool result = true; diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 48297593..96f6acc1 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -25,11 +25,13 @@ namespace Components static void Initialize(); static void Uninitialize(); static void PreDestroy(); + static void PreDestroyNoPostGame(); static bool PerformUnitTests(); static bool PerformingUnitTests(); static void Register(Component* component); static bool IsPregame(); + static bool IsPostgame(); static Utils::Memory::Allocator* GetAlloctor(); diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 3a8a66f7..247772f3 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -66,8 +66,8 @@ namespace Components } else { - //maxclientCount = Dvar::Var("sv_maxclients").get(); - maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); + maxclientCount = Dvar::Var("party_maxplayers").get(); + //maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); } diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index f632a6c3..c9dcd920 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -195,8 +195,50 @@ namespace Components Logger::MessageMutex.unlock(); } + void Logger::RedirectOSPath(const char* file, char* folder) + { + if (Dvar::Var("g_log").get() == file) + { + if (folder != "userraw"s) + { + if (Dvar::Var("g_log").get()) + { + strcpy_s(folder, 256, "userraw"); + } + } + } + } + + __declspec(naked) void Logger::BuildOSPathStub() + { + __asm + { + pushad + + push [esp + 28h] + push [esp + 30h] + + call Logger::RedirectOSPath + + add esp, 8h + + popad + + mov eax, [esp + 8h] + push ebp + push esi + mov esi, [esp + 0Ch] + + push 64213Fh + retn + } + } + Logger::Logger() { + Dvar::Register("iw4x_onelog", false, Game::dvar_flag::DVAR_FLAG_LATCHED | Game::dvar_flag::DVAR_FLAG_SAVED, "Only write the game log to the 'userraw' OS folder"); + Utils::Hook(0x642139, Logger::BuildOSPathStub, HOOK_JUMP).install()->quick(); + Logger::PipeOutput(nullptr); QuickPatch::OnFrame(Logger::Frame); diff --git a/src/Components/Modules/Logger.hpp b/src/Components/Modules/Logger.hpp index eabab8fa..accafa62 100644 --- a/src/Components/Modules/Logger.hpp +++ b/src/Components/Modules/Logger.hpp @@ -39,6 +39,9 @@ namespace Components static void PrintMessagePipe(const char* data); static void EnqueueMessage(std::string message); + static void BuildOSPathStub(); + static void RedirectOSPath(const char* file, char* folder); + static void NetworkLog(const char* data, bool gLog); static std::string Format(const char** message); diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index 465abc40..9917c956 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -169,9 +169,9 @@ namespace Components return menu; } - std::vector Menus::LoadMenu(std::string menu) + std::vector> Menus::LoadMenu(std::string menu) { - std::vector menus; + std::vector> menus; FileSystem::File menuFile(menu); if (menuFile.exists()) @@ -200,7 +200,7 @@ namespace Components if (!_stricmp(token.string, "menudef")) { Game::menuDef_t* menudef = Menus::ParseMenu(handle); - if (menudef) menus.push_back(menudef); + if (menudef) menus.push_back({ true, menudef }); // Custom menu } } @@ -211,9 +211,9 @@ namespace Components return menus; } - std::vector Menus::LoadMenu(Game::menuDef_t* menudef) + std::vector> Menus::LoadMenu(Game::menuDef_t* menudef) { - std::vector menus = Menus::LoadMenu(Utils::String::VA("ui_mp\\%s.menu", menudef->window.name)); + std::vector> menus = Menus::LoadMenu(Utils::String::VA("ui_mp\\%s.menu", menudef->window.name)); if (menus.empty()) { @@ -222,11 +222,11 @@ namespace Components // // if (originalMenu) // { -// menus.push_back(originalMenu); +// menus.push_back({ false, originalMenu }); // } // else // { - menus.push_back(menudef); + menus.push_back({ false, menudef }); // Native menu // } } @@ -235,7 +235,7 @@ namespace Components Game::MenuList* Menus::LoadScriptMenu(const char* menu) { - std::vector menus = Menus::LoadMenu(menu); + std::vector> menus = Menus::LoadMenu(menu); if (menus.empty()) return nullptr; // Allocate new menu list @@ -253,7 +253,10 @@ namespace Components newList->menuCount = menus.size(); // Copy new menus - std::memcpy(newList->menus, menus.data(), menus.size() * sizeof(Game::menuDef_t *)); + for(unsigned int i = 0; i < menus.size(); ++i) + { + newList->menus[i] = menus[i].second; + } Menus::RemoveMenuList(newList->name); Menus::MenuListList[newList->name] = newList; @@ -261,14 +264,60 @@ namespace Components return newList; } + void Menus::SafeMergeMenus(std::vector>* menus, std::vector> newMenus) + { + // Check if we overwrote a menu + for (unsigned int i = 0; i < menus->size(); ++i) + { + // Try to find the native menu + bool found = !menus->at(i).first; // Only if custom menu, try to find it + + // If there is none, try to find a custom menu + if (!found) + { + for (auto& entry : Menus::MenuList) + { + if (menus->at(i).second == entry.second) + { + found = true; + break; + } + } + } + + // Remove the menu if it has been deallocated (not found) + if (!found) + { + menus->erase(menus->begin() + i); + --i; + continue; + } + + // Remove the menu if it has been loaded twice + for (auto& newMenu : newMenus) + { + if (menus->at(i).second->window.name == std::string(newMenu.second->window.name)) + { + Menus::RemoveMenu(menus->at(i).second); + + menus->erase(menus->begin() + i); + --i; + break; + } + } + } + + Utils::Merge(menus, newMenus); + } + Game::MenuList* Menus::LoadMenuList(Game::MenuList* menuList) { - std::vector menus; + std::vector> menus; for (int i = 0; i < menuList->menuCount; ++i) { if (!menuList->menus[i]) continue; - Utils::Merge(&menus, Menus::LoadMenu(menuList->menus[i])); + Menus::SafeMergeMenus(&menus, Menus::LoadMenu(menuList->menus[i])); } // Load custom menus @@ -279,14 +328,14 @@ namespace Components bool hasMenu = false; for(auto &loadedMenu : menus) { - if(loadedMenu->window.name == menu) + if(loadedMenu.second->window.name == menu) { hasMenu = true; break; } } - if(!hasMenu) Utils::Merge(&menus, Menus::LoadMenu(menu)); + if (!hasMenu) Menus::SafeMergeMenus(&menus, Menus::LoadMenu(menu)); } } @@ -306,7 +355,10 @@ namespace Components newList->menuCount = size; // Copy new menus - std::memcpy(newList->menus, menus.data(), size * sizeof(Game::menuDef_t *)); + for (unsigned int i = 0; i < menus.size(); ++i) + { + newList->menus[i] = menus[i].second; + } Menus::RemoveMenuList(newList->name); Menus::MenuListList[newList->name] = newList; diff --git a/src/Components/Modules/Menus.hpp b/src/Components/Modules/Menus.hpp index 87a04915..4a099a2e 100644 --- a/src/Components/Modules/Menus.hpp +++ b/src/Components/Modules/Menus.hpp @@ -29,8 +29,9 @@ namespace Components static Game::MenuList* LoadMenuList(Game::MenuList* menuList); static Game::MenuList* LoadScriptMenu(const char* menu); - static std::vector LoadMenu(Game::menuDef_t* menudef); - static std::vector LoadMenu(std::string file); + static std::vector> LoadMenu(Game::menuDef_t* menudef); + static std::vector> LoadMenu(std::string file); + static void SafeMergeMenus(std::vector>* menus, std::vector> newMenus); static Game::script_t* LoadMenuScript(std::string name, std::string buffer); static int LoadMenuSource(std::string name, std::string buffer); diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index 7fd60014..c16b32fc 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -7,6 +7,8 @@ namespace Components bool News::Terminate; std::thread News::Thread; std::string News::UpdaterArgs; + std::string News::UpdaterHash; + int News::UpdaterRefresh; bool News::unitTest() { @@ -50,6 +52,56 @@ namespace Components TerminateProcess(GetCurrentProcess(), exitCode); } + bool News::GetLatestUpdater() + { + if (Utils::IO::FileExists("updater.exe")) + { + // Generate hash of local updater.exe + std::string localUpdater = Utils::IO::ReadFile("updater.exe"); + localUpdater = Utils::Cryptography::SHA1::Compute(localUpdater, true); + + if (News::UpdaterHash.empty() || (News::UpdaterRefresh - Game::Sys_Milliseconds() > 900000)) // Check for updater Update every 15 mins max + { + News::UpdaterRefresh = Game::Sys_Milliseconds(); + + std::string data = Utils::Cache::GetFile("/json/updater"); // {"updater.exe":{"SHA1":"*HASH*"}} + + std::string error; + json11::Json listData = json11::Json::parse(data, error); + + if (error.empty() || listData.is_object()) + { + News::UpdaterHash = listData["updater.exe"]["SHA1"].string_value(); + } + } + + if (!News::UpdaterHash.empty() && localUpdater != News::UpdaterHash) + { + remove("updater.exe"); + } + } + + if (!Utils::IO::FileExists("updater.exe")) + { + return News::DownloadUpdater(); + } + + return true; + } + + bool News::DownloadUpdater() + { + std::string data = Utils::Cache::GetFile("/iw4/updater.exe"); + + if (!data.empty()) + { + Utils::IO::WriteFile("updater.exe", data); + return true; + } + + return false; + } + const char* News::GetNewsText() { return Localization::Get("MPUI_MOTD_TEXT"); @@ -95,21 +147,18 @@ namespace Components std::thread([]() { - std::string data = Utils::Cache::GetFile("/iw4/updater.exe"); - - if (data.empty()) + if (News::GetLatestUpdater()) + { + Console::SetSkipShutdown(); + Command::Execute("wait 300; quit;", false); + } + else { Localization::ClearTemp(); News::UpdaterArgs.clear(); Command::Execute("closemenu popup_reconnectingtoparty", false); Game::ShowMessageBox("Failed to download the updater!", "Error"); } - else - { - Console::SetSkipShutdown(); - Utils::IO::WriteFile("updater.exe", data); - Command::Execute("wait 300; quit;", false); - } }).detach(); } @@ -121,7 +170,8 @@ namespace Components News::News() { News::UpdaterArgs.clear(); - + News::UpdaterHash.clear(); + News::UpdaterRefresh = 0; if (ZoneBuilder::IsEnabled() || Dedicated::IsEnabled()) return; // Maybe also dedi? Dvar::Register("g_firstLaunch", true, Game::DVAR_FLAG_SAVED, ""); @@ -152,10 +202,7 @@ namespace Components Localization::Set("MPUI_CHANGELOG_TEXT", "Loading..."); Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFAULT); - if (Utils::IO::FileExists("updater.exe")) - { - remove("updater.exe"); - } + //News::GetLatestUpdater(); // make newsfeed (ticker) menu items not cut off based on safe area Utils::Hook::Nop(0x63892D, 5); @@ -209,6 +256,7 @@ namespace Components News::~News() { News::UpdaterArgs.clear(); + News::UpdaterHash.clear(); } void News::preDestroy() diff --git a/src/Components/Modules/News.hpp b/src/Components/Modules/News.hpp index e374efaf..36ad52d8 100644 --- a/src/Components/Modules/News.hpp +++ b/src/Components/Modules/News.hpp @@ -20,8 +20,13 @@ namespace Components private: static std::string UpdaterArgs; + static std::string UpdaterHash; + static int UpdaterRefresh; static std::thread Thread; + static bool Terminate; + static bool GetLatestUpdater(); + static bool DownloadUpdater(); static void CheckForUpdate(); static void ExitProcessStub(unsigned int exitCode); diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 5597dd34..d4639913 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -302,8 +302,8 @@ namespace Components } else { - //maxclientCount = Dvar::Var("sv_maxclients").get(); - maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); + maxclientCount = Dvar::Var("party_maxplayers").get(); + //maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); } diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 433b433b..65cf600c 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -116,8 +116,8 @@ namespace Components if (!maxclientCount) { - //maxclientCount = Dvar::Var("sv_maxclients").get(); - maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); + maxclientCount = Dvar::Var("party_maxplayers").get(); + //maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); } Utils::InfoString info(Game::Dvar_InfoString_Big(1024)); diff --git a/src/Components/Modules/Toast.cpp b/src/Components/Modules/Toast.cpp index 501db34a..5953c85b 100644 --- a/src/Components/Modules/Toast.cpp +++ b/src/Components/Modules/Toast.cpp @@ -146,6 +146,8 @@ namespace Components Toast::Toast() { + if (Dedicated::IsEnabled()) return; + Toast::ToastHandler = new WinToastLib::WinToastHandler; WinToastLib::WinToast::instance()->setAppName(L"IW4x"); @@ -170,13 +172,28 @@ namespace Components void Toast::preDestroy() { - // Destroying that on the main thread deadlocks, for whatever reason. + if (Dedicated::IsEnabled()) return; + + // Destroying that on the main thread deadlocks. // I did not write the library, so whatever. - std::thread([]() + // If we are not in the postgame state, + // we are not allowed to spawn threads, + // so just don't uninitialize the library. + // That means we did not uninitialize the game + // correctly anyways. + if (Loader::IsPostgame()) + { + std::thread([]() + { + delete WinToastLib::WinToast::instance(); + delete Toast::ToastHandler; + Toast::ToastHandler = nullptr; + }).join(); + } + else { - delete WinToastLib::WinToast::instance(); delete Toast::ToastHandler; Toast::ToastHandler = nullptr; - }).join(); + } } } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 87450fa4..303262d8 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1,6 +1,6 @@ #pragma once -#define PROTOCOL 0x93 +#define PROTOCOL 0x94 #define NUM_CUSTOM_CLASSES 15 // This allows us to compile our structures in IDA, for easier reversing :3 diff --git a/src/Steam/Proxy.cpp b/src/Steam/Proxy.cpp index 124cf253..369bbeb6 100644 --- a/src/Steam/Proxy.cpp +++ b/src/Steam/Proxy.cpp @@ -18,6 +18,10 @@ namespace Steam Utils* Proxy::SteamUtils = nullptr; User* Proxy::SteamUser_ = nullptr; + HANDLE Proxy::Process = nullptr; + HANDLE Proxy::CancelHandle = nullptr; + std::thread Proxy::WatchGuard; + uint32_t Proxy::AppId = 0; std::recursive_mutex Proxy::CallMutex; @@ -325,6 +329,41 @@ namespace Steam Proxy::UnregisterCalls(); } + void Proxy::LaunchWatchGuard() + { + if (Proxy::WatchGuard.joinable()) return; + + HKEY hRegKey; + DWORD pid = 0; + if (RegOpenKeyExA(HKEY_CURRENT_USER, STEAM_REGISTRY_PROCESS_PATH, 0, KEY_QUERY_VALUE, &hRegKey) != ERROR_SUCCESS) return; + + DWORD dwLength = sizeof(pid); + RegQueryValueExA(hRegKey, "pid", nullptr, nullptr, reinterpret_cast(&pid), &dwLength); + RegCloseKey(hRegKey); + + Proxy::CancelHandle = CreateEventA(nullptr, TRUE, FALSE, "CancelEvent"); + if (!Proxy::CancelHandle) return; + + Proxy::Process = OpenProcess(SYNCHRONIZE, FALSE, pid); + if (!Proxy::Process) return; + + Proxy::WatchGuard = std::thread([]() + { + HANDLE handles[] = { Proxy::Process, Proxy::CancelHandle }; + + DWORD result = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + CloseHandle(Proxy::Process); + CloseHandle(Proxy::CancelHandle); + + if (result == WAIT_OBJECT_0) + { + Proxy::SteamPipe = nullptr; + Proxy::SteamUser = nullptr; + Proxy::Uninititalize(); + } + }); + } + void Proxy::StartSteamIfNecessary() { if (Proxy::GetSteamDirectory().empty() || !Steam::Enabled()) return; @@ -348,6 +387,11 @@ namespace Steam CloseHandle(hProcess); }); + allocator.reference(allocator.allocate(1), [](void*) + { + Proxy::LaunchWatchGuard(); + }); + DWORD exitCode; if (!GetExitCodeProcess(process, &exitCode)) return; if (exitCode == STILL_ACTIVE) return; @@ -375,6 +419,8 @@ namespace Steam while (!interval.elapsed(15s) && !Proxy::GetActiveUser()) std::this_thread::sleep_for(10ms); std::this_thread::sleep_for(1s); } + + Proxy::LaunchWatchGuard(); } bool Proxy::Inititalize() @@ -442,6 +488,30 @@ namespace Steam void Proxy::Uninititalize() { + if(Proxy::WatchGuard.get_id() != std::this_thread::get_id() && Proxy::WatchGuard.joinable()) + { + if (Proxy::CancelHandle) + { + SetEvent(Proxy::CancelHandle); + Proxy::WatchGuard.join(); + } + else + { + Proxy::WatchGuard.detach(); + } + } + + Proxy::Process = nullptr; + Proxy::CancelHandle = nullptr; + + Proxy::ClientEngine = nullptr; + Proxy::ClientUser = nullptr; + Proxy::ClientFriends = nullptr; + Proxy::SteamApps = nullptr; + Proxy::SteamFriends = nullptr; + Proxy::SteamUtils = nullptr; + Proxy::SteamUser_ = nullptr; + if (Proxy::SteamClient && Proxy::SteamPipe) { if (Proxy::SteamUser) @@ -452,6 +522,9 @@ namespace Steam Proxy::SteamClient->ReleaseSteamPipe(Proxy::SteamPipe); } + Proxy::SteamPipe = nullptr; + Proxy::SteamUser = nullptr; + Proxy::SteamClient = nullptr; Proxy::Client = ::Utils::Library(); Proxy::Overlay = ::Utils::Library(); } diff --git a/src/Steam/Proxy.hpp b/src/Steam/Proxy.hpp index 2159384d..c7630523 100644 --- a/src/Steam/Proxy.hpp +++ b/src/Steam/Proxy.hpp @@ -302,6 +302,10 @@ namespace Steam static void* SteamPipe; static void* SteamUser; + static HANDLE Process; + static HANDLE CancelHandle; + static std::thread WatchGuard; + static std::recursive_mutex CallMutex; static std::vector Calls; static std::unordered_map Callbacks; @@ -314,6 +318,7 @@ namespace Steam static void UnregisterCalls(); static void StartSteamIfNecessary(); + static void LaunchWatchGuard(); static void ResetActiveUser(); static uint32_t GetActiveUser(); diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index 13b25be9..130075ba 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -9,7 +9,7 @@ namespace Utils bool IsWineEnvironment(); - template void Merge(std::vector* target, T* source, size_t length) + template inline void Merge(std::vector* target, T* source, size_t length) { if (source) { @@ -20,7 +20,7 @@ namespace Utils } } - template void Merge(std::vector* target, std::vector source) + template inline void Merge(std::vector* target, std::vector source) { for (auto &entry : source) {