Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bink2w64.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
<ItemGroup>
<ClCompile Include="src\dllmain.cpp" />
<ClCompile Include="minhook\src\hde\hde64.c" />
<ClCompile Include="src\drm.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="minhook\include\MinHook.h" />
Expand Down
1 change: 1 addition & 0 deletions bink2w64.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<ClCompile Include="src\dllmain.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\drm.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="minhook\include\MinHook.h">
Expand Down
108 changes: 58 additions & 50 deletions src/dllmain.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "dllexports.h"
#define ASI_LOG_FNAME "bink2w64_proxy.log"

#include <Windows.h>
#include <filesystem>
Expand Down Expand Up @@ -125,7 +124,54 @@ void SetWorkingDirectory() {
SetCurrentDirectoryW(exePath.parent_path().c_str());
}

void __stdcall OnAttach()
void __stdcall AsyncAttach()
{
// Handle logic depending on the attached-to exe.
switch (GLEBinkProxy.Game)
{
case LEGameVersion::Launcher:
{
// Keep trying to find a pattern we are *almost* guaranteed to have until we find it.
DRM::WaitForDRMv3();

if (!GLEBinkProxy.LauncherArgs->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: handling of Launcher args failed, aborting!");
}

// Hook CreateProcessA so we can properly set the working directory when launching a game.
if (!GLEBinkProxy.LauncherFixer->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: error fixing launcher working directory issue!");
}

break;
}
default:
{
GLogger.writeln(L"OnAttach: unsupported game, bye!");
return;
}
}

return;
}

using t_PostDRM_hook = void*(void*, char, size_t, void*);
t_PostDRM_hook* PostDRM_hook_orig = nullptr;
void* PostDRM_hook(void* param_1, char param_2, size_t param_3, void* param_4)
{
void* ret = PostDRM_hook_orig(param_1, param_2, param_3, param_4);
// Unlock the console.
if (!GLEBinkProxy.ConsoleEnabler->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: console bypass installation failed");
}
GLEBinkProxy.AsiLoader->PostLoad(GLEBinkProxy.SPI);
return ret;
}

bool SyncAttach()
{
// Open console or log.
Utils::SetupOutput();
Expand All @@ -147,7 +193,7 @@ void __stdcall OnAttach()
if (mhStatus != MH_OK)
{
GLogger.writeln(L"OnAttach: ERROR: failed to initialize the hooking library (code = %d).", mhStatus);
return;
return false;
}

// Initialize global settings.
Expand All @@ -170,6 +216,7 @@ void __stdcall OnAttach()
if (!GLEBinkProxy.AsiLoader->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: loading of one or more ASI plugins failed!");
return false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if an ASI mod fails to load we should probably skip it, not abort, otherwise one bad ASI will ruin the rest, like some of our older ASIs

}

GLogger.writeln(L"OnAttach: got to preload / 0x%p", GLEBinkProxy);
Expand All @@ -178,57 +225,18 @@ void __stdcall OnAttach()
// Post-drm mods are loaded in the switch below.
GLEBinkProxy.AsiLoader->PreLoad(GLEBinkProxy.SPI);

// Prevent the compiler from *potentially* reordering instructions before and after.
MemoryBarrier();

// Handle logic depending on the attached-to exe.
switch (GLEBinkProxy.Game)
{
case LEGameVersion::LE1:
case LEGameVersion::LE2:
case LEGameVersion::LE3:
{
// Keep trying to find a pattern we are *almost* guaranteed to have until we find it.
DRM::WaitForDRMv3();

// Unlock the console.
if (!GLEBinkProxy.ConsoleEnabler->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: console bypass installation failed, aborting!");
break;
}

// Load all native mods that declare being post-drm.
GLEBinkProxy.AsiLoader->PostLoad(GLEBinkProxy.SPI);

break;
}
case LEGameVersion::Launcher:
if (GLEBinkProxy.Game == LEGameVersion::LE1
|| GLEBinkProxy.Game == LEGameVersion::LE2
|| GLEBinkProxy.Game == LEGameVersion::LE3)
{
// Keep trying to find a pattern we are *almost* guaranteed to have until we find it.
DRM::WaitForDRMv3();

if (!GLEBinkProxy.LauncherArgs->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: handling of Launcher args failed, aborting!");
}

// Hook CreateProcessA so we can properly set the working directory when launching a game.
if (!GLEBinkProxy.LauncherFixer->Activate())
{
GLogger.writeln(L"OnAttach: ERROR: error fixing launcher working directory issue!");
}

break;
DRM::InstallDRMv4Hook(PostDRM_hook, reinterpret_cast<void**>(&PostDRM_hook_orig));
}
default:
else
{
GLogger.writeln(L"OnAttach: unsupported game, bye!");
return;
}
CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)AsyncAttach, nullptr, 0, nullptr);
}

