diff --git a/PolygonClientUtilities/Debug/PolygonClientUtilities.dll.recipe b/PolygonClientUtilities/Debug/PolygonClientUtilities.dll.recipe new file mode 100644 index 0000000..16858d3 --- /dev/null +++ b/PolygonClientUtilities/Debug/PolygonClientUtilities.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\polygon-git\PolygonDLLUtilities\Debug\PolygonClientUtilities.dll + + + + + + \ No newline at end of file diff --git a/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk b/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk new file mode 100644 index 0000000..91fccf3 Binary files /dev/null and b/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk differ diff --git a/PolygonClientUtilities/Patches.cpp b/PolygonClientUtilities/Patches.cpp new file mode 100644 index 0000000..f038399 --- /dev/null +++ b/PolygonClientUtilities/Patches.cpp @@ -0,0 +1,14 @@ +#include "pch.h" +#include "Patches.h" + +#include + +LONG Patches::Apply() +{ + DetourTransactionBegin(); + + for (Patch patch : patchList) + DetourAttach(&(PVOID&)*patch.first, patch.second); + + return DetourTransactionCommit(); +} \ No newline at end of file diff --git a/PolygonClientUtilities/Patches.h b/PolygonClientUtilities/Patches.h new file mode 100644 index 0000000..45f7bb3 --- /dev/null +++ b/PolygonClientUtilities/Patches.h @@ -0,0 +1,14 @@ +#pragma once + +namespace Patches +{ + typedef std::pair Patch; + + extern std::vector patchList; + + LONG Apply(); +} + +#define START_PATCH_LIST() std::vector Patches::patchList = { +#define ADD_PATCH(a, b) { (void**)&(a), (b) }, +#define END_PATCH_LIST() }; \ No newline at end of file diff --git a/PolygonClientUtilities/PolygonClientUtilities.vcxproj b/PolygonClientUtilities/PolygonClientUtilities.vcxproj index 1a72029..8aff6b4 100644 --- a/PolygonClientUtilities/PolygonClientUtilities.vcxproj +++ b/PolygonClientUtilities/PolygonClientUtilities.vcxproj @@ -90,11 +90,14 @@ true Use pch.h + $(SolutionDir)Detours\include Windows true false + $(SolutionDir)Detours\lib.X86;%(AdditionalLibraryDirectories) + detours.lib;%(AdditionalDependencies) @@ -107,6 +110,7 @@ true Use pch.h + $(SolutionDir)Detours\include Windows @@ -114,6 +118,8 @@ true true false + $(SolutionDir)Detours\lib.X86;%(AdditionalLibraryDirectories) + detours.lib;%(AdditionalDependencies) @@ -151,17 +157,21 @@ - + + + + Create Create Create Create + diff --git a/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters b/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters index 1e57c7b..0a546db 100644 --- a/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters +++ b/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters @@ -15,10 +15,16 @@ - + Header Files - + + Header Files + + + Header Files + + Header Files @@ -29,5 +35,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/PolygonClientUtilities/RobloxMFCClasses.h b/PolygonClientUtilities/RobloxMFCClasses.h new file mode 100644 index 0000000..1d4eebc --- /dev/null +++ b/PolygonClientUtilities/RobloxMFCClasses.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +// CWorkspace +// 2010: 0x0047EC10 +// 2011: 0x0049FC90 + +class CWorkspace; + +const auto CWorkspace__ExecUrlScript = (HRESULT(__stdcall*)(CWorkspace * workspace, LPCWSTR, VARIANTARG, VARIANTARG, VARIANTARG, VARIANTARG, LPVOID))0x0047EC10; + +// CRobloxDoc + +class CRobloxDoc +{ +private: + void* padding1[40]; +public: + CWorkspace* workspace; +}; + +// CRobloxApp +// 2010: 0x0044F6E0 +// 2011: 0x0045D030 + +class CRobloxApp; + +const auto CRobloxApp__CreateDocument = (CRobloxDoc * (__thiscall*)(CRobloxApp * _this))0x0044F6E0; +// const auto CRobloxApp__CreateGame = (CWorkspace * (__thiscall*)(CRobloxApp * _this, LPCWSTR))0x00405D20; // is CApp the same thing as CRobloxApp?? + +// CRobloxCommandLineInfo +// 2010: 0x007A80A0 +// 2011: 0x0081354A + +class CCommandLineInfo +{ +private: + void* padding1[3]; +public: + BOOL m_bRunAutomated; +}; + +class CRobloxCommandLineInfo : public CCommandLineInfo {}; + +const auto CCommandLineInfo__ParseLast = (void(__thiscall*)(CCommandLineInfo * _this, BOOL bLast))0x007A80A0; \ No newline at end of file diff --git a/PolygonClientUtilities/RobloxMFCHooks.cpp b/PolygonClientUtilities/RobloxMFCHooks.cpp new file mode 100644 index 0000000..0c4925e --- /dev/null +++ b/PolygonClientUtilities/RobloxMFCHooks.cpp @@ -0,0 +1,120 @@ +#include "pch.h" +#include "RobloxMFCHooks.h" + +static HANDLE handle; +static std::ofstream jobLog; + +static bool hasJoinArg = false; +static bool hasJobId = false; + +static std::wstring joinScriptUrl; +static std::string jobId; + +// 2010: 0x00452900; +// 2011: 0x004613C0; + +CRobloxApp__InitInstance_t CRobloxApp__InitInstance = (CRobloxApp__InitInstance_t)0x00452900; + +BOOL __fastcall CRobloxApp__InitInstance_hook(CRobloxApp* _this) +{ + if (!CRobloxApp__InitInstance(_this)) + return FALSE; + + if (hasJoinArg && !joinScriptUrl.empty()) + { + try + { + CRobloxDoc* document = CRobloxApp__CreateDocument(_this); + CWorkspace__ExecUrlScript(document->workspace, joinScriptUrl.c_str(), VARIANTARG(), VARIANTARG(), VARIANTARG(), VARIANTARG(), nullptr); + } + catch (std::runtime_error& exception) + { + MessageBoxA(nullptr, exception.what(), nullptr, MB_ICONERROR); + return FALSE; + } + } + + return TRUE; +} + +// 2010: 0x00450AC0; +// 2011: 0x0045EE50; + +CRobloxCommandLineInfo__ParseParam_t CRobloxCommandLineInfo__ParseParam = (CRobloxCommandLineInfo__ParseParam_t)0x00450AC0; + +void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* _this, void*, const char* pszParam, BOOL bFlag, BOOL bLast) +{ + if (hasJoinArg && joinScriptUrl.empty()) + { + int size = MultiByteToWideChar(CP_ACP, 0, pszParam, strlen(pszParam), nullptr, 0); + joinScriptUrl.resize(size); + MultiByteToWideChar(CP_ACP, 0, pszParam, strlen(pszParam), &joinScriptUrl[0], size); + + _this->m_bRunAutomated = TRUE; + + CCommandLineInfo__ParseLast(_this, bLast); + return; + } + + if (hasJobId && jobId.empty()) + { + jobId = std::string(pszParam); + jobLog = std::ofstream(jobId + std::string(".txt")); + + AllocConsole(); + freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); + handle = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + SetStdHandle(STD_OUTPUT_HANDLE, handle); + + CCommandLineInfo__ParseLast(_this, bLast); + return; + } + + if (bFlag && _stricmp(pszParam, "j") == 0) + { + hasJoinArg = true; + CCommandLineInfo__ParseLast(_this, bLast); + return; + } + + if (bFlag && _stricmp(pszParam, "jobId") == 0) + { + hasJobId = true; + CCommandLineInfo__ParseLast(_this, bLast); + return; + } + + CRobloxCommandLineInfo__ParseParam(_this, pszParam, bFlag, bLast); +} + +// 2010: 0x0059F340; +// 2011: 0x005B25E0; + +StandardOut__print_t StandardOut__print = (StandardOut__print_t)0x0059F340; + +void __fastcall StandardOut__print_hook(void* _this, void*, int type, const std::string& message) +{ + switch (type) + { + case 1: // RBX::MESSAGE_OUTPUT: + jobLog << "[RBX::MESSAGE_OUTPUT] " << message.c_str() << std::endl; + SetConsoleTextAttribute(handle, FOREGROUND_BLUE | FOREGROUND_INTENSITY); + break; + case 0: // RBX::MESSAGE_INFO: + jobLog << "[RBX::MESSAGE_INFO] " << message.c_str() << std::endl; + SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + break; + case 2: // RBX::MESSAGE_WARNING: + jobLog << "[RBX::MESSAGE_WARNING] " << message.c_str() << std::endl; + SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN); + break; + case 3: // RBX::MESSAGE_ERROR: + jobLog << "[RBX::MESSAGE_ERROR] " << message.c_str() << std::endl; + SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_INTENSITY); + break; + } + printf("%s\n", message.c_str()); + SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + + StandardOut__print(_this, type, message); +} \ No newline at end of file diff --git a/PolygonClientUtilities/RobloxMFCHooks.h b/PolygonClientUtilities/RobloxMFCHooks.h new file mode 100644 index 0000000..3833b47 --- /dev/null +++ b/PolygonClientUtilities/RobloxMFCHooks.h @@ -0,0 +1,24 @@ +#pragma once + +#include "RobloxMFCClasses.h" + +// CRobloxApp + +typedef BOOL(__thiscall* CRobloxApp__InitInstance_t)(CRobloxApp* _this); +extern CRobloxApp__InitInstance_t CRobloxApp__InitInstance; + +BOOL __fastcall CRobloxApp__InitInstance_hook(CRobloxApp* _this); + +// CRobloxCommandLineInfo + +typedef void(__thiscall* CRobloxCommandLineInfo__ParseParam_t)(CRobloxCommandLineInfo* _this, const char* pszParam, BOOL bFlag, BOOL bLast); +extern CRobloxCommandLineInfo__ParseParam_t CRobloxCommandLineInfo__ParseParam; + +void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* _this, void*, const char* pszParam, BOOL bFlag, BOOL bLast); + +// StandardOut + +typedef void(__thiscall* StandardOut__print_t)(void* _this, int type, const std::string& message); +extern StandardOut__print_t StandardOut__print; + +void __fastcall StandardOut__print_hook(void* _this, void*, int type, const std::string& message); \ No newline at end of file diff --git a/PolygonClientUtilities/dllmain.cpp b/PolygonClientUtilities/dllmain.cpp index f266597..dc4d338 100644 --- a/PolygonClientUtilities/dllmain.cpp +++ b/PolygonClientUtilities/dllmain.cpp @@ -1,19 +1,31 @@ -// dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" +#include "Patches.h" +#include "RobloxMFCHooks.h" -BOOL APIENTRY DllMain( HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) +START_PATCH_LIST() +ADD_PATCH(CRobloxApp__InitInstance, CRobloxApp__InitInstance_hook) +ADD_PATCH(CRobloxCommandLineInfo__ParseParam, CRobloxCommandLineInfo__ParseParam_hook) +ADD_PATCH(StandardOut__print, StandardOut__print_hook) +END_PATCH_LIST() + +// To be added to game imports +void __declspec(dllexport) doNothing() {} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { - switch (ul_reason_for_call) + if (ul_reason_for_call == DLL_PROCESS_ATTACH) { - case DLL_PROCESS_ATTACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} + LONG error = Patches::Apply(); + if (error != NO_ERROR) + { +#ifdef _DEBUG + std::string message = "Patches::Apply returned " + std::to_string(error); + MessageBoxA(nullptr, message.c_str(), nullptr, MB_ICONERROR); +#endif + ExitProcess(EXIT_FAILURE); + } + } + + return TRUE; +} \ No newline at end of file diff --git a/PolygonClientUtilities/pch.cpp b/PolygonClientUtilities/pch.cpp index 64b7eef..1730571 100644 --- a/PolygonClientUtilities/pch.cpp +++ b/PolygonClientUtilities/pch.cpp @@ -1,5 +1 @@ -// pch.cpp: source file corresponding to the pre-compiled header - -#include "pch.h" - -// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. +#include "pch.h" \ No newline at end of file diff --git a/PolygonClientUtilities/pch.h b/PolygonClientUtilities/pch.h index 885d5d6..ec322c5 100644 --- a/PolygonClientUtilities/pch.h +++ b/PolygonClientUtilities/pch.h @@ -1,13 +1,8 @@ -// pch.h: This is a precompiled header file. -// Files listed below are compiled only once, improving build performance for future builds. -// This also affects IntelliSense performance, including code completion and many code browsing features. -// However, files listed here are ALL re-compiled if any one of them is updated between builds. -// Do not add files here that you will be updating frequently as this negates the performance advantage. +#pragma once -#ifndef PCH_H -#define PCH_H - -// add headers that you want to pre-compile here -#include "framework.h" - -#endif //PCH_H +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/README.md b/README.md index 231a59f..6cddcee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # PolygonDLLUtilities Manages DLLs for extending game client/server functionality +Based off [ndoesstuff/JoinScriptUrlImpl](https://github.com/ndoesstuff/JoinScriptUrlImpl) as per the MIT license \ No newline at end of file