diff --git a/Kiseki.Patcher/CMakeLists.txt b/Kiseki.Patcher/CMakeLists.txt index 2b48ff7..ca03b3b 100644 --- a/Kiseki.Patcher/CMakeLists.txt +++ b/Kiseki.Patcher/CMakeLists.txt @@ -33,9 +33,22 @@ if(COMPILE_PLAYER OR COMPILE_SERVER) endif() if(COMPILE_SERVER) - # Hook ServerReplicator - list(APPEND SOURCE Source/Hooks/ServerReplicator.cpp) - list(APPEND HEADER Header/Hooks/ServerReplicator.hpp) + # Hook DataModel, ServerReplicator, and StandardOut as well as include our custom server interface + list(APPEND SOURCE + Source/Server.cpp + + Source/Hooks/DataModel.cpp + Source/Hooks/ServerReplicator.cpp + Source/Hooks/StandardOut.cpp + ) + + list(APPEND HEADER + Header/Server.hpp + + Header/Hooks/DataModel.hpp + Header/Hooks/ServerReplicator.hpp + Header/Hooks/StandardOut.hpp + ) endif() endif() diff --git a/Kiseki.Patcher/Header/Globals.hpp b/Kiseki.Patcher/Header/Globals.hpp index aad9f74..7c0de98 100644 --- a/Kiseki.Patcher/Header/Globals.hpp +++ b/Kiseki.Patcher/Header/Globals.hpp @@ -7,9 +7,19 @@ #define PUBLIC_KEY 0x06, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xA5, 0x11, 0xD0, 0x9F, 0xAB, 0x9B, 0x3A, 0x96, 0xC5, 0xBE, 0x50, 0xBB, 0xCA, 0x0C, 0xBB, 0xC8, 0x1A, 0x9C, 0xC1, 0x2F, 0x22, 0x7A, 0x80, 0x2C, 0x31, 0x01, 0xE1, 0x21, 0xC2, 0x7F, 0xE0, 0x10, 0xA1, 0x2D, 0x09, 0xED, 0x10, 0x3E, 0x28, 0xB9, 0xBD, 0x0F, 0x38, 0xDB, 0x52, 0x78, 0xC0, 0xEC, 0x04, 0xD4, 0x00, 0xAD, 0x45, 0xD3, 0xC7, 0x78, 0xF2, 0x83, 0xB5, 0x5B, 0x16, 0x0C, 0x32, 0x5D, 0xB3, 0x3B, 0xDA, 0xA2, 0x9C, 0x73, 0xB2, 0x0C, 0x09, 0x93, 0xD8, 0xF8, 0xD9, 0xC5, 0x75, 0xAB, 0x33, 0x19, 0xD3, 0x6A, 0xAF, 0x20, 0x21, 0x6C, 0x78, 0x31, 0x41, 0xD1, 0xCD, 0x6D, 0x4D, 0xA1, 0x6D, 0x49, 0x3A, 0x6A, 0x33, 0x10, 0x6D, 0x52, 0x33, 0x4A, 0x10, 0x32, 0x3D, 0x42, 0xE6, 0xEC, 0x38, 0x97, 0x2F, 0x05, 0x41, 0xDD, 0xD6, 0xB6, 0xAC, 0x17, 0x4B, 0x7E, 0xFA, 0xFE, 0xA4, 0xD2 +#define RBX__MESSAGE_INFO 0 +#define RBX__MESSAGE_OUTPUT 1 +#define RBX__MESSAGE_WARNING 2 +#define RBX__MESSAGE_ERROR 3 + +#define CLASSPADDING_DATAMODEL__JOBID 739 + #define ADDRESS_HTTP__HTTPGETPOSTWININET 0x006F03B0 #define ADDRESS_HTTP__TRUSTCHECK 0x005B7050 #define ADDRESS_CRYPT__VERIFYSIGNATUREBASE64 0x00809EC0 +#define ADDRESS_DATAMODEL__GETJOBID 0x005E70C0 +#define ADDRESS_CRYPT__VERIFYSIGNATUREBASE64 0x00809EC0 +#define ADDRESS_STANDARDOUT__PRINT 0x005B25E0 #define ADDRESS_SERVERREPLICATOR__SENDTOP 0x00513E80 #define ADDRESS_SERVERREPLICATOR__PROCESSTICKET 0x00514B60 diff --git a/Kiseki.Patcher/Header/Hooks/CRoblox.hpp b/Kiseki.Patcher/Header/Hooks/CRoblox.hpp index 6038f7a..fa26d1e 100644 --- a/Kiseki.Patcher/Header/Hooks/CRoblox.hpp +++ b/Kiseki.Patcher/Header/Hooks/CRoblox.hpp @@ -7,6 +7,10 @@ #include "Globals.hpp" #include "Helpers.hpp" +#ifdef SERVER +#include "Server.hpp" +#endif + #ifdef PLAYER #include "Discord.hpp" #endif @@ -48,4 +52,9 @@ void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* extern CRobloxApp__InitInstance_t CRobloxApp__InitInstance; extern CRobloxCommandLineInfo__ParseParam_t CRobloxCommandLineInfo__ParseParam; +#ifdef SERVER +extern std::wstring jobId; +extern bool hasJobId; +#endif + #endif \ No newline at end of file diff --git a/Kiseki.Patcher/Header/Hooks/DataModel.hpp b/Kiseki.Patcher/Header/Hooks/DataModel.hpp new file mode 100644 index 0000000..74ce905 --- /dev/null +++ b/Kiseki.Patcher/Header/Hooks/DataModel.hpp @@ -0,0 +1,20 @@ +#ifdef SERVER + +#pragma once + +#include "Hooks/CRoblox.hpp" + +#include "Globals.hpp" +#include "Helpers.hpp" + +struct DataModel +{ + void* padding1[CLASSPADDING_DATAMODEL__JOBID]; + std::string jobId; +}; + +typedef INT(__thiscall* DataModel__getJobId_t)(DataModel* _this, int a2); +int __fastcall DataModel__getJobId_hook(DataModel* _this, void*, int a2); +extern DataModel__getJobId_t DataModel__getJobId; + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Header/Hooks/StandardOut.hpp b/Kiseki.Patcher/Header/Hooks/StandardOut.hpp new file mode 100644 index 0000000..09ad762 --- /dev/null +++ b/Kiseki.Patcher/Header/Hooks/StandardOut.hpp @@ -0,0 +1,14 @@ +#ifdef SERVER + +#pragma once + +#include "Globals.hpp" +#include "Helpers.hpp" +#include "Patcher.hpp" +#include "Server.hpp" + +typedef void(__thiscall* StandardOut__print_t)(int _this, int type, std::string* message); +void __fastcall StandardOut__print_hook(int _this, void*, int type, std::string* message); +extern StandardOut__print_t StandardOut__print; + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Header/Server.hpp b/Kiseki.Patcher/Header/Server.hpp new file mode 100644 index 0000000..d993756 --- /dev/null +++ b/Kiseki.Patcher/Header/Server.hpp @@ -0,0 +1,37 @@ +#ifdef SERVER + +#pragma once + +#include +#include + +#include "Hooks/StandardOut.hpp" + +#include "Globals.hpp" +#include "Helpers.hpp" + +enum class LogSeverity { + Information = RBX__MESSAGE_INFO, + Output = RBX__MESSAGE_OUTPUT, + Warning = RBX__MESSAGE_WARNING, + Error = RBX__MESSAGE_ERROR +}; + +class Server { +public: + static HANDLE Handle; + + static void Initialize(const std::wstring jobId); + static void Cleanup(); + + struct Log + { + static void Output(const LogSeverity severity, const std::string message); + }; +private: + static std::string OutputLogPath; + + static std::ofstream OutputLog; +}; + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Source/Hooks/CRoblox.cpp b/Kiseki.Patcher/Source/Hooks/CRoblox.cpp index 5c7d0fa..65d8db6 100644 --- a/Kiseki.Patcher/Source/Hooks/CRoblox.cpp +++ b/Kiseki.Patcher/Source/Hooks/CRoblox.cpp @@ -10,6 +10,11 @@ std::wstring authenticationUrl; std::wstring authenticationTicket; std::wstring joinScriptUrl; +#ifdef SERVER +bool hasJobId = false; +std::wstring jobId; +#endif + CRobloxApp__InitInstance_t CRobloxApp__InitInstance = (CRobloxApp__InitInstance_t)ADDRESS_CROBLOXAPP__INITINSTANCE; CRobloxCommandLineInfo__ParseParam_t CRobloxCommandLineInfo__ParseParam = (CRobloxCommandLineInfo__ParseParam_t)ADDRESS_CROBLOXCOMMANDLINEINFO__PARSEPARAM; @@ -45,6 +50,16 @@ BOOL __fastcall CRobloxApp__InitInstance_hook(CRobloxApp* _this) } #endif +#ifdef SERVER + if (!hasJobId) + { +#ifdef _DEBUG + MessageBoxW(nullptr, L"Missing job ID", L"Kiseki", MB_OK | MB_ICONERROR); +#endif + return FALSE; + } +#endif + CApp* app = reinterpret_cast(CLASSLOCATION_CAPP); if (hasAuthenticationUrl && hasAuthenticationTicket && !authenticationUrl.empty() && !authenticationTicket.empty()) @@ -104,6 +119,18 @@ void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* CCommandLineInfo__ParseLast(_this, bLast); return; } + +#ifdef SERVER + if (hasJobId && jobId.empty()) + { + int size = MultiByteToWideChar(CP_ACP, 0, pszParam, strlen(pszParam), nullptr, 0); + jobId.resize(size); + MultiByteToWideChar(CP_ACP, 0, pszParam, strlen(pszParam), &jobId[0], size); + + CCommandLineInfo__ParseLast(_this, bLast); + return; + } +#endif if (bFlag && _stricmp(pszParam, "a") == 0) { @@ -126,6 +153,18 @@ void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* return; } +#ifdef SERVER + if (bFlag && _stricmp(pszParam, "jobId") == 0) + { + hasJobId = true; + CCommandLineInfo__ParseLast(_this, bLast); + + Server::Initialize(jobId); + + return; + } +#endif + CRobloxCommandLineInfo__ParseParam(_this, pszParam, bFlag, bLast); } diff --git a/Kiseki.Patcher/Source/Hooks/DataModel.cpp b/Kiseki.Patcher/Source/Hooks/DataModel.cpp new file mode 100644 index 0000000..3713cfb --- /dev/null +++ b/Kiseki.Patcher/Source/Hooks/DataModel.cpp @@ -0,0 +1,20 @@ +#ifdef SERVER + +#include "Hooks/DataModel.hpp" + +bool setJobId = false; + +DataModel__getJobId_t DataModel__getJobId = (DataModel__getJobId_t)ADDRESS_DATAMODEL__GETJOBID; + +int __fastcall DataModel__getJobId_hook(DataModel* _this, void*, int a2) +{ + if (!setJobId && hasJobId && !jobId.empty()) + { + _this->jobId = Helpers::ws2s(jobId); + setJobId = true; + } + + return DataModel__getJobId(_this, a2); +} + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Source/Hooks/StandardOut.cpp b/Kiseki.Patcher/Source/Hooks/StandardOut.cpp new file mode 100644 index 0000000..8b5b754 --- /dev/null +++ b/Kiseki.Patcher/Source/Hooks/StandardOut.cpp @@ -0,0 +1,22 @@ +#ifdef SERVER + +#include "Hooks/StandardOut.hpp" + +StandardOut__print_t StandardOut__print = (StandardOut__print_t)ADDRESS_STANDARDOUT__PRINT; + +void __fastcall StandardOut__print_hook(int _this, void*, int type, std::string* message) +{ + StandardOut__print(_this, type, message); + + if (Server::Handle) + { +#ifndef _DEBUG + // Message pointer is offset 4 bytes when the DLL is compiled as release + message = reinterpret_cast((int)message + 4); +#endif + + Server::Log::Output((LogSeverity)type, message->c_str()); + } +} + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Source/Server.cpp b/Kiseki.Patcher/Source/Server.cpp new file mode 100644 index 0000000..2cd0799 --- /dev/null +++ b/Kiseki.Patcher/Source/Server.cpp @@ -0,0 +1,78 @@ +#ifdef SERVER + +#include "Server.hpp" + +HANDLE Server::Handle; + +std::ofstream Server::OutputLog; + +std::string Server::OutputLogPath; + +void Server::Initialize(const std::wstring jobId) +{ + std::string _jobId = Helpers::ws2s(jobId); + std::string signature = "Kiseki.Patcher v1.0.0"; + +#ifdef _DEBUG + signature += " (compiled as Debug)"; +#endif + + AllocConsole(); + freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); + Server::Handle = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + SetStdHandle(STD_OUTPUT_HANDLE, Server::Handle); + + printf((signature + "\n\n").c_str()); + + // Initialize file logging + std::filesystem::create_directory(std::filesystem::path(Helpers::getModulePath()).parent_path() / "logs"); + + OutputLogPath = (std::filesystem::path(Helpers::getModulePath()).parent_path() / "logs" / (_jobId + "-Output.log")).string(); + + OutputLog.open(OutputLogPath, std::ios::out); + + OutputLog << signature << " - StandardOut" << std::endl << std::endl; + + OutputLog.close(); +} + +void Server::Log::Output(const LogSeverity severity, const std::string message) +{ + std::string type; + std::string time = Helpers::getISOTimestamp(); + + switch (severity) + { + case LogSeverity::Output: + SetConsoleTextAttribute(Server::Handle, FOREGROUND_BLUE | FOREGROUND_INTENSITY); + type = "out"; + break; + case LogSeverity::Information: + SetConsoleTextAttribute(Server::Handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + type = "info"; + break; + case LogSeverity::Warning: + SetConsoleTextAttribute(Server::Handle, FOREGROUND_RED | FOREGROUND_GREEN); + type = "warn"; + break; + case LogSeverity::Error: + type = "err"; + SetConsoleTextAttribute(Server::Handle, FOREGROUND_RED | FOREGROUND_INTENSITY); + break; + } + + printf("%s\n", message.c_str()); + SetConsoleTextAttribute(Server::Handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + + OutputLog.open(OutputLogPath, std::ios::out); + OutputLog << "[" << time << "] [" << type << "] " << message << std::endl; + OutputLog.close(); +} + +void Server::Cleanup() +{ + CloseHandle(Server::Handle); + OutputLog.close(); +} + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Source/main.cpp b/Kiseki.Patcher/Source/main.cpp index a56db09..c321a4d 100644 --- a/Kiseki.Patcher/Source/main.cpp +++ b/Kiseki.Patcher/Source/main.cpp @@ -3,6 +3,10 @@ #include "Globals.hpp" #include "Patcher.hpp" +#ifdef SERVER +#include "Server.hpp" +#endif + #ifdef PLAYER #include "Discord.hpp" #endif @@ -12,6 +16,7 @@ #include "Hooks/CRoblox.hpp" #ifdef SERVER +#include "Hooks/DataModel.hpp" #include "Hooks/ServerReplicator.hpp" #endif @@ -28,6 +33,10 @@ ADD_PATCH(CRobloxCommandLineInfo__ParseParam, CRobloxCommandLineInfo__ParseParam #endif #ifdef SERVER +ADD_PATCH(DataModel__getJobId, DataModel__getJobId_hook) + +ADD_PATCH(StandardOut__print, StandardOut__print_hook) + ADD_PATCH(ServerReplicator__sendTop, ServerReplicator__sendTop_hook) ADD_PATCH(ServerReplicator__processTicket, ServerReplicator__processTicket_hook) #endif