diff --git a/src/Components/Modules/Scheduler.cpp b/src/Components/Modules/Scheduler.cpp index 500b4b7..6c94f28 100644 --- a/src/Components/Modules/Scheduler.cpp +++ b/src/Components/Modules/Scheduler.cpp @@ -3,12 +3,162 @@ namespace Components { + bool Scheduler::AsyncTerminate; + std::thread Scheduler::AsyncThread; + + 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) + { + if (clientOnly && Game::IsDedicated()) 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 && Game::IsDedicated()) return; + Scheduler::FrameSignal.connect(callback); + } + + void Scheduler::OnReady(Utils::Slot callback, bool clientOnly) + { + if (clientOnly && Game::IsDedicated()) return; + if (Scheduler::ReadyPassed) Scheduler::Once(callback); + else Scheduler::ReadySignal.connect(callback); + } + + void Scheduler::ReadyHandler() + { + if (!false) + { + Scheduler::Once(Scheduler::ReadyHandler); + } + else + { + Scheduler::ReadyPassed = true; + Scheduler::ReadySignal(); + Scheduler::ReadySignal.clear(); + } + } + + 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 && Game::IsDedicated()) 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)) + { + signal.connect(i->callback); + i = Scheduler::DelayedSlots.erase(i); + continue; + } + + ++i; + } + + signal(); + } + + void Scheduler::ShutdownStub(int num) + { + 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); + } + Scheduler::Scheduler() { - //MessageBoxA(nullptr, Utils::String::VA("%d", Game::svs), "DEBUG", 0); + Scheduler::ReadyPassed = false; + Scheduler::Once(Scheduler::ReadyHandler); + + // hook frames, + //Utils::Hook(0x4D697A, Scheduler::ShutdownStub, HOOK_CALL).install()->quick(); + + if (!Loader::IsPerformingUnitTests()) + { + Scheduler::AsyncTerminate = false; + Scheduler::AsyncThread = std::thread([]() + { + while (!Scheduler::AsyncTerminate) + { + Scheduler::AsyncFrameSignal(); + + Utils::Signal copy(Scheduler::AsyncFrameOnceSignal); + Scheduler::AsyncFrameOnceSignal.clear(); + copy(); + + std::this_thread::sleep_for(16ms); + } + }); + } } Scheduler::~Scheduler() { + Scheduler::ReadySignal.clear(); + Scheduler::ShutdownSignal.clear(); + + Scheduler::FrameSignal.clear(); + Scheduler::FrameOnceSignal.clear(); + Scheduler::DelayedSlots.clear(); + + Scheduler::AsyncFrameSignal.clear(); + Scheduler::AsyncFrameOnceSignal.clear(); + + Scheduler::ReadyPassed = false; + } + + void Scheduler::preDestroy() + { + Scheduler::AsyncTerminate = true; + if (Scheduler::AsyncThread.joinable()) + { + Scheduler::AsyncThread.join(); + } } } diff --git a/src/Components/Modules/Scheduler.hpp b/src/Components/Modules/Scheduler.hpp index 9e17541..7b187b8 100644 --- a/src/Components/Modules/Scheduler.hpp +++ b/src/Components/Modules/Scheduler.hpp @@ -5,7 +5,50 @@ namespace Components class Scheduler : public Component { public: + typedef void(Callback)(); + 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(); + + private: + class DelayedSlot + { + public: + std::chrono::nanoseconds delay; + Utils::Time::Interval interval; + Utils::Slot callback; + }; + + static bool AsyncTerminate; + static std::thread AsyncThread; + + static Utils::Signal FrameSignal; + static Utils::Signal FrameOnceSignal; + static std::vector DelayedSlots; + + static bool ReadyPassed; + static Utils::Signal ReadySignal; + static Utils::Signal ShutdownSignal; + + static Utils::Signal AsyncFrameSignal; + static Utils::Signal AsyncFrameOnceSignal; + + static void ReadyHandler(); + static void DelaySignal(); + + static void ShutdownStub(int num); }; } diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index f74c8a3..351d963 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -33,6 +33,12 @@ namespace Game Player_GetMethod_t* Player_GetMethod; GScr_LoadGameTypeScript_t* GScr_LoadGameTypeScript; G_LoadStructs_t* G_LoadStructs; + Sys_Milliseconds_t* Sys_Milliseconds; + + bool IsDedicated() + { + return false; + } void Init(GAMEEXE) { @@ -41,6 +47,7 @@ namespace Game Scr_LoadScript = ASSIGN(Scr_LoadScript_t*, 0x474D80); GScr_LoadGameTypeScript = ASSIGN(GScr_LoadGameTypeScript_t*, 0x503F90); G_LoadStructs = ASSIGN(G_LoadStructs_t*, 0x5118A0); + Sys_Milliseconds = ASSIGN(Sys_Milliseconds_t*, 0x435200); cls = ASSIGN(clientStatic_t*, 0x68A408); diff --git a/src/Game/Game.hpp b/src/Game/Game.hpp index 0288d1e..cf5a72c 100644 --- a/src/Game/Game.hpp +++ b/src/Game/Game.hpp @@ -6,6 +6,7 @@ namespace Game { void Init(GAMEEXE); + bool IsDedicated(); extern clientStatic_t* cls; extern serverStatic_t* svs; @@ -42,6 +43,9 @@ namespace Game typedef void (G_LoadStructs_t)(); extern G_LoadStructs_t* G_LoadStructs; + typedef int (Sys_Milliseconds_t)(); + extern Sys_Milliseconds_t* Sys_Milliseconds; + extern unsigned int Scr_GetFunctionHandle(char*, const char*); extern __int16 Scr_ExecThread(int); extern void RemoveRefToObject(int); diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index d06bcd6..646b591 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -90,6 +90,8 @@ enum GAMEEXE #include "Utils/Hooking.hpp" #include "Utils/Memory.hpp" #include "Utils/String.hpp" +#include "Utils/Time.hpp" +#include "Utils/Utils.hpp" #include "Game/Structs.hpp" #include "Game/Game.hpp" diff --git a/src/Utils/Time.cpp b/src/Utils/Time.cpp new file mode 100644 index 0000000..4761f43 --- /dev/null +++ b/src/Utils/Time.cpp @@ -0,0 +1,42 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + namespace Time + { + void Interval::update() + { + this->lastPoint = std::chrono::high_resolution_clock::now(); + } + + bool Interval::elapsed(std::chrono::nanoseconds nsecs) + { + return ((std::chrono::high_resolution_clock::now() - this->lastPoint) >= nsecs); + } + + Point::Point() : lastPoint(Game::Sys_Milliseconds()) + { + + } + + void Point::update() + { + this->lastPoint = Game::Sys_Milliseconds(); + } + + int Point::diff(Point point) + { + return point.lastPoint - this->lastPoint; + } + + bool Point::after(Point point) + { + return this->diff(point) < 0; + } + + bool Point::elapsed(int milliseconds) + { + return (Game::Sys_Milliseconds() - this->lastPoint) >= milliseconds; + } + } +} diff --git a/src/Utils/Time.hpp b/src/Utils/Time.hpp new file mode 100644 index 0000000..d26763a --- /dev/null +++ b/src/Utils/Time.hpp @@ -0,0 +1,33 @@ +#pragma once + +namespace Utils +{ + namespace Time + { + class Interval + { + protected: + std::chrono::high_resolution_clock::time_point lastPoint; + + public: + Interval() : lastPoint(std::chrono::high_resolution_clock::now()) {} + + void update(); + bool elapsed(std::chrono::nanoseconds nsecs); + }; + + class Point + { + public: + Point(); + + void update(); + int diff(Point point); + bool after(Point point); + bool elapsed(int milliseconds); + + private: + int lastPoint; + }; + } +} diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp new file mode 100644 index 0000000..e569ea1 --- /dev/null +++ b/src/Utils/Utils.cpp @@ -0,0 +1,148 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + std::string GetMimeType(const std::string& url) + { + wchar_t* mimeType = nullptr; + FindMimeFromData(nullptr, std::wstring(url.begin(), url.end()).data(), nullptr, 0, nullptr, 0, &mimeType, 0); + + if (mimeType) + { + std::wstring wMimeType(mimeType); + return std::string(wMimeType.begin(), wMimeType.end()); + } + + return "application/octet-stream"; + } + + std::string ParseChallenge(const std::string& data) + { + auto pos = data.find_first_of("\n "); + if (pos == std::string::npos) return data; + return data.substr(0, pos).data(); + } + + void OutputDebugLastError() + { + DWORD errorMessageID = ::GetLastError(); + OutputDebugStringA(Utils::String::VA("Last error code: 0x%08X (%s)\n", errorMessageID, GetLastWindowsError().data())); + } + + std::string GetLastWindowsError() + { + DWORD errorMessageID = ::GetLastError(); + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&messageBuffer), 0, nullptr); + std::string message(messageBuffer, size); + LocalFree(messageBuffer); + return message; + } + + bool IsWineEnvironment() + { + HMODULE hntdll = GetModuleHandleA("ntdll.dll"); + if (!hntdll) return false; + + return (GetProcAddress(hntdll, "wine_get_version") != nullptr); + } + + unsigned long GetParentProcessId() + { + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) return 0; + + Utils::Memory::Allocator allocator; + allocator.reference(hSnapshot, [](void* handle) { CloseHandle(handle); }); + + PROCESSENTRY32 pe32; + ZeroMemory(&pe32, sizeof(pe32)); + pe32.dwSize = sizeof(pe32); + + DWORD pid = GetCurrentProcessId(); + while (Process32Next(hSnapshot, &pe32)) + { + if (pe32.th32ProcessID == pid) + { + return pe32.th32ParentProcessID; + } + } + + return 0; + } + + size_t GetModuleSize(HMODULE module) + { + PIMAGE_DOS_HEADER header = PIMAGE_DOS_HEADER(module); + PIMAGE_NT_HEADERS ntHeader = PIMAGE_NT_HEADERS(DWORD(module) + header->e_lfanew); + return ntHeader->OptionalHeader.SizeOfImage; + } + + void* GetThreadStartAddress(HANDLE hThread) + { + HMODULE ntdll = Utils::GetNTDLL(); + if (!ntdll) return nullptr; + + + static uint8_t ntQueryInformationThread[] = { 0xB1, 0x8B, 0xAE, 0x8A, 0x9A, 0x8D, 0x86, 0xB6, 0x91, 0x99, 0x90, 0x8D, 0x92, 0x9E, 0x8B, 0x96, 0x90, 0x91, 0xAB, 0x97, 0x8D, 0x9A, 0x9E, 0x9B }; // NtQueryInformationThread + NtQueryInformationThread_t NtQueryInformationThread = NtQueryInformationThread_t(GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ntQueryInformationThread), sizeof ntQueryInformationThread), -1).data())); + if (!NtQueryInformationThread) return nullptr; + + HANDLE dupHandle, currentProcess = GetCurrentProcess(); + if (!DuplicateHandle(currentProcess, hThread, currentProcess, &dupHandle, THREAD_QUERY_INFORMATION, FALSE, 0)) + { + SetLastError(ERROR_ACCESS_DENIED); + return nullptr; + } + + void* address = nullptr; + NTSTATUS status = NtQueryInformationThread(dupHandle, ThreadQuerySetWin32StartAddress, &address, sizeof(address), nullptr); + CloseHandle(dupHandle); + + if (status != 0) return nullptr; + return address; + } + + void SetEnvironment() + { + wchar_t exeName[512]; + GetModuleFileName(GetModuleHandle(nullptr), exeName, sizeof(exeName) / 2); + + wchar_t* exeBaseName = wcsrchr(exeName, L'\\'); + exeBaseName[0] = L'\0'; + + SetCurrentDirectory(exeName); + } + + HMODULE GetNTDLL() + { + static uint8_t ntdll[] = { 0x91, 0x8B, 0x9B, 0x93, 0x93, 0xD1, 0x9B, 0x93, 0x93 }; // ntdll.dll + return GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(ntdll), sizeof ntdll), -1).data()); + } + + void SafeShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd) + { + [=]() + { + __try + { + ShellExecuteA(hwnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd); + } + __finally + {} + }(); + + std::this_thread::yield(); + } + + void OpenUrl(const std::string& url) + { + SafeShellExecute(nullptr, "open", url.data(), nullptr, nullptr, SW_SHOWNORMAL); + } + + bool HasIntercection(unsigned int base1, unsigned int len1, unsigned int base2, unsigned int len2) + { + return !(base1 + len1 <= base2 || base2 + len2 <= base1); + } +} diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp new file mode 100644 index 0000000..f296f0e --- /dev/null +++ b/src/Utils/Utils.hpp @@ -0,0 +1,129 @@ +#pragma once + +typedef LONG NTSTATUS; +typedef NTSTATUS(NTAPI *NtCreateThreadEx_t)(PHANDLE hThread, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD StackZeroBits, DWORD SizeOfStackCommit, DWORD SizeOfStackReserve, LPVOID lpBytesBuffer); +typedef NTSTATUS(NTAPI* NtQueryInformationThread_t)(HANDLE ThreadHandle, LONG ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength); +#define ThreadQuerySetWin32StartAddress 9 + +namespace Utils +{ + std::string GetMimeType(const std::string& url); + std::string ParseChallenge(const std::string& data); + void OutputDebugLastError(); + std::string GetLastWindowsError(); + + bool IsWineEnvironment(); + + unsigned long GetParentProcessId(); + size_t GetModuleSize(HMODULE); + void* GetThreadStartAddress(HANDLE hThread); + HMODULE GetNTDLL(); + + void SetEnvironment(); + + void OpenUrl(const std::string& url); + + bool HasIntercection(unsigned int base1, unsigned int len1, unsigned int base2, unsigned int len2); + + template inline void RotLeft(T& object, size_t bits) + { + bits %= sizeof(T) * 8; + + T sign = 1; + sign = sign << (sizeof(T) * 8 - 1); + + bool negative = (object & sign) != 0; + object &= ~sign; + object = (object << bits) | (object >> (sizeof(T) * 8 - bits)); + object |= T(negative) << ((sizeof(T) * 8 - 1 + bits) % (sizeof(T) * 8)); + } + + template inline void RotRight(T& object, size_t bits) + { + bits %= (sizeof(T) * 8); + RotLeft(object, ((sizeof(T) * 8) - bits)); + } + + template inline void Merge(std::vector* target, T* source, size_t length) + { + if (source) + { + for (size_t i = 0; i < length; ++i) + { + target->push_back(source[i]); + } + } + } + + template inline void Merge(std::vector* target, std::vector source) + { + for (auto &entry : source) + { + target->push_back(entry); + } + } + + template using Slot = std::function; + template + class Signal + { + public: + Signal() + { + std::lock_guard _(this->mutex); + + this->slots.clear(); + } + + Signal(Signal& obj) : Signal() + { + std::lock_guard _(this->mutex); + std::lock_guard __(obj.mutex); + + Utils::Merge(&this->slots, obj.getSlots()); + } + + void connect(Slot slot) + { + std::lock_guard _(this->mutex); + + if (slot) + { + this->slots.push_back(slot); + } + } + + void clear() + { + std::lock_guard _(this->mutex); + + this->slots.clear(); + } + + std::vector>& getSlots() + { + return this->slots; + } + + template + void operator()(Args&&... args) const + { + std::lock_guard _(this->mutex); + + std::vector> copiedSlots; + Utils::Merge(&copiedSlots, this->slots); + + for (auto& slot : copiedSlots) + { + if (slot) + { + slot(std::forward(args)...); + } + } + } + + private: + mutable std::recursive_mutex mutex; + std::vector> slots; + }; +}