From ec3d660134f5db14603027fec58b86b4b14ba128 Mon Sep 17 00:00:00 2001 From: Silent Date: Sun, 17 Mar 2024 16:47:54 +0100 Subject: [PATCH] Introduce DirectPlay passthrough The wrapper now checks for the existence of a "real" dplayx.dll circumventing the Windows missing dependencies prompt. If real dplayx.dll is found, it will pass through to that, so users who have DirectPlay installed can now play online without having to delete files from the game directory. --- source/VersionInfo.lua | 4 +- source/dplayx.def | 13 ++--- source/dplayx/dplayx.cpp | 103 +++++++++++++++++++++++++++------------ source/dplayx/stubs.cpp | 47 ++++++++++++++++++ source/dplayx/stubs.h | 13 +++++ source/exportChecker.cpp | 42 ++++++++++++++++ source/exportChecker.h | 6 +++ 7 files changed, 188 insertions(+), 40 deletions(-) create mode 100644 source/dplayx/stubs.cpp create mode 100644 source/dplayx/stubs.h create mode 100644 source/exportChecker.cpp create mode 100644 source/exportChecker.h diff --git a/source/VersionInfo.lua b/source/VersionInfo.lua index 8fd4af4..c303cf2 100644 --- a/source/VersionInfo.lua +++ b/source/VersionInfo.lua @@ -1,7 +1,7 @@ defines { "rsc_FullName=\"DirectPlay Stub\"", - "rsc_MinorVersion=0", + "rsc_MinorVersion=1", "rsc_RevisionID=1", "rsc_BuildID=0", - "rsc_Copyright=\"2023\"" + "rsc_Copyright=\"2023-2024\"" } \ No newline at end of file diff --git a/source/dplayx.def b/source/dplayx.def index 4dc8969..22a165c 100644 --- a/source/dplayx.def +++ b/source/dplayx.def @@ -1,8 +1,9 @@ LIBRARY DPLAYX EXPORTS - DirectPlayCreate=DirectPlayCreate_Stub @1 - DirectPlayEnumerateA=DirectPlayEnumerate_Stub @2 - DirectPlayEnumerateW=DirectPlayEnumerate_Stub @3 - DirectPlayLobbyCreateA=DirectPlayLobbyCreate_Stub @4 - DirectPlayLobbyCreateW=DirectPlayLobbyCreate_Stub @5 - DirectPlayEnumerate=DirectPlayEnumerate_Stub @9 + DirectPlayCreate @1 + DirectPlayEnumerateA @2 + DirectPlayEnumerateW @3 + DirectPlayLobbyCreateA @4 + DirectPlayLobbyCreateW @5 + DirectPlayEnumerate @9 + gdwDPlaySPRefCount @11 diff --git a/source/dplayx/dplayx.cpp b/source/dplayx/dplayx.cpp index 95856a2..bfbbddb 100644 --- a/source/dplayx/dplayx.cpp +++ b/source/dplayx/dplayx.cpp @@ -3,49 +3,88 @@ #include #include +#include -#include "../dplay-stub.h" +#include "../exportChecker.h" +#include "stubs.h" -HRESULT WINAPI DirectPlayCreate_Stub(LPGUID lpGUID, LPVOID* lplpDP, IUnknown* lpUnk) +#include +#include + +static auto DirectPlayCreate_Func = &DirectPlayCreate_Stub; +static auto DirectPlayEnumerate_Func = &DirectPlayEnumerate_Stub; +static auto DirectPlayEnumerateA_Func = &DirectPlayEnumerate_Stub; +static auto DirectPlayEnumerateW_Func = &DirectPlayEnumerate_Stub; +static auto DirectPlayLobbyCreateA_Func = &DirectPlayLobbyCreate_Stub; +static auto DirectPlayLobbyCreateW_Func = &DirectPlayLobbyCreate_Stub; + +static std::once_flag TryPassthroughFlag; +static void DoTryPassthrough() { - if (lpUnk != nullptr) - { - return CLASS_E_NOAGGREGATION; - } - if (lpGUID == nullptr || lplpDP == nullptr) + PWSTR system32Path = nullptr; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_System, KF_FLAG_DEFAULT, nullptr, &system32Path))) { - return DPERR_INVALIDPARAM; + const std::filesystem::path dllPath(std::filesystem::path(system32Path) / L"dplayx.dll"); + if (CheckExports(dllPath.c_str(), { + "DirectPlayCreate", "DirectPlayEnumerate", "DirectPlayEnumerateA", "DirectPlayEnumerateW", "DirectPlayLobbyCreateA", "DirectPlayLobbyCreateW" + })) + { + HMODULE systemDplay = LoadLibraryW(dllPath.c_str()); + if (systemDplay != nullptr) + { + DirectPlayCreate_Func = reinterpret_cast(GetProcAddress(systemDplay, "DirectPlayCreate")); + DirectPlayEnumerate_Func = reinterpret_cast(GetProcAddress(systemDplay, "DirectPlayEnumerate")); + DirectPlayEnumerateA_Func = reinterpret_cast(GetProcAddress(systemDplay, "DirectPlayEnumerateA")); + DirectPlayEnumerateW_Func = reinterpret_cast(GetProcAddress(systemDplay, "DirectPlayEnumerateW")); + DirectPlayLobbyCreateA_Func = reinterpret_cast(GetProcAddress(systemDplay, "DirectPlayLobbyCreateA")); + DirectPlayLobbyCreateW_Func = reinterpret_cast(GetProcAddress(systemDplay, "DirectPlayLobbyCreateW")); + + // "Leak" the DLL handle on purpose + } + } + + CoTaskMemFree(system32Path); } +} - *lplpDP = nullptr; - return DPERR_UNSUPPORTED; +static void TryPassthrough() +{ + std::call_once(TryPassthroughFlag, DoTryPassthrough); } -HRESULT WINAPI DirectPlayEnumerate_Stub(LPVOID lpEnumCallback, LPVOID /*lpContext*/) + +HRESULT WINAPI DirectPlayCreate(LPGUID lpGUID, LPVOID* lplpDP, IUnknown* lpUnk) { - if (lpEnumCallback == nullptr) - { - return DPERR_INVALIDPARAM; - } - return DP_OK; + TryPassthrough(); + return DirectPlayCreate_Func(lpGUID, lplpDP, lpUnk); } -HRESULT WINAPI DirectPlayLobbyCreate_Stub(LPGUID lpGUIDDSP, LPVOID* lplpDPL, IUnknown* lpUnk, void* lpData, DWORD dwDataSize) +HRESULT WINAPI DirectPlayEnumerate(LPVOID lpEnumCallback, LPVOID lpContext) { - if (lpGUIDDSP != nullptr && *lpGUIDDSP != GUID_NULL) - { - return DPERR_INVALIDPARAM; - } - if (lpUnk != nullptr) - { - return CLASS_E_NOAGGREGATION; - } - if (lpData != nullptr || dwDataSize != 0) - { - return DPERR_INVALIDPARAM; - } + TryPassthrough(); + return DirectPlayEnumerate_Func(lpEnumCallback, lpContext); +} + +HRESULT WINAPI DirectPlayEnumerateA(LPVOID lpEnumCallback, LPVOID lpContext) +{ + TryPassthrough(); + return DirectPlayEnumerateA_Func(lpEnumCallback, lpContext); +} - // lplpDPL is not checked for validity in the real DLL... - *lplpDPL = nullptr; - return DPERR_UNSUPPORTED; +HRESULT WINAPI DirectPlayEnumerateW(LPVOID lpEnumCallback, LPVOID lpContext) +{ + TryPassthrough(); + return DirectPlayEnumerateW_Func(lpEnumCallback, lpContext); +} + +HRESULT WINAPI DirectPlayLobbyCreateA(LPGUID lpGUIDDSP, LPVOID* lplpDPL, IUnknown* lpUnk, void* lpData, DWORD dwDataSize) +{ + TryPassthrough(); + return DirectPlayLobbyCreateA_Func(lpGUIDDSP, lplpDPL, lpUnk, lpData, dwDataSize); +} + +HRESULT WINAPI DirectPlayLobbyCreateW(LPGUID lpGUIDDSP, LPVOID* lplpDPL, IUnknown* lpUnk, void* lpData, DWORD dwDataSize) +{ + TryPassthrough(); + return DirectPlayLobbyCreateW_Func(lpGUIDDSP, lplpDPL, lpUnk, lpData, dwDataSize); } diff --git a/source/dplayx/stubs.cpp b/source/dplayx/stubs.cpp new file mode 100644 index 0000000..66a7819 --- /dev/null +++ b/source/dplayx/stubs.cpp @@ -0,0 +1,47 @@ +#include "stubs.h" + +HRESULT WINAPI DirectPlayCreate_Stub(LPGUID lpGUID, LPVOID* lplpDP, IUnknown* lpUnk) +{ + if (lpUnk != nullptr) + { + return CLASS_E_NOAGGREGATION; + } + if (lpGUID == nullptr || lplpDP == nullptr) + { + return DPERR_INVALIDPARAM; + } + + *lplpDP = nullptr; + return DPERR_UNSUPPORTED; +} + +HRESULT WINAPI DirectPlayEnumerate_Stub(LPVOID lpEnumCallback, LPVOID /*lpContext*/) +{ + if (lpEnumCallback == nullptr) + { + return DPERR_INVALIDPARAM; + } + return DP_OK; +} + +HRESULT WINAPI DirectPlayLobbyCreate_Stub(LPGUID lpGUIDDSP, LPVOID* lplpDPL, IUnknown* lpUnk, void* lpData, DWORD dwDataSize) +{ + if (lpGUIDDSP != nullptr && *lpGUIDDSP != GUID_NULL) + { + return DPERR_INVALIDPARAM; + } + if (lpUnk != nullptr) + { + return CLASS_E_NOAGGREGATION; + } + if (lpData != nullptr || dwDataSize != 0) + { + return DPERR_INVALIDPARAM; + } + + // lplpDPL is not checked for validity in the real DLL... + *lplpDPL = nullptr; + return DPERR_UNSUPPORTED; +} + +DWORD gdwDPlaySPRefCount = 0; diff --git a/source/dplayx/stubs.h b/source/dplayx/stubs.h new file mode 100644 index 0000000..7aa0067 --- /dev/null +++ b/source/dplayx/stubs.h @@ -0,0 +1,13 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX + +#include +#include + +#include "../dplay-stub.h" + +HRESULT WINAPI DirectPlayCreate_Stub(LPGUID lpGUID, LPVOID* lplpDP, IUnknown* lpUnk); +HRESULT WINAPI DirectPlayEnumerate_Stub(LPVOID lpEnumCallback, LPVOID /*lpContext*/); +HRESULT WINAPI DirectPlayLobbyCreate_Stub(LPGUID lpGUIDDSP, LPVOID* lplpDPL, IUnknown* lpUnk, void* lpData, DWORD dwDataSize); diff --git a/source/exportChecker.cpp b/source/exportChecker.cpp new file mode 100644 index 0000000..062298b --- /dev/null +++ b/source/exportChecker.cpp @@ -0,0 +1,42 @@ +#include "exportChecker.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX + +#include + +#include + +bool CheckExports(const wchar_t* path, std::initializer_list exports) +{ + std::vector exportsToCheck(exports); + + HMODULE file = LoadLibraryExW(path, nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE); + if (file != nullptr) + { + DWORD_PTR instance = reinterpret_cast(file) & ~3; + const PIMAGE_NT_HEADERS ntHeader = reinterpret_cast(instance + reinterpret_cast(instance)->e_lfanew); + const DWORD exportVA = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (exportVA != 0) + { + const PIMAGE_EXPORT_DIRECTORY exportDirectory = reinterpret_cast(instance + exportVA); + const DWORD* functionNames = reinterpret_cast(instance + exportDirectory->AddressOfNames); + for (DWORD i = 0; i < exportDirectory->NumberOfNames; i++) + { + const char* nameRva = reinterpret_cast(instance + functionNames[i]); + auto it = std::find(exportsToCheck.begin(), exportsToCheck.end(), nameRva); + if (it != exportsToCheck.end()) + { + exportsToCheck.erase(it); + if (exportsToCheck.empty()) + { + // Early out if all required functions are found + break; + } + } + } + } + FreeLibrary(file); + } + return exportsToCheck.empty(); +} diff --git a/source/exportChecker.h b/source/exportChecker.h new file mode 100644 index 0000000..8193cac --- /dev/null +++ b/source/exportChecker.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +bool CheckExports(const wchar_t* path, std::initializer_list exports);