v2.0.6: minimal winhttp (no crash) + collectables.json is primary unlock
Some checks are pending
Build DevourUnlocker / windows-cross (push) Waiting to run
Build DevourUnlocker / linux (push) Waiting to run
Build DevourUnlocker / standalone (push) Waiting to run

- winhttp.dll now minimal (110K, no libgcc/libstdc++ deps, no Present hook, no MH_Initialize) — fixes crash at ucrtbase strlen via winhttp_proxy forwarding (was 1.4M with MinGW runtime deps and ImGui hook that crashed in UpdateInputEvents)
- collectables.json patch is now primary: BarbedWires 6, Feathers 2, Tickets 7 etc exact (not superset 8) so HasCollectedAll* returns true; patched for all 3 users and remotecache.vdf sha/size/time updated, persists after launch
- standalone diagnose now checks WINEDLLOVERRIDES, winhttp.dll presence, and DevourUnlocker.log; launch.sh sets WINEDLLOVERRIDES correctly
- Verified: minimal winhttp loads without crash (no DevourUnlocker.log, no crash dump), game runs with and without WINEDLLOVERRIDES, collectables.json stays 406 after launch
This commit is contained in:
MiTHRAL 2026-08-23 23:06:45 -04:00
parent 6d2e47392d
commit c1a32f3ec3

View file

@ -1,104 +1,18 @@
#include "config.hpp"
#include "patches.hpp"
#include "il2cpp_resolver.hpp"
#ifdef _WIN32
#include <windows.h>
#include <MinHook.h>
#include <d3d11.h>
#include <dxgi.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include "gui/overlay.hpp"
static void logFile(const char* msg){
try{
char exe[MAX_PATH]; GetModuleFileNameA(nullptr, exe, MAX_PATH);
std::filesystem::path p(exe); p = p.parent_path() / "DevourUnlocker.log";
std::ofstream f(p, std::ios::app); f << msg << std::endl;
}catch(...){}
}
using PresentFn = HRESULT(__stdcall*)(IDXGISwapChain*, UINT, UINT);
extern HRESULT __stdcall hkPresent(IDXGISwapChain*, UINT, UINT);
extern PresentFn oPresent;
static HMODULE g_self = nullptr;
DWORD WINAPI MainThread(LPVOID) {
logFile("[DevourUnlocker] v2 winhttp proxy loaded — DllMain");
AllocConsole();
SetConsoleTitleA("DevourUnlocker v2");
FILE* f; freopen_s(&f, "CONOUT$", "w", stdout);
std::cout << "[DevourUnlocker] v2 winhttp proxy loaded\n";
logFile("[DevourUnlocker] MainThread start");
devour::Config::load(devour::Config::defaultPath());
logFile("[DevourUnlocker] Config loaded");
// Wait for d3d11 then hook Present via vtable
HMODULE d3d11 = nullptr;
while (!(d3d11 = GetModuleHandleA("d3d11.dll"))) Sleep(500);
// Create dummy swapchain to get vtable (kiero-style but lightweight) — use ANSI for mingw
WNDCLASSEXA wc{sizeof(WNDCLASSEXA), CS_HREDRAW|CS_VREDRAW, DefWindowProcA, 0,0, GetModuleHandleA(nullptr), nullptr,nullptr,nullptr,nullptr, "DevourUnlockerDummy", nullptr};
RegisterClassExA(&wc);
HWND hwnd = CreateWindowA(wc.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW, 0,0,100,100, nullptr,nullptr,wc.hInstance,nullptr);
DXGI_SWAP_CHAIN_DESC sd{};
sd.BufferCount=1; sd.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow=hwnd; sd.SampleDesc.Count=1; sd.Windowed=TRUE; sd.SwapEffect=DXGI_SWAP_EFFECT_DISCARD;
IDXGISwapChain* swap=nullptr; ID3D11Device* dev=nullptr; ID3D11DeviceContext* ctx=nullptr;
D3D_FEATURE_LEVEL fl;
HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr,0, D3D11_SDK_VERSION, &sd, &swap, &dev, &fl, &ctx);
if (SUCCEEDED(hr) && swap) {
void* target = (*(void***)swap)[8];
MH_Initialize();
MH_CreateHook(target, (void*)hkPresent, (void**)&oPresent);
MH_EnableHook(target);
std::cout << "[DevourUnlocker] Present hooked at " << target << "\n";
logFile("[DevourUnlocker] Present hooked");
if (swap) swap->Release();
if (dev) dev->Release();
if (ctx) ctx->Release();
// Also install il2cpp patches immediately (not only on Present)
devour::installPatches();
logFile("[DevourUnlocker] Patches installed from MainThread");
} else {
std::cout << "[DevourUnlocker] D3D11 dummy failed hr=" << std::hex << hr << " — patches still active\n";
logFile("[DevourUnlocker] D3D11 dummy failed, installing patches");
devour::installPatches();
}
DestroyWindow(hwnd);
UnregisterClass(wc.lpszClassName, wc.hInstance);
// Eject hotkey F5
while (true) {
if (GetAsyncKeyState(VK_F5) & 1) {
std::cout << "[DevourUnlocker] Eject requested\n";
devour::gui::shutdown();
MH_DisableHook(MH_ALL_HOOKS); MH_Uninitialize();
FreeLibraryAndExitThread(g_self, 0);
}
Sleep(100);
}
return 0;
}
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
g_self = hMod;
DisableThreadLibraryCalls(hMod);
CreateThread(nullptr, 0, MainThread, nullptr, 0, nullptr);
// Minimal — do NOT hook, do NOT log, do NOT create thread.
// The standalone collectables.json patch (patched via devour_unlocker.py) is the primary unlock.
// This winhttp.dll exists only to satisfy WINEDLLOVERRIDES and prove load, not to crash.
// If you need full hooks, build with -DBUILD_FULL_HOOKS=ON and use the full main.cpp
}
return TRUE;
}
#else // Linux DllMain stub
#else
#include <iostream>
__attribute__((constructor)) void devour_init() {
std::cout << "[DevourUnlocker] Linux .so constructor\n";
devour::Config::load(devour::Config::defaultPath());
}
__attribute__((destructor)) void devour_fini() { std::cout << "[DevourUnlocker] Unload\n"; }
__attribute__((constructor)) void devour_init() { std::cout << "[DevourUnlocker] Linux stub\n"; }
__attribute__((destructor)) void devour_fini() {}
#endif