return;
return true;
}

void __stdcall OnDetach()
Expand All @@ -252,7 +260,7 @@ void __stdcall OnDetach()
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
switch (dwReason) {
case DLL_PROCESS_ATTACH:
CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)OnAttach, nullptr, 0, nullptr);
SyncAttach();
return TRUE;

case DLL_PROCESS_DETACH:
Expand Down
4 changes: 2 additions & 2 deletions src/dllstruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ struct LEBinkProxy
auto leargs = GetStringValueFromHKCU(L"SOFTWARE\\ME3Tweaks", L"LEAutobootArgs");
if (leargs.length() > 0)
{
CmdLine = new wchar_t[leargs.length()];
CmdLine = new wchar_t[leargs.length() + 1];
wcscpy(CmdLine, leargs.data());
DeleteValueFromHKCU(L"SOFTWARE\\ME3Tweaks", L"LEAutobootArgs");
}
Expand All @@ -196,4 +196,4 @@ struct LEBinkProxy

// Global instance.

static LEBinkProxy GLEBinkProxy;
inline LEBinkProxy GLEBinkProxy;
105 changes: 105 additions & 0 deletions src/drm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "drm.h"
#include <utils/memory.h>
#include <cwchar>

void DRM::WaitForDRMv2()
{
GLogger.writeln(L"WaitForDRMv2: waiting for DRM...");

if (!DrmEvent->InError())
{
auto rc = DrmEvent->WaitForIt(10000); // 10 seconds timeout should be more than enough
switch (rc)
{
case Utils::EventWaitValue::Signaled:
GLogger.writeln(L"WaitForDRMv2: event signaled!");
delete DrmEvent;
DrmEvent = nullptr;
break;
default:
GLogger.writeln(L"WaitForDRMv2: event wait failed (EventWaitValue = %d)", (int)rc);
}
}
}

void DRM::WaitForDRMv3()
{
int iterations = 0;
bool foundPattern = false;
BYTE* offset = nullptr;

do
{
switch (GLEBinkProxy.Game)
{
case LEGameVersion::LE1:
foundPattern = nullptr != Utils::ScanProcess(LE1_UFunctionBind_Pattern, LE1_UFunctionBind_Mask);
break;
case LEGameVersion::LE2:
foundPattern = nullptr != Utils::ScanProcess(LE2_UFunctionBind_Pattern, LE2_UFunctionBind_Mask);
break;
case LEGameVersion::LE3:
foundPattern = nullptr != Utils::ScanProcess(LE3_UFunctionBind_Pattern, LE3_UFunctionBind_Mask);
break;
case LEGameVersion::Launcher:
foundPattern = nullptr != Utils::ScanProcess(LEL_DRMTest_Pattern, LEL_DRMTest_Mask);
break;
default:
foundPattern = true;
}

iterations++;

} while (!foundPattern && iterations < 20000);

if (!foundPattern)
{
GLogger.writeln(L"WaitForDRMv3 - FAILED TO FIND THE PATTERN in %d iteration(s), but still stopping the poll because YOLO.", iterations);
return;
}

GLogger.writeln(L"WaitForDRMv3 - found the pattern in %d iteration(s), stopping the poll.", iterations);
}

BYTE* GetActivatorModuleBase() {
DWORD const CurrentProcess = GetCurrentProcessId();
HANDLE const ProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, CurrentProcess);

BYTE* BaseAddress = nullptr;

if (ProcessSnapshot != INVALID_HANDLE_VALUE) {
MODULEENTRY32 ModuleEntry{};
ModuleEntry.dwSize = sizeof ModuleEntry;

if (Module32First(ProcessSnapshot, &ModuleEntry)) {
do {
auto const* const ModuleName = ModuleEntry.szModule;
if (ModuleName == std::wcsstr(ModuleName, L"Activation64")) {
BaseAddress = ModuleEntry.modBaseAddr;
break;
}
} while (Module32Next(ProcessSnapshot, &ModuleEntry));
}

CloseHandle(ProcessSnapshot);
}

