diff --git a/.gitmodules b/.gitmodules index 92d0b332..1f577bd3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "deps/dxsdk"] path = deps/dxsdk url = https://github.com/devKlausS/dxsdk.git +[submodule "deps/GSL"] + path = deps/GSL + url = https://github.com/microsoft/GSL.git diff --git a/deps/GSL b/deps/GSL new file mode 160000 index 00000000..da01eb28 --- /dev/null +++ b/deps/GSL @@ -0,0 +1 @@ +Subproject commit da01eb28dbb75bed11a51acff0f80ecedd622573 diff --git a/deps/premake/gsl.lua b/deps/premake/gsl.lua new file mode 100644 index 00000000..7a2daf64 --- /dev/null +++ b/deps/premake/gsl.lua @@ -0,0 +1,19 @@ +gsl = { + source = path.join(dependencies.basePath, "GSL"), +} + +function gsl.import() + gsl.includes() +end + +function gsl.includes() + includedirs { + path.join(gsl.source, "include") + } +end + +function gsl.project() + +end + +table.insert(dependencies, gsl) diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index dabec0e8..371e0e4f 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -5,8 +5,8 @@ namespace Components class Component { public: - Component() {}; - virtual ~Component() {}; + Component() {} + virtual ~Component() {} #if defined(DEBUG) || defined(FORCE_UNIT_TESTS) virtual std::string getName() @@ -20,8 +20,8 @@ namespace Components // It's illegal to spawn threads in DLLMain, and apparently it causes problems if they are destroyed there as well. // This method is called before DLLMain (if possible) and should to destroy threads. // It's not 100% guaranteed that it's called outside DLLMain, as it depends on the game, but it's 100% guaranteed, that it is called at all. - virtual void preDestroy() {}; - virtual bool unitTest() { return true; }; // Unit testing entry + virtual void preDestroy() {} + virtual bool unitTest() { return true; } // Unit testing entry }; class Loader diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index f5df53b9..f20ce605 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -514,18 +514,19 @@ namespace Components if (!ZoneBuilder::IsEnabled()) Utils::Hook(0x5BB3F2, AssetHandler::MissingAssetError, HOOK_CALL).install()->quick(); // Log missing empty assets - Scheduler::OnFrame([]() + Scheduler::Loop([] { if (FastFiles::Ready() && !AssetHandler::EmptyAssets.empty()) { for (auto& asset : AssetHandler::EmptyAssets) { - Game::Com_PrintWarning(25, reinterpret_cast(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data()); + Game::Com_PrintWarning(Game::conChannel_t::CON_CHANNEL_FILES, + reinterpret_cast(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data()); } AssetHandler::EmptyAssets.clear(); } - }); + }, Scheduler::Pipeline::MAIN); AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool*) { diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index 51e0bd29..2bc65818 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -429,10 +429,13 @@ namespace Components Auth::LoadKey(true); Steam::SteamUser()->GetSteamID(); - Scheduler::OnFrame(Auth::Frame); + Scheduler::Loop(Auth::Frame, Scheduler::Pipeline::MAIN); // Register dvar - Dvar::Register("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_SERVERINFO, "Security level for GUID certificates (POW)"); + Dvar::OnInit([] + { + Dvar::Register("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_SERVERINFO, "Security level for GUID certificates (POW)"); + }); // Install registration hook Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index 8e43cc2d..f4a126c6 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -268,10 +268,10 @@ namespace Components }); // Verify the list on startup - Scheduler::Once([]() + Scheduler::OnGameInitialized([]() { Bans::BanList list; Bans::LoadBans(&list); - }); + }, Scheduler::Pipeline::SERVER); } } diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index e89c73a2..d7519f45 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -85,26 +85,28 @@ namespace Components { for (auto i = 0u; i < count; ++i) { - Scheduler::OnDelay([]() + Scheduler::Once([] { auto* ent = Game::SV_AddTestClient(); if (ent == nullptr) return; - Scheduler::OnDelay([ent]() + Scheduler::Once([ent]() { Game::Scr_AddString("autoassign"); Game::Scr_AddString("team_marinesopfor"); Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); - Scheduler::OnDelay([ent]() + Scheduler::Once([ent]() { Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u)); Game::Scr_AddString("changeclass"); Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); - }, 1s); - }, 1s); - }, 500ms * (i + 1)); + }, Scheduler::Pipeline::SERVER, 1s); + + }, Scheduler::Pipeline::SERVER, 1s); + + }, Scheduler::Pipeline::SERVER, 500ms * (i + 1)); } } diff --git a/src/Components/Modules/ConnectProtocol.cpp b/src/Components/Modules/ConnectProtocol.cpp index c07d3df8..387196a1 100644 --- a/src/Components/Modules/ConnectProtocol.cpp +++ b/src/Components/Modules/ConnectProtocol.cpp @@ -213,7 +213,7 @@ namespace Components }); // Invocation handler - Scheduler::OnReady(ConnectProtocol::Invocation); + Scheduler::OnGameInitialized(ConnectProtocol::Invocation, Scheduler::Pipeline::CLIENT); ConnectProtocol::InstallProtocol(); ConnectProtocol::EvaluateProtocol(); @@ -232,10 +232,10 @@ namespace Components // Only skip intro here, invocation will be done later. Utils::Hook::Set(0x60BECF, 0xEB); - Scheduler::OnDelay([]() + Scheduler::Once([]() { Command::Execute("openmenu popup_reconnectingtoparty", false); - }, 8s); + }, Scheduler::Pipeline::CLIENT, 8s); } } } diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 20cfb527..d3667054 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -580,7 +580,7 @@ namespace Components if (Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) { - Scheduler::OnFrame(Console::RefreshStatus); + Scheduler::Loop(Console::RefreshStatus, Scheduler::Pipeline::MAIN); } // Code below is not necessary when performing unit tests! @@ -622,10 +622,10 @@ namespace Components } }, HOOK_CALL).install()->quick(); - Scheduler::OnFrame([]() + Scheduler::OnGameInitialized([] { Console::LastRefresh = Game::Sys_Milliseconds(); - }); + }, Scheduler::Pipeline::MAIN); } else if (Dedicated::IsEnabled()/* || ZoneBuilder::IsEnabled()*/) { diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 582bebb0..c3520383 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -268,19 +268,6 @@ namespace Components Network::SendCommand(master, "heartbeat", "IW4"); } - __declspec(naked) void Dedicated::FrameStub() - { - __asm - { - pushad - call Scheduler::FrameHandler - popad - - push 5A8E80h - retn - } - } - Game::dvar_t* Dedicated::Dvar_RegisterSVNetworkFps(const char* dvarName, int, int min, int, int, const char* description) { return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::dvar_flag::DVAR_NONE, description); @@ -296,7 +283,7 @@ namespace Components if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) { // Make sure all callbacks are handled - Scheduler::OnFrame(Steam::SteamAPI_RunCallbacks); + Scheduler::Loop(Steam::SteamAPI_RunCallbacks, Scheduler::Pipeline::MAIN); Dvar::OnInit([] { @@ -361,9 +348,6 @@ namespace Components // don't load the config Utils::Hook::Set(0x4B4D19, 0xEB); - // Dedicated frame handler - Utils::Hook(0x4B0F81, Dedicated::FrameStub, HOOK_CALL).install()->quick(); - // Intercept time wrapping Utils::Hook(0x62737D, Dedicated::TimeWrapStub, HOOK_CALL).install()->quick(); //Utils::Hook::Set(0x62735C, 50'000); // Time wrap after 50 seconds (for testing - i don't want to wait 3 weeks) @@ -374,30 +358,20 @@ namespace Components Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).install()->quick(); // Transmit custom data - Scheduler::OnFrame([]() + Scheduler::Loop([] { - static Utils::Time::Interval interval; - if (interval.elapsed(10s)) - { - interval.update(); - - CardTitles::SendCustomTitlesToClients(); - //Clantags::SendClantagsToClients(); - } - }); + CardTitles::SendCustomTitlesToClients(); + //Clantags::SendClantagsToClients(); + }, Scheduler::Pipeline::SERVER, 10s); // Heartbeats - Scheduler::Once(Dedicated::Heartbeat); - Scheduler::OnFrame([]() + Scheduler::Loop([] { - static Utils::Time::Interval interval; - - if (Dvar::Var("sv_maxclients").get() > 0 && interval.elapsed(2min)) + if (Dvar::Var("sv_maxclients").get() > 0) { - interval.update(); Dedicated::Heartbeat(); } - }); + }, Scheduler::Pipeline::SERVER); Dvar::OnInit([]() { @@ -495,18 +469,12 @@ namespace Components }); } - Scheduler::OnFrame([]() + Scheduler::Loop([] { if (Dvar::Var("sv_running").get()) { - static Utils::Time::Interval interval; - - if (interval.elapsed(15s)) - { - interval.update(); - Dedicated::TransmitGuids(); - } + Dedicated::TransmitGuids(); } - }); + }, Scheduler::Pipeline::SERVER, 15s); } } diff --git a/src/Components/Modules/Dedicated.hpp b/src/Components/Modules/Dedicated.hpp index 80b39795..dba28fc5 100644 --- a/src/Components/Modules/Dedicated.hpp +++ b/src/Components/Modules/Dedicated.hpp @@ -24,8 +24,6 @@ namespace Components static void PostInitialization(); static void PostInitializationStub(); - static void FrameStub(); - static void TransmitGuids(); static void TimeWrapStub(Game::errorParm_t code, const char* message); diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 5865d569..b248f523 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -924,7 +924,7 @@ namespace Components Dvar::Register("mod_force_download_server", false, Game::dvar_flag::DVAR_ARCHIVE, "Set to true to force the client to run the download server for mods (for mods in private matches)."); }); - Scheduler::OnFrame([]() + Scheduler::Loop([] { int workingCount = 0; @@ -957,7 +957,8 @@ namespace Components ++workingCount; } } - }); + + }, Scheduler::Pipeline::MAIN); Script::OnVMShutdown([]() { diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index 99acf132..e4f5ac53 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -2,7 +2,7 @@ namespace Components { - Utils::Signal Dvar::RegistrationSignal; + Utils::Signal Dvar::RegistrationSignal; const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg"; Dvar::Var::Var(const std::string& dvarName) : Var() @@ -201,7 +201,7 @@ namespace Components return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description); } - void Dvar::OnInit(Utils::Slot callback) + void Dvar::OnInit(Utils::Slot callback) { Dvar::RegistrationSignal.connect(callback); } @@ -216,16 +216,13 @@ namespace Components Utils::IO::RemoveFile(Dvar::ArchiveDvarPath); } - Game::dvar_t* Dvar::RegisterName(const char* name, const char* /*default*/, Game::dvar_flag flag, const char* description) + Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, unsigned __int16 flags, const char* description) { - // Run callbacks - Dvar::RegistrationSignal(); - // Name watcher - Scheduler::OnFrame([]() + Scheduler::Loop([] { static std::string lastValidName = "Unknown Soldier"; - std::string name = Dvar::Var("name").get(); + auto name = Dvar::Var("name").get(); // Don't perform any checks if name didn't change if (name == lastValidName) return; @@ -241,7 +238,7 @@ namespace Components lastValidName = name; Friends::UpdateName(); } - }, true); + }, Scheduler::MAIN, 3s); // Don't need to do this every frame std::string username = "Unknown Soldier"; @@ -255,7 +252,7 @@ namespace Components } } - return Dvar::Register(name, username.data(), Dvar::Flag(flag | Game::dvar_flag::DVAR_ARCHIVE).val, description).get(); + return Dvar::Register(name, username.data(), flags | Game::dvar_flag::DVAR_ARCHIVE, description).get(); } void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string) @@ -272,7 +269,7 @@ namespace Components "ui_mptype", }; - for (int i = 0; i < ARRAYSIZE(exceptions); ++i) + for (std::size_t i = 0; i < ARRAYSIZE(exceptions); ++i) { if (Utils::String::ToLower(dvarName) == Utils::String::ToLower(exceptions[i])) { @@ -313,6 +310,12 @@ namespace Components Utils::Hook::Call(0x4F52E0)(dvarName, value); } + void Dvar::Com_InitDvars_Hk() + { + Utils::Hook::Call(0x60AD10)(); + Dvar::RegistrationSignal(); + } + Dvar::Dvar() { // set flags of cg_drawFPS to archive @@ -352,7 +355,7 @@ namespace Components Utils::Hook::Xor(0x6312DE, Game::dvar_flag::DVAR_CHEAT); // Hook dvar 'name' registration - Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).install()->quick(); + Utils::Hook(0x40531C, Dvar::Dvar_RegisterName, HOOK_CALL).install()->quick(); // un-cheat safeArea_* and add archive flags Utils::Hook::Xor(0x42E3F5, Game::dvar_flag::DVAR_READONLY | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_adjusted_horizontal @@ -360,6 +363,8 @@ namespace Components Utils::Hook::Xor(0x42E398, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_horizontal Utils::Hook::Xor(0x42E3C4, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_vertical + Utils::Hook(0x60BB3A, Dvar::Com_InitDvars_Hk, HOOK_CALL).install()->quick(); + // Don't allow setting cheat protected dvars via menus Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); Utils::Hook(0x63CA96, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); @@ -385,10 +390,7 @@ namespace Components Utils::Hook(0x59386A, Dvar::DvarSetFromStringByNameStub, HOOK_CALL).install()->quick(); // If the game closed abruptly, the dvars would not have been restored - Dvar::OnInit([] - { - Dvar::ResetDvarsValue(); - }); + Dvar::OnInit(Dvar::ResetDvarsValue); } Dvar::~Dvar() diff --git a/src/Components/Modules/Dvar.hpp b/src/Components/Modules/Dvar.hpp index c140ac73..5080909c 100644 --- a/src/Components/Modules/Dvar.hpp +++ b/src/Components/Modules/Dvar.hpp @@ -8,8 +8,8 @@ namespace Components class Flag { public: - Flag(Game::dvar_flag flag) : val(flag) {}; - Flag(unsigned __int16 flag) : Flag(static_cast(flag)) {}; + Flag(Game::dvar_flag flag) : val(flag) {} + Flag(unsigned __int16 flag) : Flag(static_cast(flag)) {} Game::dvar_flag val; }; @@ -17,10 +17,10 @@ namespace Components class Var { public: - Var() : dvar(nullptr) {}; - Var(const Var& obj) { this->dvar = obj.dvar; }; - Var(Game::dvar_t* _dvar) : dvar(_dvar) {}; - Var(DWORD ppdvar) : Var(*reinterpret_cast(ppdvar)) {}; + Var() : dvar(nullptr) {} + Var(const Var& obj) { this->dvar = obj.dvar; } + Var(Game::dvar_t* _dvar) : dvar(_dvar) {} + Var(DWORD ppdvar) : Var(*reinterpret_cast(ppdvar)) {} Var(const std::string& dvarName); template T get(); @@ -43,7 +43,9 @@ namespace Components Dvar(); ~Dvar(); - static void OnInit(Utils::Slot callback); + typedef void(Callback)(); + + static void OnInit(Utils::Slot callback); // Only strings and bools use this type of declaration template static Var Register(const char* dvarName, T value, Flag flag, const char* description); @@ -52,15 +54,18 @@ namespace Components static void ResetDvarsValue(); private: - static Utils::Signal RegistrationSignal; + static Utils::Signal RegistrationSignal; static const char* ArchiveDvarPath; - static Game::dvar_t* RegisterName(const char* name, const char* defaultVal, Game::dvar_flag flag, const char* description); + static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, unsigned __int16 flags, const char* description); static void SetFromStringByNameExternal(const char* dvar, const char* value); static void SetFromStringByNameSafeExternal(const char* dvar, const char* value); static void SaveArchiveDvar(const Game::dvar_t* var); static void DvarSetFromStringByNameStub(const char* dvarName, const char* value); + + // Unable to do any earlier because SL system will not be active for string dvars + static void Com_InitDvars_Hk(); }; } diff --git a/src/Components/Modules/FastFiles.cpp b/src/Components/Modules/FastFiles.cpp index 963b1777..e9497a73 100644 --- a/src/Components/Modules/FastFiles.cpp +++ b/src/Components/Modules/FastFiles.cpp @@ -569,28 +569,28 @@ namespace Components FastFiles::AddZonePath("zone\\patch\\"); FastFiles::AddZonePath("zone\\dlc\\"); - Scheduler::OnFrame([]() + Scheduler::Loop([]() { if (FastFiles::Current().empty() || !Dvar::Var("ui_zoneDebug").get()) return; - Game::Font_s* font = Game::R_RegisterFont("fonts/consoleFont", 0); + auto* const font = Game::R_RegisterFont("fonts/consoleFont", 0); float color[4] = { 1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f) }; - std::uint32_t FFTotalSize = *reinterpret_cast(0x10AA5D8); - std::uint32_t FFCurrentOffset = *reinterpret_cast(0x10AA608); + auto FFTotalSize = *reinterpret_cast(0x10AA5D8); + auto FFCurrentOffset = *reinterpret_cast(0x10AA608); float fastfileLoadProgress = (float(FFCurrentOffset) / float(FFTotalSize)) * 100.0f; - if (fastfileLoadProgress == INFINITY) + if (isinf(fastfileLoadProgress)) { fastfileLoadProgress = 100.0f; } - else if (fastfileLoadProgress == NAN) + else if (isnan(fastfileLoadProgress)) { fastfileLoadProgress = 0.0f; } Game::R_AddCmdDrawText(Utils::String::VA("Loading FastFile: %s [%0.1f%%]", FastFiles::Current().data(), fastfileLoadProgress), 0x7FFFFFFF, font, 5.0f, static_cast(Renderer::Height() - 5), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); - }, true); + }, Scheduler::Pipeline::RENDERER); Command::Add("loadzone", [](Command::Params* params) { diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index b64fd46e..23b965f0 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -653,7 +653,7 @@ namespace Components } }); - Scheduler::OnFrame([]() + Scheduler::Loop([] { static Utils::Time::Interval timeInterval; static Utils::Time::Interval sortInterval; @@ -692,11 +692,11 @@ namespace Components Friends::SortList(true); } } - }); + }, Scheduler::Pipeline::CLIENT); UIFeeder::Add(61.0f, Friends::GetFriendCount, Friends::GetFriendText, Friends::SelectFriend); - Scheduler::OnShutdown([]() + Scheduler::OnGameShutdown([] { Friends::ClearPresence("iw4x_server"); Friends::ClearPresence("iw4x_playing"); diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index 605a5510..f2905e18 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -245,7 +245,7 @@ namespace Components Logger::PipeOutput(nullptr); - Scheduler::OnFrame(Logger::Frame); + Scheduler::OnGameInitialized(Logger::Frame, Scheduler::Pipeline::MAIN); Utils::Hook(0x4B0218, Logger::GameLogStub, HOOK_CALL).install()->quick(); Utils::Hook(Game::Com_PrintMessage, Logger::PrintMessageStub, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 1b82de10..b7d72612 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -879,11 +879,11 @@ namespace Components Command::Add("delayReconnect", [](Command::Params*) { - Scheduler::OnDelay([]() + Scheduler::Once([] { Command::Execute("closemenu popup_reconnectingtoparty", false); Command::Execute("reconnect", false); - }, 10s, true); + }, Scheduler::Pipeline::CLIENT, 10s); }); if(Dedicated::IsEnabled()) @@ -908,9 +908,9 @@ namespace Components // Allow hiding specific smodels Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick(); - Scheduler::OnFrame([]() + Scheduler::Loop([] { - Game::GfxWorld*& gameWorld = *reinterpret_cast(0x66DEE94); + auto*& gameWorld = *reinterpret_cast(0x66DEE94); if (!Game::CL_IsCgameInitialized() || !gameWorld || !Dvar::Var("r_listSModels").get()) return; std::map models; @@ -926,16 +926,16 @@ namespace Components } Game::Font_s* font = Game::R_RegisterFont("fonts/smallFont", 0); - int height = Game::R_TextHeight(font); - float scale = 0.75; - float color[4] = { 0, 1.0f, 0, 1.0f }; + auto height = Game::R_TextHeight(font); + auto scale = 0.75f; + float color[4] = {0.0f, 1.0f, 0.0f, 1.0f}; unsigned int i = 0; for (auto& model : models) { Game::R_AddCmdDrawText(Utils::String::VA("%d %s", model.second, model.first.data()), 0x7FFFFFFF, font, 15.0f, (height * scale + 1) * (i++ + 1) + 15.0f, scale, scale, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); } - }, true); + }, Scheduler::Pipeline::RENDERER); } Maps::~Maps() diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 6f112469..f7598642 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -320,11 +320,11 @@ namespace Components size_t i = 0; for (auto& nodeListData : nodeListReponseMessages) { - Scheduler::OnDelay([nodeListData, i, address]() + Scheduler::Once([nodeListData, i, address]() { NODE_LOG("Sending %d nodeListResponse length to %s\n", nodeListData.length(), address.getCString()); Session::Send(address, "nodeListResponse", nodeListData); - }, NODE_SEND_RATE * i++); + }, Scheduler::Pipeline::MAIN, NODE_SEND_RATE * i++); } } @@ -339,12 +339,13 @@ namespace Components if (ZoneBuilder::IsEnabled()) return; Dvar::Register("net_natFix", false, 0, "Fix node registration for certain firewalls/routers"); - Scheduler::OnFrameAsync([]() + Scheduler::Loop([] { Node::StoreNodes(false); }); - Scheduler::OnFrame(Node::RunFrame); + Scheduler::Loop(Node::RunFrame, Scheduler::Pipeline::MAIN); + Session::Handle("nodeListResponse", Node::HandleResponse); Session::Handle("nodeListRequest", [](Network::Address address, const std::string&) { diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 3c51a7f2..33842419 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -278,7 +278,7 @@ namespace Components Party::Connect(Party::Container.target); }); - Scheduler::OnFrame([]() + Scheduler::Loop([] { if (Party::Container.valid) { @@ -297,7 +297,8 @@ namespace Components Party::ConnectError("Playlist request timed out."); } } - }, true); + + }, Scheduler::Pipeline::CLIENT); // Basic info handler Network::Handle("getInfo", [](Network::Address address, const std::string& data) diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 8cb95b53..a0823807 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -252,7 +252,7 @@ namespace Components Utils::Hook(0x5063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick(); // Make sure preDestroy is called when the game shuts down - Scheduler::OnShutdown(Loader::PreDestroy); + Scheduler::OnGameShutdown(Loader::PreDestroy); // protocol version (workaround for hacks) Utils::Hook::Set(0x4FB501, PROTOCOL); @@ -469,10 +469,10 @@ namespace Components // Fix mouse lag Utils::Hook::Nop(0x4731F5, 8); - Scheduler::OnFrame([]() + Scheduler::Loop([] { SetThreadExecutionState(ES_DISPLAY_REQUIRED); - }); + }, Scheduler::Pipeline::RENDERER); // Fix mouse pitch adjustments Dvar::Register("ui_mousePitch", false, Game::DVAR_ARCHIVE, ""); @@ -709,13 +709,13 @@ namespace Components // Constantly draw the mini console Utils::Hook::Set(0x412A45, 0xEB); - Scheduler::OnFrame([]() + Scheduler::Loop([]() { if (*reinterpret_cast(0x62E4BAC)) { Game::Con_DrawMiniConsole(0, 2, 4, (Game::CL_IsCgameInitialized() ? 1.0f : 0.4f)); } - }, true); + }, Scheduler::Pipeline::RENDERER); #else // Remove missing tag message Utils::Hook::Nop(0x4EBF1A, 5); diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index 7eaaabd7..3fcccb68 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -5,8 +5,8 @@ namespace Components Utils::Signal Renderer::BackendFrameSignal; Utils::Signal Renderer::SingleBackendFrameSignal; - Utils::Signal Renderer::EndRecoverDeviceSignal; - Utils::Signal Renderer::BeginRecoverDeviceSignal; + Utils::Signal Renderer::EndRecoverDeviceSignal; + Utils::Signal Renderer::BeginRecoverDeviceSignal; Dvar::Var Renderer::r_drawTriggers; Dvar::Var Renderer::r_drawSceneModelCollisions; @@ -32,19 +32,6 @@ namespace Components float once[4] = { 0.0f, 1.0f, 1.0f, 1.0f }; float multiple[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; - __declspec(naked) void Renderer::FrameStub() - { - __asm - { - pushad - call Scheduler::FrameHandler - popad - - push 5AC950h - retn - } - } - __declspec(naked) void Renderer::BackendFrameStub() { __asm @@ -87,12 +74,12 @@ namespace Components Renderer::BackendFrameSignal.connect(callback); } - void Renderer::OnDeviceRecoveryEnd(Utils::Slot callback) + void Renderer::OnDeviceRecoveryEnd(Utils::Slot callback) { Renderer::EndRecoverDeviceSignal.connect(callback); } - void Renderer::OnDeviceRecoveryBegin(Utils::Slot callback) + void Renderer::OnDeviceRecoveryBegin(Utils::Slot callback) { Renderer::BeginRecoverDeviceSignal.connect(callback); } @@ -463,7 +450,8 @@ namespace Components { if (Dedicated::IsEnabled()) return; - Scheduler::OnFrame([]() { + Scheduler::Loop([] + { if (Game::CL_IsCgameInitialized()) { DebugDrawAABBTrees(); @@ -472,35 +460,7 @@ namespace Components DebugDrawSceneModelCollisions(); DebugDrawTriggers(); } - }); - - // Renderer::OnBackendFrame([] (IDirect3DDevice9* device) - // { - // if (Game::Sys_Milliseconds() % 2) - // { - // device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 0, 0); - // } - // - // return; - // - // IDirect3DSurface9* buffer = nullptr; - // - // device->CreateOffscreenPlainSurface(Renderer::Width(), Renderer::Height(), D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &buffer, nullptr); - // device->GetFrontBufferData(0, buffer); - // - // if (buffer) - // { - // D3DSURFACE_DESC desc; - // D3DLOCKED_RECT lockedRect; - // - // buffer->GetDesc(&desc); - // - // HRESULT res = buffer->LockRect(&lockedRect, NULL, D3DLOCK_READONLY); - // - // - // buffer->UnlockRect(); - // } - // }); + }, Scheduler::Pipeline::RENDERER); // Log broken materials Utils::Hook(0x0054CAAA, Renderer::StoreGfxBufContextPtrStub1, HOOK_JUMP).install()->quick(); @@ -510,9 +470,6 @@ namespace Components Utils::Hook::Set(0x005086DA, "^3solid^7"); Utils::Hook(0x00580F53, Renderer::DrawTechsetForMaterial, HOOK_CALL).install()->quick(); - // Frame hook - Utils::Hook(0x5ACB99, Renderer::FrameStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x536A80, Renderer::BackendFrameStub, HOOK_JUMP).install()->quick(); // Begin device recovery (not D3D9Ex) diff --git a/src/Components/Modules/Renderer.hpp b/src/Components/Modules/Renderer.hpp index 9e43a154..11c799cd 100644 --- a/src/Components/Modules/Renderer.hpp +++ b/src/Components/Modules/Renderer.hpp @@ -6,6 +6,7 @@ namespace Components { public: typedef void(BackendCallback)(IDirect3DDevice9*); + typedef void(Callback)(); Renderer(); ~Renderer(); @@ -15,12 +16,11 @@ namespace Components static void OnBackendFrame(Utils::Slot callback); static void OnNextBackendFrame(Utils::Slot callback); - static void OnDeviceRecoveryEnd(Utils::Slot callback); - static void OnDeviceRecoveryBegin(Utils::Slot callback); + + static void OnDeviceRecoveryEnd(Utils::Slot callback); + static void OnDeviceRecoveryBegin(Utils::Slot callback); private: - static void FrameStub(); - static void BackendFrameStub(); static void BackendFrameHandler(); @@ -40,8 +40,8 @@ namespace Components static void DebugDrawModelNames(); static void DebugDrawAABBTrees(); - static Utils::Signal EndRecoverDeviceSignal; - static Utils::Signal BeginRecoverDeviceSignal; + static Utils::Signal EndRecoverDeviceSignal; + static Utils::Signal BeginRecoverDeviceSignal; static Utils::Signal BackendFrameSignal; static Utils::Signal SingleBackendFrameSignal; diff --git a/src/Components/Modules/Scheduler.cpp b/src/Components/Modules/Scheduler.cpp index 22b412c3..ef403946 100644 --- a/src/Components/Modules/Scheduler.cpp +++ b/src/Components/Modules/Scheduler.cpp @@ -1,162 +1,193 @@ #include +constexpr bool COND_CONTINUE = false; +constexpr bool COND_END = true; + namespace Components { - bool Scheduler::AsyncTerminate; - std::thread Scheduler::AsyncThread; + std::thread Scheduler::Thread; + volatile bool Scheduler::Kill = false; + Scheduler::TaskPipeline Scheduler::Pipelines[Pipeline::COUNT]; - bool Scheduler::ReadyPassed = false; - Utils::Signal Scheduler::ReadySignal; - Utils::Signal Scheduler::ShutdownSignal; - - Utils::Signal Scheduler::FrameSignal; - Utils::Signal Scheduler::FrameOnceSignal; - std::vector Scheduler::DelayedSlots; - - Utils::Signal Scheduler::AsyncFrameSignal; - Utils::Signal Scheduler::AsyncFrameOnceSignal; - - void Scheduler::Once(Utils::Slot callback, bool clientOnly) + void Scheduler::TaskPipeline::add(Task&& task) { - if (clientOnly && (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())) return; - Scheduler::FrameOnceSignal.connect(callback); - } - - void Scheduler::OnShutdown(Utils::Slot callback) - { - Scheduler::ShutdownSignal.connect(callback); - } - - void Scheduler::OnFrame(Utils::Slot callback, bool clientOnly) - { - if (clientOnly && (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())) return; - Scheduler::FrameSignal.connect(callback); - } - - void Scheduler::OnReady(Utils::Slot callback, bool clientOnly) - { - if (clientOnly && (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())) return; - if (Scheduler::ReadyPassed) Scheduler::Once(callback); - else Scheduler::ReadySignal.connect(callback); - } - - void Scheduler::ReadyHandler() - { - if (!FastFiles::Ready()) + newCallbacks_.access([&task](taskList& tasks) { - Scheduler::Once(Scheduler::ReadyHandler); - } - else + tasks.emplace_back(std::move(task)); + }); + } + + void Scheduler::TaskPipeline::execute() + { + callbacks_.access([&](taskList& tasks) { - Scheduler::ReadyPassed = true; - Scheduler::ReadySignal(); - Scheduler::ReadySignal.clear(); - } - } + this->mergeCallbacks(); - void Scheduler::FrameHandler() - { - Scheduler::DelaySignal(); - Scheduler::FrameSignal(); - - Utils::Signal copy(Scheduler::FrameOnceSignal); - Scheduler::FrameOnceSignal.clear(); - copy(); - } - - void Scheduler::OnDelay(Utils::Slot callback, std::chrono::nanoseconds delay, bool clientOnly) - { - if (clientOnly && (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())) return; - - Scheduler::DelayedSlot slot; - slot.callback = callback; - slot.delay = delay; - - Scheduler::DelayedSlots.push_back(slot); - } - - void Scheduler::DelaySignal() - { - Utils::Signal signal; - - for (auto i = Scheduler::DelayedSlots.begin(); i != Scheduler::DelayedSlots.end();) - { - if (i->interval.elapsed(i->delay)) + for (auto i = tasks.begin(); i != tasks.end();) { - signal.connect(i->callback); - i = Scheduler::DelayedSlots.erase(i); - continue; + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - i->lastCall; + + if (diff < i->interval) + { + ++i; + continue; + } + + i->lastCall = now; + + const auto res = i->handler(); + if (res == COND_END) + { + i = tasks.erase(i); + } + else + { + ++i; + } + } + }); + } + + void Scheduler::TaskPipeline::mergeCallbacks() + { + callbacks_.access([&](taskList& tasks) + { + newCallbacks_.access([&](taskList& new_tasks) + { + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), std::move_iterator(new_tasks.end())); + new_tasks = {}; + }); + }); + } + + void Scheduler::Execute(Pipeline type) + { + AssertCount(type, Pipeline::COUNT); + Pipelines[type].execute(); + } + + void Scheduler::REndFrame_Hk() + { + Execute(Pipeline::RENDERER); + Utils::Hook::Call(0x50AB20)(); + } + + void Scheduler::ServerFrame_Hk() + { + Execute(Pipeline::SERVER); + Utils::Hook::Call(0x471C50)(); + } + + void Scheduler::ClientFrame_Hk(const int localClientNum) + { + Utils::Hook::Call(0x5A8E80)(localClientNum); + Execute(Pipeline::CLIENT); + } + + void Scheduler::MainFrame_Hk() + { + Execute(Pipeline::MAIN); + Utils::Hook::Call(0x47DCA0)(); + } + + void Scheduler::SysSetBlockSystemHotkeys_Hk(int block) + { + Execute(Pipeline::QUIT); + Utils::Hook::Call(0x46B370)(block); + } + + void Scheduler::Schedule(const std::function& callback, const Pipeline type, + const std::chrono::milliseconds delay) + { + AssertCount(type, Pipeline::COUNT); + + Task task; + task.handler = callback; + task.interval = delay; + task.lastCall = std::chrono::high_resolution_clock::now(); + + Pipelines[type].add(std::move(task)); + } + + void Scheduler::Loop(const std::function& callback, const Pipeline type, + const std::chrono::milliseconds delay) + { + Schedule([callback] + { + callback(); + return COND_CONTINUE; + }, type, delay); + } + + void Scheduler::Once(const std::function& callback, const Pipeline type, + const std::chrono::milliseconds delay) + { + Schedule([callback] + { + callback(); + return COND_END; + }, type, delay); + } + + void Scheduler::OnGameInitialized(const std::function& callback, const Pipeline type, + const std::chrono::milliseconds delay) + { + Schedule([=] + { + if (Game::Sys_IsDatabaseReady2()) + { + Once(callback, type, delay); + return COND_END; } - ++i; - } - - signal(); + return COND_CONTINUE; + }, Pipeline::MAIN); // Once Com_Frame_Try_Block_Function is called we know the game is 'ready' } - void Scheduler::ShutdownStub(int num) + void Scheduler::OnGameShutdown(const std::function& callback) { - Scheduler::ShutdownSignal(); - Utils::Hook::Call(0x46B370)(num); - } - - void Scheduler::OnFrameAsync(Utils::Slot callback) - { - Scheduler::AsyncFrameSignal.connect(callback); - } - - void Scheduler::OnceAsync(Utils::Slot callback) - { - Scheduler::AsyncFrameOnceSignal.connect(callback); + Schedule([callback] + { + callback(); + return COND_END; + }, Pipeline::QUIT, 0ms); } Scheduler::Scheduler() { - Scheduler::ReadyPassed = false; - Scheduler::Once(Scheduler::ReadyHandler); - - Utils::Hook(0x4D697A, Scheduler::ShutdownStub, HOOK_CALL).install()->quick(); - - if (!Loader::IsPerformingUnitTests()) + Thread = Utils::Thread::createNamedThread("Async Scheduler", [] { - Scheduler::AsyncTerminate = false; - Scheduler::AsyncThread = std::thread([]() + while (!Kill) { - while (!Scheduler::AsyncTerminate) - { - Scheduler::AsyncFrameSignal(); + Execute(Pipeline::ASYNC); + std::this_thread::sleep_for(10ms); + } + }); - Utils::Signal copy(Scheduler::AsyncFrameOnceSignal); - Scheduler::AsyncFrameOnceSignal.clear(); - copy(); + Utils::Hook(0x4DBE9A, REndFrame_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x518D5C, REndFrame_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x5ACBA3, REndFrame_Hk, HOOK_CALL).install()->quick(); - std::this_thread::sleep_for(16ms); - } - }); - } - } + // Hook G_Glass_Update so we may fix TLS issues + Utils::Hook(0x416049, ServerFrame_Hk, HOOK_CALL).install()->quick(); - Scheduler::~Scheduler() - { - Scheduler::ReadySignal.clear(); - Scheduler::ShutdownSignal.clear(); + // CL_CheckTimeout + Utils::Hook(0x4B0F81, ClientFrame_Hk, HOOK_CALL).install()->quick(); - Scheduler::FrameSignal.clear(); - Scheduler::FrameOnceSignal.clear(); - Scheduler::DelayedSlots.clear(); + // Com_Frame_Try_Block_Function + Utils::Hook(0x4B724F, MainFrame_Hk, HOOK_CALL).install()->quick(); - Scheduler::AsyncFrameSignal.clear(); - Scheduler::AsyncFrameOnceSignal.clear(); - - Scheduler::ReadyPassed = false; + // Sys_Quit + Utils::Hook(0x4D697A, SysSetBlockSystemHotkeys_Hk, HOOK_CALL).install()->quick(); } void Scheduler::preDestroy() { - Scheduler::AsyncTerminate = true; - if (Scheduler::AsyncThread.joinable()) + Kill = true; + if (Thread.joinable()) { - Scheduler::AsyncThread.join(); + Thread.join(); } } } diff --git a/src/Components/Modules/Scheduler.hpp b/src/Components/Modules/Scheduler.hpp index 7b187b89..8a85fd26 100644 --- a/src/Components/Modules/Scheduler.hpp +++ b/src/Components/Modules/Scheduler.hpp @@ -5,50 +5,64 @@ namespace Components class Scheduler : public Component { public: - typedef void(Callback)(); + enum Pipeline + { + ASYNC, + RENDERER, + SERVER, + CLIENT, + MAIN, + QUIT, + COUNT, + }; Scheduler(); - ~Scheduler(); void preDestroy() override; - static void OnShutdown(Utils::Slot callback); - static void OnFrame(Utils::Slot callback, bool clientOnly = false); - static void OnReady(Utils::Slot callback, bool clientOnly = false); - static void Once(Utils::Slot callback, bool clientOnly = false); - static void OnDelay(Utils::Slot callback, std::chrono::nanoseconds delay, bool clientOnly = false); - - static void OnFrameAsync(Utils::Slot callback); - static void OnceAsync(Utils::Slot callback); - - static void FrameHandler(); + static void Schedule(const std::function& callback, Pipeline type = Pipeline::ASYNC, + std::chrono::milliseconds delay = 0ms); + static void Loop(const std::function& callback, Pipeline type = Pipeline::ASYNC, + std::chrono::milliseconds delay = 0ms); + static void Once(const std::function& callback, Pipeline type = Pipeline::ASYNC, + std::chrono::milliseconds delay = 0ms); + static void OnGameInitialized(const std::function& callback, Pipeline type = Pipeline::ASYNC, + std::chrono::milliseconds delay = 0ms); + static void OnGameShutdown(const std::function& callback); private: - class DelayedSlot + struct Task { - public: - std::chrono::nanoseconds delay; - Utils::Time::Interval interval; - Utils::Slot callback; + std::function handler{}; + std::chrono::milliseconds interval{}; + std::chrono::high_resolution_clock::time_point lastCall{}; }; - static bool AsyncTerminate; - static std::thread AsyncThread; + using taskList = std::vector; - static Utils::Signal FrameSignal; - static Utils::Signal FrameOnceSignal; - static std::vector DelayedSlots; + class TaskPipeline + { + public: + void add(Task&& task); + void execute(); - static bool ReadyPassed; - static Utils::Signal ReadySignal; - static Utils::Signal ShutdownSignal; + private: + Utils::Concurrency::Container newCallbacks_; + Utils::Concurrency::Container callbacks_; - static Utils::Signal AsyncFrameSignal; - static Utils::Signal AsyncFrameOnceSignal; + void mergeCallbacks(); + }; - static void ReadyHandler(); - static void DelaySignal(); + static volatile bool Kill; + static std::thread Thread; + static TaskPipeline Pipelines[]; - static void ShutdownStub(int num); + static void Execute(Pipeline type); + + static void REndFrame_Hk(); + static void ServerFrame_Hk(); + static void ClientFrame_Hk(int localClientNum); + static void MainFrame_Hk(); + static void SysSetBlockSystemHotkeys_Hk(int block); }; } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 0beb6ce0..5a62ee6c 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -14,7 +14,7 @@ namespace Components const char* Script::ReplacedPos = nullptr; int Script::LastFrameTime = -1; - Utils::Signal Script::VMShutdownSignal; + Utils::Signal Script::VMShutdownSignal; void Script::FunctionError() { @@ -411,7 +411,7 @@ namespace Components } } - void Script::OnVMShutdown(Utils::Slot callback) + void Script::OnVMShutdown(Utils::Slot callback) { Script::ScriptBaseProgramNum.clear(); Script::VMShutdownSignal.connect(std::move(callback)); @@ -718,7 +718,7 @@ namespace Components Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_LoadGame Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_ShutdownGame - Scheduler::OnFrame([]() + Scheduler::Loop([]() { if (!Game::SV_Loaded()) return; @@ -735,7 +735,7 @@ namespace Components } Script::LastFrameTime = nowMs; - }); + }, Scheduler::Pipeline::SERVER); #ifdef _DEBUG Script::AddFunction("DebugBox", [] diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index 2885e53e..893fd726 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -9,12 +9,14 @@ namespace Components Script(); ~Script(); + typedef void(Callback)(); + static int LoadScriptAndLabel(const std::string& script, const std::string& label); static void AddFunction(const char* name, Game::BuiltinFunction func, int type = 0); static void AddMethod(const char* name, Game::BuiltinMethod func, int type = 0); - static void OnVMShutdown(Utils::Slot callback); + static void OnVMShutdown(Utils::Slot callback); static Game::client_t* GetClient(const Game::gentity_t* gentity); @@ -31,7 +33,7 @@ namespace Components static const char* ReplacedPos; static int LastFrameTime; - static Utils::Signal VMShutdownSignal; + static Utils::Signal VMShutdownSignal; static void CompileError(unsigned int offset, const char* message, ...); static void PrintSourcePos(const char* filename, unsigned int offset); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 5bffcf9e..fe79963f 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -922,7 +922,7 @@ namespace Components UIScript::AddOwnerDraw(253, ServerList::UpdateGameType); // Add frame callback - Scheduler::OnFrame(ServerList::Frame); + Scheduler::OnGameInitialized(ServerList::Frame, Scheduler::Pipeline::CLIENT); } ServerList::~ServerList() diff --git a/src/Components/Modules/Session.cpp b/src/Components/Modules/Session.cpp index 09efdd1f..d83717d2 100644 --- a/src/Components/Modules/Session.cpp +++ b/src/Components/Modules/Session.cpp @@ -33,11 +33,11 @@ namespace Components Network::SendCommand(target, command, data); - Scheduler::OnDelay([delayData]() + Scheduler::Once([delayData]() { Network::SendCommand(delayData->target, delayData->command, delayData->data); delete delayData; - }, 500ms + std::chrono::milliseconds(rand() % 200)); + }, Scheduler::Pipeline::MAIN, 500ms + std::chrono::milliseconds(rand() % 200)); #else std::lock_guard _(Session::Mutex); diff --git a/src/Components/Modules/Toast.cpp b/src/Components/Modules/Toast.cpp index eda8240e..93f8409f 100644 --- a/src/Components/Modules/Toast.cpp +++ b/src/Components/Modules/Toast.cpp @@ -148,10 +148,7 @@ namespace Components { if (Dedicated::IsEnabled() || Monitor::IsEnabled()) return; - Scheduler::OnReady([]() - { - Scheduler::OnFrame(Toast::Handler); - }); + Scheduler::OnGameInitialized(Toast::Handler, Scheduler::Pipeline::RENDERER); Command::Add("testtoast", [](Command::Params*) { diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index 04fa8f22..e725d397 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -178,7 +178,7 @@ namespace Components Utils::Hook(0x48E5D3, Window::DrawCursorStub, HOOK_CALL).install()->quick(); // Draw the cursor if necessary - Scheduler::OnFrame([]() + Scheduler::Loop([] { if (Window::NativeCursor.get() && IsWindow(Window::MainWindow) && GetForegroundWindow() == Window::MainWindow && Window::IsCursorWithin(Window::MainWindow)) { @@ -198,7 +198,7 @@ namespace Components Window::CursorVisible = FALSE; } - }); + }, Scheduler::Pipeline::RENDERER); // Don't let the game interact with the native cursor Utils::Hook::Set(0x6D7348, Window::ShowCursorHook); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 672a6cc0..c91fa939 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -873,10 +873,10 @@ namespace Game typedef void(__cdecl * Sys_FreeFileList_t)(char** list); extern Sys_FreeFileList_t Sys_FreeFileList; - typedef bool(__cdecl * Sys_IsDatabaseReady_t)(); + typedef int(__cdecl * Sys_IsDatabaseReady_t)(); extern Sys_IsDatabaseReady_t Sys_IsDatabaseReady; - typedef bool(__cdecl * Sys_IsDatabaseReady2_t)(); + typedef int(__cdecl * Sys_IsDatabaseReady2_t)(); extern Sys_IsDatabaseReady2_t Sys_IsDatabaseReady2; typedef bool(__cdecl * Sys_IsMainThread_t)(); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index ec9595e3..e1bfeb7a 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -353,7 +353,7 @@ namespace Game const char** argv[8]; }; - static_assert(sizeof(CmdArgs) == 132); + static_assert(sizeof(CmdArgs) == 0x84); typedef struct cmd_function_s { diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 4480f216..37b75112 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -16,7 +16,7 @@ #define VLD_FORCE_ENABLE //#include -#include +#include #include #include #include @@ -29,7 +29,7 @@ #pragma warning(push) #pragma warning(disable: 4091) #pragma warning(disable: 4244) -#include +#include #include #include @@ -45,13 +45,14 @@ #include #include #include +#include #pragma warning(pop) #include #pragma comment(lib, "D3dx9.lib") -#include +#include #pragma comment (lib, "xinput.lib") // Ignore the warnings @@ -63,7 +64,6 @@ #pragma warning(disable: 4389) #pragma warning(disable: 4702) #pragma warning(disable: 4800) -#pragma warning(disable: 4996) // _CRT_SECURE_NO_WARNINGS #pragma warning(disable: 5054) #pragma warning(disable: 6001) #pragma warning(disable: 6011) @@ -74,22 +74,36 @@ #pragma warning(disable: 6387) #pragma warning(disable: 26812) -#include - #include -#include +#include #include #include +#include #include +#include + +// Enable additional literals +using namespace std::literals; #ifdef max -#undef max + #undef max #endif #ifdef min -#undef min + #undef min #endif +#define AssertSize(x, size) \ + static_assert(sizeof(x) == (size), \ + "Structure has an invalid size. " #x " must be " #size " bytes") + +#define AssertOffset(x, y, offset) \ + static_assert(offsetof(x, y) == (offset), \ + #x "::" #y " is not at the right offset. Must be at " #offset) + +#define AssertCount(expr, count) \ + assert((#expr " doesn't index " #count, (expr) < (count))) + // Protobuf #include "proto/session.pb.h" #include "proto/party.pb.h" @@ -101,28 +115,31 @@ #pragma warning(pop) -#include "Utils/IO.hpp" -#include "Utils/CSV.hpp" -#include "Utils/Time.hpp" +#include "Utils/Memory.hpp" // Breaks order on purpose + #include "Utils/Cache.hpp" #include "Utils/Chain.hpp" +#include "Utils/Compression.hpp" +#include "Utils/Concurrency.hpp" +#include "Utils/Cryptography.hpp" +#include "Utils/CSV.hpp" +#include "Utils/Entities.hpp" +#include "Utils/Hooking.hpp" +#include "Utils/InfoString.hpp" +#include "Utils/IO.hpp" +#include "Utils/Library.hpp" +#include "Utils/String.hpp" +#include "Utils/Thread.hpp" +#include "Utils/Time.hpp" #include "Utils/Utils.hpp" #include "Utils/WebIO.hpp" -#include "Utils/Memory.hpp" -#include "Utils/String.hpp" -#include "Utils/Hooking.hpp" -#include "Utils/Library.hpp" -#include "Utils/Entities.hpp" -#include "Utils/InfoString.hpp" -#include "Utils/Compression.hpp" -#include "Utils/Cryptography.hpp" -#include "Steam/Steam.hpp" +#include "Steam/Steam.hpp" // Some definitions are used in functions and structs #include "Game/Structs.hpp" #include "Game/Functions.hpp" -#include "Utils/Stream.hpp" +#include "Utils/Stream.hpp" // Breaks order on purpose #include "Components/Loader.hpp" @@ -139,20 +156,11 @@ #pragma comment(lib, "dbghelp.lib") #pragma comment(lib, "ntdll.lib") -// Enable additional literals -using namespace std::literals; - #endif -#define STRINGIZE_(x) #x -#define STRINGIZE(x) STRINGIZE_(x) - #define BASEGAME "iw4x" #define CLIENT_CONFIG "iw4x_config.cfg" -#define AssertSize(x, size) static_assert(sizeof(x) == size, STRINGIZE(x) " structure has an invalid size.") -#define AssertOffset(x, y, offset) static_assert(offsetof(x, y) == offset, STRINGIZE(x) "::" STRINGIZE(y) " is not at the right offset.") - // Resource stuff #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS diff --git a/src/Utils/Concurrency.hpp b/src/Utils/Concurrency.hpp new file mode 100644 index 00000000..2a4e4ecf --- /dev/null +++ b/src/Utils/Concurrency.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace Utils::Concurrency +{ + template + class Container + { + public: + template + R access(F&& accessor) const + { + std::lock_guard _{mutex_}; + return accessor(object_); + } + + template + R access(F&& accessor) + { + std::lock_guard _{mutex_}; + return accessor(object_); + } + + template + R accessWithLock(F&& accessor) const + { + std::unique_lock lock{mutex_}; + return accessor(object_, lock); + } + + template + R accessWithLock(F&& accessor) + { + std::unique_lock lock{mutex_}; + return accessor(object_, lock); + } + + T& getRaw() {return object_;} + const T& getRaw() const {return object_;} + + private: + mutable MutexType mutex_{}; + T object_{}; + }; +} diff --git a/src/Utils/Library.cpp b/src/Utils/Library.cpp index 1b10bf40..2ff31f9a 100644 --- a/src/Utils/Library.cpp +++ b/src/Utils/Library.cpp @@ -19,14 +19,14 @@ namespace Utils return Library(handle); } - Library::Library(const std::string& name, bool _freeOnDestroy) : _module(nullptr), freeOnDestroy(_freeOnDestroy) + Library::Library(const std::string& name, bool _freeOnDestroy) : module_(nullptr), freeOnDestroy(_freeOnDestroy) { - this->_module = LoadLibraryExA(name.data(), nullptr, 0); + this->module_ = LoadLibraryExA(name.data(), nullptr, 0); } Library::Library(const HMODULE handle) { - this->_module = handle; + this->module_ = handle; this->freeOnDestroy = true; } @@ -38,23 +38,38 @@ namespace Utils } } + bool Library::operator==(const Library& obj) const + { + return this->module_ == obj.module_; + } + + Library::operator bool() const + { + return this->isValid(); + } + + Library::operator HMODULE() const + { + return this->getModule(); + } + bool Library::isValid() const { - return this->_module != nullptr; + return this->module_ != nullptr; } HMODULE Library::getModule() const { - return this->_module; + return this->module_; } void Library::free() { if (this->isValid()) { - FreeLibrary(this->_module); + FreeLibrary(this->module_); } - this->_module = nullptr; + this->module_ = nullptr; } } diff --git a/src/Utils/Library.hpp b/src/Utils/Library.hpp index 3ef0db41..8c999f67 100644 --- a/src/Utils/Library.hpp +++ b/src/Utils/Library.hpp @@ -9,12 +9,18 @@ namespace Utils static Library Load(const std::filesystem::path& path); static Library GetByAddress(void* address); - Library() : _module(nullptr), freeOnDestroy(false) {}; + Library() : module_(nullptr), freeOnDestroy(false) {}; Library(const std::string& name, bool freeOnDestroy); - explicit Library(const std::string& name) : _module(GetModuleHandleA(name.data())), freeOnDestroy(true) {}; + explicit Library(const std::string& name) : module_(GetModuleHandleA(name.data())), freeOnDestroy(true) {}; explicit Library(HMODULE handle); ~Library(); + bool operator!=(const Library& obj) const { return !(*this == obj); } + bool operator==(const Library& obj) const; + + operator bool() const; + operator HMODULE() const; + bool isValid() const; HMODULE getModule() const; @@ -22,7 +28,7 @@ namespace Utils T getProc(const std::string& process) const { if (!this->isValid()) T{}; - return reinterpret_cast(GetProcAddress(this->_module, process.data())); + return reinterpret_cast(GetProcAddress(this->module_, process.data())); } template @@ -59,7 +65,7 @@ namespace Utils void free(); private: - HMODULE _module; + HMODULE module_; bool freeOnDestroy; }; } diff --git a/src/Utils/Memory.hpp b/src/Utils/Memory.hpp index fde99eb7..cc0bb48c 100644 --- a/src/Utils/Memory.hpp +++ b/src/Utils/Memory.hpp @@ -34,7 +34,7 @@ namespace Utils this->refMemory.clear(); - for (auto& data : this->pool) + for (const auto& data : this->pool) { Memory::Free(data); } diff --git a/src/Utils/Thread.cpp b/src/Utils/Thread.cpp new file mode 100644 index 00000000..e328ff30 --- /dev/null +++ b/src/Utils/Thread.cpp @@ -0,0 +1,122 @@ +#include + +namespace Utils::Thread +{ + bool setName(const HANDLE t, const std::string& name) + { + const Library kernel32("kernel32.dll"); + if (!kernel32) + { + return false; + } + + const auto setDescription = kernel32.getProc("SetThreadDescription"); + if (!setDescription) + { + return false; + } + + return SUCCEEDED(setDescription(t, String::Convert(name).data())); + } + + bool setName(const DWORD id, const std::string& name) + { + auto* const t = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, id); + if (!t) return false; + + const auto _ = gsl::finally([t]() + { + CloseHandle(t); + }); + + return setName(t, name); + } + + bool setName(std::thread& t, const std::string& name) + { + return setName(t.native_handle(), name); + } + + bool setName(const std::string& name) + { + return setName(GetCurrentThread(), name); + } + + std::vector getThreadIds() + { + auto* const h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId()); + if (h == INVALID_HANDLE_VALUE) + { + return {}; + } + + const auto _ = gsl::finally([h]() + { + CloseHandle(h); + }); + + THREADENTRY32 entry{}; + entry.dwSize = sizeof(entry); + if (!Thread32First(h, &entry)) + { + return {}; + } + + std::vector ids{}; + + do + { + const auto check_size = entry.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + + sizeof(entry.th32OwnerProcessID); + entry.dwSize = sizeof(entry); + + if (check_size && entry.th32OwnerProcessID == GetCurrentProcessId()) + { + ids.emplace_back(entry.th32ThreadID); + } + } while (Thread32Next(h, &entry)); + + return ids; + } + + void forEachThread(const std::function& callback) + { + const auto ids = getThreadIds(); + + for (const auto& id : ids) + { + auto* const thread = OpenThread(THREAD_ALL_ACCESS, FALSE, id); + if (thread != nullptr) + { + const auto _ = gsl::finally([thread]() + { + CloseHandle(thread); + }); + + callback(thread); + } + } + } + + void suspendOtherThreads() + { + forEachThread([](const HANDLE thread) + { + if (GetThreadId(thread) != GetCurrentThreadId()) + { + SuspendThread(thread); + } + }); + } + + void resumeOtherThreads() + { + forEachThread([](const HANDLE thread) + { + if (GetThreadId(thread) != GetCurrentThreadId()) + { + ResumeThread(thread); + } + }); + } +} diff --git a/src/Utils/Thread.hpp b/src/Utils/Thread.hpp new file mode 100644 index 00000000..7b6a1314 --- /dev/null +++ b/src/Utils/Thread.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace Utils::Thread +{ + bool setName(HANDLE t, const std::string& name); + bool setName(DWORD id, const std::string& name); + bool setName(std::thread& t, const std::string& name); + bool setName(const std::string& name); + + template + std::thread createNamedThread(const std::string& name, Args&&... args) + { + auto t = std::thread(std::forward(args)...); + setName(t, name); + return t; + } + + std::vector getThreadIds(); + void forEachThread(const std::function& callback); + + void suspendOtherThreads(); + void resumeOtherThreads(); +} \ No newline at end of file