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