return BaseAddress;
}

void DRM::InstallDRMv4Hook(void* detour, void** original)
{
auto const ActivatorBase = GetActivatorModuleBase();
if (!ActivatorBase) {
GLogger.writeln(L"InstallDRMv4Hook: ERROR: failed to find Activation64.dll base address.");
return;
}
auto const Offset = ActivatorBase + 0x11480;
auto spiReturn = GLEBinkProxy.SPI->InstallHook("PostDRM_hook", Offset, detour, original);
if (spiReturn != SPIReturn::Success)
{
GLogger.writeln(L"InstallDRMv4Hook: ERROR: failed to install post-drm hook (code = %d).", static_cast<int>(spiReturn));
return;
}
GLogger.writeln(L"InstallDRMv4Hook: successfully installed hook on Activation64.dll.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work on DRM-free executables? Is Actionvation64 used?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. I could have it fall back to the old method if Activation64.dll is not found.

}
73 changes: 10 additions & 63 deletions src/drm.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#pragma once

#include "gamever.h"
#include "utils/io.h"
#include "utils/event.h"
#include "utils/hook.h"
#include "dllstruct.h"
#include <wchar.h>
#include <Windows.h>


namespace DRM
Expand All @@ -13,12 +13,12 @@ namespace DRM
// Version 2
// ======================================================================

static Utils::Event* DrmEvent = nullptr;
bool GameWindowCreated = false;
inline Utils::Event* DrmEvent = nullptr;
inline bool GameWindowCreated = false;

typedef HWND(WINAPI* CREATEWINDOWEXW)(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
CREATEWINDOWEXW CreateWindowExW_orig = nullptr;
HWND WINAPI CreateWindowExW_hooked(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
inline CREATEWINDOWEXW CreateWindowExW_orig = nullptr;
inline HWND WINAPI CreateWindowExW_hooked(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
{
//GLogger.writeln(L"CreateWindowExW: lpWindowName = %s", lpWindowName);
if (nullptr != lpWindowName
Expand All @@ -34,70 +34,17 @@ namespace DRM
return CreateWindowExW_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
}

void InitializeDRMv2()
inline void InitializeDRMv2()
{
DrmEvent = new Utils::Event(L"drm_wait");
}
void WaitForDRMv2()
{
GLogger.writeln(L"WaitForDRMv2: waiting for DRM...");

if (!DrmEvent->InError())
{
auto rc = DrmEvent->WaitForIt(10000); // 10 seconds timeout should be more than enough
switch (rc)
{
case Utils::EventWaitValue::Signaled:
GLogger.writeln(L"WaitForDRMv2: event signaled!");
delete DrmEvent;
DrmEvent = nullptr;
break;
default:
GLogger.writeln(L"WaitForDRMv2: event wait failed (EventWaitValue = %d)", (int)rc);
}
}
}
void WaitForDRMv2();


// Version 3
// ======================================================================

void WaitForDRMv3()
{
int iterations = 0;
bool foundPattern = false;
BYTE* offset = nullptr;

do
{
switch (GLEBinkProxy.Game)
{
case LEGameVersion::LE1:
foundPattern = nullptr != Utils::ScanProcess(LE1_UFunctionBind_Pattern, LE1_UFunctionBind_Mask);
break;
case LEGameVersion::LE2:
foundPattern = nullptr != Utils::ScanProcess(LE2_UFunctionBind_Pattern, LE2_UFunctionBind_Mask);
break;
case LEGameVersion::LE3:
foundPattern = nullptr != Utils::ScanProcess(LE3_UFunctionBind_Pattern, LE3_UFunctionBind_Mask);
break;
case LEGameVersion::Launcher:
foundPattern = nullptr != Utils::ScanProcess(LEL_DRMTest_Pattern, LEL_DRMTest_Mask);
break;
default:
foundPattern = true;
}

iterations++;

} while (!foundPattern && iterations < 20000);
void WaitForDRMv3();

if (!foundPattern)
{
GLogger.writeln(L"WaitForDRMv3 - FAILED TO FIND THE PATTERN in %d iteration(s), but still stopping the poll because YOLO.", iterations);
return;
}

GLogger.writeln(L"WaitForDRMv3 - found the pattern in %d iteration(s), stopping the poll.", iterations);
}
void InstallDRMv4Hook(void* detour, void** original);
}
Loading