From 6f9a8d32e3891911c44d3cef4292c475ab91763c Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Mon, 11 Dec 2023 22:42:48 +0000 Subject: [PATCH] Implement DllRegisterServer() and DllUnregisterServer() in dpnet.dll --- src/Factory.hpp | 21 ++++- src/dpnet.cpp | 223 +++++++++++++++++++++++++++++++++++++++++++++--- src/dpnet.def | 8 +- 3 files changed, 233 insertions(+), 19 deletions(-) diff --git a/src/Factory.hpp b/src/Factory.hpp index 8e4e6cb..7b743ba 100644 --- a/src/Factory.hpp +++ b/src/Factory.hpp @@ -1,5 +1,5 @@ /* DirectPlay Lite - * Copyright (C) 2018 Daniel Collins + * Copyright (C) 2018-2023 Daniel Collins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ #define DPLITE_FACTORY_HPP #include +#include template class Factory: public IClassFactory { @@ -100,6 +101,24 @@ template class Factory: public IClassFactory { return S_OK; } + + static HRESULT CreateFactoryInstance(void **ppvObject, std::atomic *global_refcount) + { + try { + *((IUnknown**)(ppvObject)) = new Factory(global_refcount); + return S_OK; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + catch (...) + { + return E_UNEXPECTED; + } + } }; +typedef HRESULT(*CreateFactoryInstanceFunc)(void**, std::atomic *); + #endif /* !DPLITE_FACTORY_HPP */ diff --git a/src/dpnet.cpp b/src/dpnet.cpp index 63af754..9d1f89d 100644 --- a/src/dpnet.cpp +++ b/src/dpnet.cpp @@ -1,5 +1,5 @@ /* DirectPlay Lite - * Copyright (C) 2018 Daniel Collins + * Copyright (C) 2018-2023 Daniel Collins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,28 @@ #include #include #include +#include #include #include "DirectPlay8Address.hpp" #include "DirectPlay8Peer.hpp" #include "Factory.hpp" +struct DllClass +{ + GUID clsid; + const char *desc; + + CreateFactoryInstanceFunc CreateFactoryInstance; +}; + +DllClass DLL_CLASSES[] = { + { CLSID_DirectPlay8Address, "DirectPlay8Address Object", &Factory::CreateFactoryInstance }, + { CLSID_DirectPlay8Peer, "DirectPlay8Peer Object", &Factory::CreateFactoryInstance }, +}; + +size_t NUM_DLL_CLASSES = sizeof(DLL_CLASSES) / sizeof(*DLL_CLASSES); + /* Sum of refcounts of all created COM objects. */ static std::atomic global_refcount; @@ -50,18 +66,197 @@ HRESULT CALLBACK DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) { return CLASS_E_CLASSNOTAVAILABLE; } + + for (size_t i = 0; i < NUM_DLL_CLASSES; ++i) + { + if (rclsid == DLL_CLASSES[i].clsid) + { + return DLL_CLASSES[i].CreateFactoryInstance(ppv, &global_refcount); + } + } - if(rclsid == CLSID_DirectPlay8Address) - { - *((IUnknown**)(ppv)) = new Factory(&global_refcount); - return S_OK; - } - else if(rclsid == CLSID_DirectPlay8Peer) - { - *((IUnknown**)(ppv)) = new Factory(&global_refcount); - return S_OK; - } - else{ - return CLASS_E_CLASSNOTAVAILABLE; - } + return CLASS_E_CLASSNOTAVAILABLE; +} + +HRESULT CALLBACK DllRegisterServer() +{ + HRESULT status = S_OK; + + HMODULE this_dll; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (TCHAR*)(&DllRegisterServer), &this_dll)) + { + return E_UNEXPECTED; + } + + char this_dll_path[MAX_PATH]; + if (GetModuleFileNameA(this_dll, this_dll_path, sizeof(this_dll_path)) != ERROR_SUCCESS) + { + return E_UNEXPECTED; + } + + for (size_t i = 0; i < NUM_DLL_CLASSES; ++i) + { + OLECHAR *clsid_string; + HRESULT status2 = StringFromCLSID(DLL_CLASSES[i].clsid, &clsid_string); + if (status2 == E_OUTOFMEMORY) + { + status = E_OUTOFMEMORY; + continue; + } + else if (status2 != S_OK) + { + status = SELFREG_E_CLASS; + continue; + } + + char clsid_path[72]; + snprintf(clsid_path, sizeof(clsid_path), "SOFTWARE\\Classes\\CLSID\\%ls", clsid_string); + + HKEY clsid_key; + if (RegCreateKeyExA( + HKEY_LOCAL_MACHINE, + clsid_path, + 0, + NULL, + 0, + KEY_READ | KEY_WRITE, + NULL, + &clsid_key, + NULL) == ERROR_SUCCESS) + { + if (RegSetValueExA(clsid_key, NULL, 0, REG_SZ, (BYTE*)(DLL_CLASSES[i].desc), strlen(DLL_CLASSES[i].desc) + 1) != ERROR_SUCCESS) + { + status = SELFREG_E_CLASS; + } + + HKEY ips32_key; + if (RegCreateKeyExA( + clsid_key, + "InprocServer32", + 0, + NULL, + 0, + KEY_READ | KEY_WRITE, + NULL, + &ips32_key, + NULL) == ERROR_SUCCESS) + { + if (RegSetValueExA(clsid_key, NULL, 0, REG_SZ, (BYTE*)(this_dll_path), strlen(this_dll_path) + 1) != ERROR_SUCCESS) + { + status = SELFREG_E_CLASS; + } + + if (RegSetValueExA(clsid_key, "ThreadingModel", 0, REG_SZ, (BYTE*)("Both"), strlen("Both") + 1) != ERROR_SUCCESS) + { + status = SELFREG_E_CLASS; + } + + RegCloseKey(ips32_key); + } + else { + status = SELFREG_E_CLASS; + } + + RegCloseKey(clsid_key); + } + else { + status = SELFREG_E_CLASS; + } + + CoTaskMemFree(clsid_string); + } + + return status; +} + +HRESULT CALLBACK DllUnregisterServer() +{ + HRESULT status = S_OK; + + for (size_t i = 0; i < NUM_DLL_CLASSES; ++i) + { + OLECHAR *clsid_string; + HRESULT status2 = StringFromCLSID(DLL_CLASSES[i].clsid, &clsid_string); + if (status2 == E_OUTOFMEMORY) + { + status = E_OUTOFMEMORY; + continue; + } + else if (status2 != S_OK) + { + status = SELFREG_E_CLASS; + continue; + } + + char clsid_path[72]; + snprintf(clsid_path, sizeof(clsid_path), "SOFTWARE\\Classes\\CLSID\\%ls", clsid_string); + + /* From the DllUnregisterServer() documentation on MSDN: + * + * > The server must not disturb any entries that it did not create which currently exist + * > for its object classes. For example, between registration and unregistration, the user + * > may have specified a Treat As relationship between this class and another. In that + * > case, unregistration can remove all entries except the TreatAs key and any others that + * > were not explicitly created in DllRegisterServer. The registry functions specifically + * > disallow the deletion of an entire populated tree in the registry. The server can + * > attempt, as the last step, to remove the CLSID key, but if other entries still exist, + * > the key will remain. + * + * And that is why we don't check whether RegDeleteKey() succeeds below. + */ + + HKEY clsid_key; + DWORD err = RegOpenKeyExA( + HKEY_LOCAL_MACHINE, + clsid_path, + 0, + KEY_READ | KEY_WRITE, + &clsid_key); + + if (err == ERROR_SUCCESS) + { + err = RegDeleteValueA(clsid_key, NULL); + if (err != ERROR_SUCCESS && err != ERROR_FILE_NOT_FOUND) + { + status = SELFREG_E_CLASS; + } + + HKEY ips32_key; + err = RegOpenKeyExA( + clsid_key, + "InprocServer32", + 0, + KEY_READ | KEY_WRITE, + &ips32_key); + + if (err == ERROR_SUCCESS) + { + err = RegDeleteValueA(ips32_key, NULL); + if (err != ERROR_SUCCESS && err != ERROR_FILE_NOT_FOUND) + { + status = SELFREG_E_CLASS; + } + + err = RegDeleteValueA(ips32_key, "ThreadingModel"); + if (err != ERROR_SUCCESS && err != ERROR_FILE_NOT_FOUND) + { + status = SELFREG_E_CLASS; + } + + RegCloseKey(ips32_key); + RegDeleteKeyA(clsid_key, "InprocServer32"); + } + + RegCloseKey(clsid_key); + RegDeleteKeyA(HKEY_LOCAL_MACHINE, clsid_path); + } + else if (err != ERROR_FILE_NOT_FOUND) + { + status = SELFREG_E_CLASS; + } + + CoTaskMemFree(clsid_string); + } + + return status; } diff --git a/src/dpnet.def b/src/dpnet.def index 1d465df..e562c0a 100644 --- a/src/dpnet.def +++ b/src/dpnet.def @@ -1,7 +1,7 @@ LIBRARY dpnet.dll EXPORTS ; DirectPlay8Create @1 - DllCanUnloadNow @2 - DllGetClassObject @3 - ; DllRegisterServer @4 - ; DllUnregisterServer @5 + DllCanUnloadNow @2 PRIVATE + DllGetClassObject @3 PRIVATE + DllRegisterServer @4 PRIVATE + DllUnregisterServer @5 PRIVATE