diff --git a/CMakeLists.txt b/CMakeLists.txt index 7504a6fe..2015194c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,6 +343,10 @@ add_subdirectory(dAuthServer) add_subdirectory(dChatServer) add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the other binaries +if (WIN32 AND LOCAL_SERVEr) + add_subdirectory(dLocalServer) +endif() + target_precompile_headers( dZoneManager PRIVATE ${HEADERS_DZONEMANAGER} diff --git a/CMakeVariables.txt b/CMakeVariables.txt index 4ded5f59..53bc609a 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -30,3 +30,6 @@ OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3/ # Whether or not to cache the entire CDClient Database into memory instead of lazy loading. # 0 means to lazy load, all other values mean load the entire database. CDCLIENT_CACHE_ALL=0 + +# Build a local client running server instead of a production server. +LOCAL_SERVER=1 diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 741a6e59..0e173cf1 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -38,7 +38,7 @@ namespace Game { void HandlePacket(Packet* packet); -int main(int argc, char** argv) { +int start(int argc, char** argv) { constexpr uint32_t authFramerate = mediumFramerate; constexpr uint32_t authFrameDelta = mediumFrameDelta; Diagnostics::SetProcessName("Auth"); @@ -161,6 +161,43 @@ int main(int argc, char** argv) { return EXIT_SUCCESS; } +#ifdef LOCAL_SERVER +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved) // reserved +{ + // Perform actions based on the reason for calling. + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + start(0, nullptr); + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + + if (lpvReserved != nullptr) { + break; // do not do cleanup if process termination scenario + } + + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} +#else +int main(int argc, char** argv) { + return start(argc, argv); +} +#endif + void HandlePacket(Packet* packet) { if (packet->length < 4) return; diff --git a/dAuthServer/CMakeLists.txt b/dAuthServer/CMakeLists.txt index 7dcbf041..964b43a2 100644 --- a/dAuthServer/CMakeLists.txt +++ b/dAuthServer/CMakeLists.txt @@ -1,4 +1,9 @@ -add_executable(AuthServer "AuthServer.cpp") + +if (WIN32 AND LOCAL_SERVER) + add_library(AuthServer SHARED "AuthServer.cpp") +else() + add_executable(AuthServer "AuthServer.cpp") +endif() target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer) diff --git a/dChatServer/CMakeLists.txt b/dChatServer/CMakeLists.txt index c7eea041..a8c8cbed 100644 --- a/dChatServer/CMakeLists.txt +++ b/dChatServer/CMakeLists.txt @@ -4,7 +4,13 @@ set(DCHATSERVER_SOURCES "PlayerContainer.cpp" ) -add_executable(ChatServer "ChatServer.cpp") + +if (WIN32 AND LOCAL_SERVER) + add_library(ChatServer SHARED "ChatServer.cpp") +else() + add_executable(ChatServer "ChatServer.cpp") +endif() + target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter") add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"") diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index b4959992..5e4d6516 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -41,7 +41,7 @@ namespace Game { void HandlePacket(Packet* packet); -int main(int argc, char** argv) { +int start(int argc, char** argv) { constexpr uint32_t chatFramerate = mediumFramerate; constexpr uint32_t chatFrameDelta = mediumFrameDelta; Diagnostics::SetProcessName("Chat"); @@ -178,6 +178,43 @@ int main(int argc, char** argv) { return EXIT_SUCCESS; } +#ifdef LOCAL_SERVER +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved) // reserved +{ + // Perform actions based on the reason for calling. + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + start(0, nullptr); + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + + if (lpvReserved != nullptr) { + break; // do not do cleanup if process termination scenario + } + + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} +#else +int main(int argc, char** argv) { + return start(argc, argv); +} +#endif + void HandlePacket(Packet* packet) { if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { diff --git a/dLocalServer/CMakeLists.txt b/dLocalServer/CMakeLists.txt new file mode 100644 index 00000000..df206907 --- /dev/null +++ b/dLocalServer/CMakeLists.txt @@ -0,0 +1,7 @@ +set(DLOCAL_SERVER_SOURCES + "dllmain.cpp" +) + +add_library(dLocalServer OBJECT ${DLOCAL_SERVER_SOURCES}) + +target_link_libraries(dLocalServer) diff --git a/dLocalServer/dllmain.cpp b/dLocalServer/dllmain.cpp new file mode 100644 index 00000000..656ffd37 --- /dev/null +++ b/dLocalServer/dllmain.cpp @@ -0,0 +1,32 @@ +#include + +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved) // reserved +{ + // Perform actions based on the reason for calling. + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + + if (lpvReserved != nullptr) { + break; // do not do cleanup if process termination scenario + } + + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} diff --git a/dMasterServer/CMakeLists.txt b/dMasterServer/CMakeLists.txt index 2e2b4dd9..63c326ba 100644 --- a/dMasterServer/CMakeLists.txt +++ b/dMasterServer/CMakeLists.txt @@ -1,11 +1,23 @@ set(DMASTERSERVER_SOURCES "InstanceManager.cpp" "PersistentIDManager.cpp" - "Start.cpp" + "StartLocal.cpp" ) +if(WIN32 AND LOCAL_SERVER) + set(DMASTERSERVER_SOURCES ${DMASTERSERVER_SOURCES} "StartLocal.cpp") +else() + set(DMASTERSERVER_SOURCES ${DMASTERSERVER_SOURCES} "Start.cpp") +endif() + add_library(dMasterServer ${DMASTERSERVER_SOURCES}) -add_executable(MasterServer "MasterServer.cpp") + +if (WIN32 AND LOCAL_SERVER) + add_library(MasterServer SHARED "MasterServer.cpp") +else() + add_executable(MasterServer "MasterServer.cpp") +endif() + target_compile_definitions(MasterServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"") target_include_directories(dMasterServer PUBLIC "." "${PROJECT_SOURCE_DIR}/dZoneManager" # InstanceManager.h uses dZMCommon.h diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index b764169a..0df136d3 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -62,7 +62,7 @@ std::map activeSessions; SystemAddress authServerMasterPeerSysAddr; SystemAddress chatServerMasterPeerSysAddr; -int main(int argc, char** argv) { +int start(int argc, char** argv) { constexpr uint32_t masterFramerate = mediumFramerate; constexpr uint32_t masterFrameDelta = mediumFrameDelta; Diagnostics::SetProcessName("Master"); @@ -429,6 +429,44 @@ int main(int argc, char** argv) { return ShutdownSequence(EXIT_SUCCESS); } +#ifdef LOCAL_SERVER +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved) // reserved +{ + // Perform actions based on the reason for calling. + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + AllocConsole(); + start(0, nullptr); + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + + if (lpvReserved != nullptr) { + break; // do not do cleanup if process termination scenario + } + + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} +#else +int main(int argc, char** argv) { + return start(argc, argv); +} +#endif + void HandlePacket(Packet* packet) { if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION) { diff --git a/dMasterServer/StartLocal.cpp b/dMasterServer/StartLocal.cpp new file mode 100644 index 00000000..97032c5f --- /dev/null +++ b/dMasterServer/StartLocal.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include "Start.h" +#include "Logger.h" +#include "dConfig.h" +#include "Game.h" +#include "BinaryPathFinder.h" + +// Function to create a process and execute a DLL +bool RunDLLAsApplication(const char* dllPath) { + STARTUPINFOA si = { sizeof(STARTUPINFOA) }; + PROCESS_INFORMATION pi = { 0 }; + + // Path to a dummy executable + const char* dummyProcessPath = "C:\\Windows\\System32\\notepad.exe"; + + // Create a suspended dummy process + if (!CreateProcessA( + dummyProcessPath, + NULL, + NULL, + NULL, + FALSE, + CREATE_SUSPENDED, + NULL, + NULL, + &si, + &pi)) { + std::cerr << "Failed to create dummy process. Error: " << GetLastError() << std::endl; + return false; + } + + // Get the address of LoadLibraryA in the current process + HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); + if (!kernel32) { + std::cerr << "Failed to get handle for kernel32.dll. Error: " << GetLastError() << std::endl; + TerminateProcess(pi.hProcess, 1); + return false; + } + + void* loadLibraryAddr = (void*)GetProcAddress(kernel32, "LoadLibraryA"); + if (!loadLibraryAddr) { + std::cerr << "Failed to get address of LoadLibraryA. Error: " << GetLastError() << std::endl; + TerminateProcess(pi.hProcess, 1); + return false; + } + + // Allocate memory in the target process for the DLL path + void* remoteMemory = VirtualAllocEx(pi.hProcess, NULL, strlen(dllPath) + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!remoteMemory) { + std::cerr << "Failed to allocate memory in target process. Error: " << GetLastError() << std::endl; + TerminateProcess(pi.hProcess, 1); + return false; + } + + // Write the DLL path into the allocated memory + if (!WriteProcessMemory(pi.hProcess, remoteMemory, dllPath, strlen(dllPath) + 1, NULL)) { + std::cerr << "Failed to write DLL path to target process. Error: " << GetLastError() << std::endl; + VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE); + TerminateProcess(pi.hProcess, 1); + return false; + } + + // Create a remote thread in the target process to load the DLL + HANDLE remoteThread = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddr, remoteMemory, 0, NULL); + if (!remoteThread) { + std::cerr << "Failed to create remote thread. Error: " << GetLastError() << std::endl; + VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE); + TerminateProcess(pi.hProcess, 1); + return false; + } + + // Wait for the remote thread to complete + WaitForSingleObject(remoteThread, INFINITE); + CloseHandle(remoteThread); + + // Free the allocated memory + VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE); + + // Resume the main thread of the process + ResumeThread(pi.hThread); + + // Clean up process and thread handles + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return true; +} + +void StartChatServer() { + if (Game::ShouldShutdown()) { + LOG("Currently shutting down. Chat will not be restarted."); + return; + } + + RunDLLAsApplication("ChatServer.dll"); +} + +void StartAuthServer() { + if (Game::ShouldShutdown()) { + LOG("Currently shutting down. Auth will not be restarted."); + return; + } + + RunDLLAsApplication("AuthServer.dll"); +} + +void StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID) { + RunDLLAsApplication("WorldServer.dll"); + + //cmd.append(std::to_string(mapID)); + //cmd.append(" -port "); + //cmd.append(std::to_string(port)); + //cmd.append(" -instance "); + //cmd.append(std::to_string(lastInstanceID)); + //cmd.append(" -maxclients "); + //cmd.append(std::to_string(maxPlayers)); + //cmd.append(" -clone "); + //cmd.append(std::to_string(cloneID)); +} diff --git a/dWorldServer/CMakeLists.txt b/dWorldServer/CMakeLists.txt index 62a3767a..a0170690 100644 --- a/dWorldServer/CMakeLists.txt +++ b/dWorldServer/CMakeLists.txt @@ -5,7 +5,12 @@ set(DWORLDSERVER_SOURCES add_library(dWorldServer OBJECT ${DWORLDSERVER_SOURCES}) target_link_libraries(dWorldServer PUBLIC dGameBase dCommon) -add_executable(WorldServer "WorldServer.cpp") +if (WIN32 AND LOCAL_SERVER) + add_library(WorldServer SHARED "WorldServer.cpp") +else() + add_executable(WorldServer "WorldServer.cpp") +endif() + target_include_directories(WorldServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter") add_compile_definitions(WorldServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"") diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index c3dbeaca..b2907d61 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -120,7 +120,8 @@ uint32_t instanceID = 0; uint32_t g_CloneID = 0; std::string databaseChecksum = ""; -int main(int argc, char** argv) { + +int start(int argc, char** argv) { Diagnostics::SetProcessName("World"); Diagnostics::SetProcessFileName(argv[0]); Diagnostics::Initialize(); @@ -526,10 +527,50 @@ int main(int argc, char** argv) { Metrics::AddMeasurement(MetricVariable::CPUTime, (1e6 * (1000.0 * (std::clock() - metricCPUTimeStart))) / CLOCKS_PER_SEC); Metrics::EndMeasurement(MetricVariable::Frame); } + FinalizeShutdown(); + return EXIT_SUCCESS; } + +#ifdef LOCAL_SERVER +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved) // reserved +{ + // Perform actions based on the reason for calling. + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + start(0, nullptr); + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + + if (lpvReserved != nullptr) { + break; // do not do cleanup if process termination scenario + } + + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} +#else +int main(int argc, char** argv) { + return start(argc, argv); +} +#endif + void HandlePacketChat(Packet* packet) { if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 79863a53..38d72bb7 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -31,6 +31,10 @@ endif() target_include_directories(bcrypt INTERFACE "libbcrypt/include") target_include_directories(bcrypt PRIVATE "libbcrypt/src") +if (WIN32) + add_subdirectory(hijackkit) +endif() + # Source code for sqlite add_subdirectory(SQLite) diff --git a/thirdparty/hijackkit/.gitignore b/thirdparty/hijackkit/.gitignore new file mode 100644 index 00000000..8f403f35 --- /dev/null +++ b/thirdparty/hijackkit/.gitignore @@ -0,0 +1,2 @@ +.vs/ +out/ diff --git a/thirdparty/hijackkit/CMakeLists.txt b/thirdparty/hijackkit/CMakeLists.txt new file mode 100644 index 00000000..dd4ca9a0 --- /dev/null +++ b/thirdparty/hijackkit/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required (VERSION 3.8) +project (HijackKit) + +add_library(HijackKit "include/utils.h" "include/memory.h" "include/tricks.h" "source/memory.cpp" "source/utils.cpp") +target_include_directories(HijackKit PUBLIC "include/") + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET HijackKit PROPERTY CXX_STANDARD 20) +endif() + +set_target_properties(HijackKit PROPERTIES LINKER_LANGUAGE CXX) \ No newline at end of file diff --git a/thirdparty/hijackkit/README.txt b/thirdparty/hijackkit/README.txt new file mode 100644 index 00000000..0014c40e --- /dev/null +++ b/thirdparty/hijackkit/README.txt @@ -0,0 +1 @@ +https://github.com/Jettford/HijackKit \ No newline at end of file diff --git a/thirdparty/hijackkit/include/memory.h b/thirdparty/hijackkit/include/memory.h new file mode 100644 index 00000000..017186f5 --- /dev/null +++ b/thirdparty/hijackkit/include/memory.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +namespace hijack { + namespace memory { + inline void Protect(size_t address, size_t size, std::function function); + inline void* Read(size_t address, size_t size); + inline void Patch(size_t address, size_t size, void* data); + + template + inline void Patch(size_t address, T data) { + hijack::memory::Patch(address, sizeof(T), &data); + } + } +} \ No newline at end of file diff --git a/thirdparty/hijackkit/include/tricks.h b/thirdparty/hijackkit/include/tricks.h new file mode 100644 index 00000000..8841de9f --- /dev/null +++ b/thirdparty/hijackkit/include/tricks.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include + +namespace hijack { + namespace tricks { + struct ModuleInfo { + wchar_t* m_ModuleName; + size_t m_ModuleBase; + }; + + template + inline T* GetVFunc(void* instance, size_t index) { + return (T*)*(size_t*)((size_t)instance + index * sizeof(size_t)); + } + + inline std::vector LookupDLL() { + size_t pedAddr = __readgsqword(0x60); + + size_t ldrData = *(size_t*)(pedAddr + 0x18); + size_t firstEntry = *(size_t*)(ldrData + 0x10); + size_t currentEntry = firstEntry; + + std::vector modules; + + while (*(DWORD*)(currentEntry + 0x60) != NULL) { + wchar_t* dllName = (wchar_t*)(currentEntry + 0x60); + size_t dllBase = *(size_t*)(currentEntry + 0x30); + + ModuleInfo info; + info.m_ModuleName = dllName; + info.m_ModuleBase = dllBase; + + modules.push_back(info); + + currentEntry = *(size_t*)currentEntry; + } + + return modules; + } + } +} \ No newline at end of file diff --git a/thirdparty/hijackkit/include/utils.h b/thirdparty/hijackkit/include/utils.h new file mode 100644 index 00000000..263e4961 --- /dev/null +++ b/thirdparty/hijackkit/include/utils.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace hjiack { + namespace utils { + uintptr_t GetModuleBaseAddress(const wchar_t* moduleName); + void AllocateConsole(); + } +} \ No newline at end of file diff --git a/thirdparty/hijackkit/source/memory.cpp b/thirdparty/hijackkit/source/memory.cpp new file mode 100644 index 00000000..f591f3b0 --- /dev/null +++ b/thirdparty/hijackkit/source/memory.cpp @@ -0,0 +1,28 @@ +#include "memory.h" + +#include + +void hijack::memory::Protect(size_t address, size_t size, std::function function) { + DWORD oldProtect; + VirtualProtect((void*)address, size, PAGE_EXECUTE_READWRITE, &oldProtect); + + function(); + + VirtualProtect((void*)address, size, oldProtect, &oldProtect); +} + +void* hijack::memory::Read(size_t address, size_t size) { + void* returnData = malloc(size); + + Protect(address, size, [&]() { + memcpy(returnData, (void*)address, size); + }); + + return returnData; +} + +void hijack::memory::Patch(size_t address, size_t size, void* data) { + Protect(address, size, [&]() { + memcpy((void*)address, data, size); + }); +} diff --git a/thirdparty/hijackkit/source/utils.cpp b/thirdparty/hijackkit/source/utils.cpp new file mode 100644 index 00000000..94d7c6e2 --- /dev/null +++ b/thirdparty/hijackkit/source/utils.cpp @@ -0,0 +1,45 @@ +#include "utils.h" + +#include +#include + +#include + +#include + +uintptr_t hjiack::utils::GetModuleBaseAddress(const wchar_t* moduleName) { + uintptr_t modBaseAddr = 0; + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); + + if (hSnap != INVALID_HANDLE_VALUE) + { + MODULEENTRY32 modEntry; + modEntry.dwSize = sizeof(modEntry); + if (Module32First(hSnap, &modEntry)) + { + do + { + auto a = std::wstring((wchar_t*)modEntry.szModule); + if (!_wcsicmp(a.c_str(), moduleName)) + { + modBaseAddr = (uintptr_t)modEntry.modBaseAddr; + break; + } + } while (Module32Next(hSnap, &modEntry)); + } + } + + CloseHandle(hSnap); + + return modBaseAddr; +} + +void hjiack::utils::AllocateConsole() { + AllocConsole(); + + + freopen_s((FILE**)__acrt_iob_func(1), "CONOUT$", "w", __acrt_iob_func(1)); + freopen_s((FILE**)__acrt_iob_func(2), "CONOUT$", "w", __acrt_iob_func(2)); + freopen_s((FILE**)__acrt_iob_func(0), "CONIN$", "r", __acrt_iob_func(0)); +} +