DS2Revival/mods/widescreen/portrait.cpp

90 lines
5.1 KiB
C++

// Broken World: the HUD portrait grab follows the real screen when HudScale shrinks the UI shell (DS2Widescreen).
//
// Part of the HUD scale, not of the picture-quality options: without HudScale the shell has the screen's size and the stub
// below changes nothing. (The Steam build's own grab bug, PortraitFix, is DS2Graphics' - mods/graphics/graphics.cpp.)
#include "../common/patch.h"
#include <cstring>
namespace {
DWORD g_screenPtrAddr = 0; // address of the pointer to the renderer's screen rectangle (+0xac/+0xb0 left/top, +0xb4/+0xb8 right/bottom)
// ---- Broken World: the HUD portrait grab with a scaled interface (HudScale) --------------------------------------
// Broken World's 2.2 portrait generator (0x50089d, the same function as Steam 0x4f25bb) places the 64x64 grab from the UI
// shell's size ([UIShell+0x78/+0x7c], the shell centred in a 1024x768 layout), not from the renderer's screen rectangle. With
// HudScale the shell is W/N x H/N (1720x720 at 3440x1440) while the head is still drawn at the centre of the real screen: the
// rectangle then lies above the top of the screen (shell height below 768), the grab is clipped, its size does not match, and
// the generator fails. It makes a clone of the hero for every attempt and only deletes it on success, and the HUD asks again
// every frame: on a join in progress the host's address space (2 GB) is gone within seconds ("SmartHeap Library: Out of
// memory"). The fix moves the rectangle by half the difference between the screen and the shell, which is zero without HudScale.
DWORD g_shellPtrAddr = 0; // address of the pointer to the UI shell (+0x78 width, +0x7c height)
void __cdecl FixPortraitRectBw(int* r) {
const BYTE* screen = *reinterpret_cast<const BYTE* const*>(g_screenPtrAddr);
const BYTE* shell = *reinterpret_cast<const BYTE* const*>(g_shellPtrAddr);
if (!screen || !shell) return;
int w = *reinterpret_cast<const int*>(screen + 0xb4) - *reinterpret_cast<const int*>(screen + 0xac);
int h = *reinterpret_cast<const int*>(screen + 0xb8) - *reinterpret_cast<const int*>(screen + 0xb0);
int sw = *reinterpret_cast<const int*>(shell + 0x78), sh = *reinterpret_cast<const int*>(shell + 0x7c);
if (sw <= 0 || sh <= 0 || (sw == w && sh == h)) return;
int dx = (w - sw) / 2, dy = (h - sh) / 2;
r[0] += dx; r[2] += dx;
r[1] += dy; r[3] += dy;
static bool logged = false;
if (!logged) {
logged = true;
ds2::log().Write("portrait: HUD portrait grab for shell %dx%d on screen %dx%d moved by %d,%d to %d,%d", sw, sh, w, h, dx, dy, r[0], r[1]);
}
}
bool InstallPortraitFixBw() {
DWORD site = ds2::FindUnique("A1 ?? ?? ?? ?? 8B 48 3C 8D 45 84 50 E8 ?? ?? ?? ?? 8B F0 8B");
DWORD shell = ds2::FindUnique("A1 ?? ?? ?? ?? 8B 70 78 53 68 ?? ?? ?? ?? 8D 4D 94 E8");
DWORD screen = ds2::FindUnique("8B 0D ?? ?? ?? ?? 8B D0 8B 81 B4 00 00 00 2B 42 68 2B 81 AC 00 00 00");
if (!site || !shell || !screen) {
ds2::log().Write("SKIP portrait: HUD portrait shift - grab code not found");
return false;
}
g_shellPtrAddr = *reinterpret_cast<const DWORD*>(shell + 1);
g_screenPtrAddr = *reinterpret_cast<const DWORD*>(screen + 2);
site += 8; // "lea eax,[ebp-0x7c]; push eax; call <grab>" (left, top, right, bottom)
const DWORD grab = site + 9 + *reinterpret_cast<const LONG*>(site + 5);
BYTE expect[9];
std::memcpy(expect, reinterpret_cast<void*>(site), sizeof(expect));
BYTE* stub = static_cast<BYTE*>(VirtualAlloc(nullptr, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
if (!stub) return false;
BYTE* c = stub;
auto rel = [&](DWORD target, BYTE* at) { return static_cast<LONG>(target - (reinterpret_cast<DWORD>(at) + 4)); };
*c++ = 0x60; // pushad
*c++ = 0x8D; *c++ = 0x45; *c++ = 0x84; // lea eax,[ebp-0x7c]
*c++ = 0x50; // push eax
*c++ = 0xE8; { LONG v = rel(reinterpret_cast<DWORD>(&FixPortraitRectBw), c); std::memcpy(c, &v, 4); c += 4; }
*c++ = 0x83; *c++ = 0xC4; *c++ = 0x04; // add esp,4
*c++ = 0x61; // popad
*c++ = 0x8D; *c++ = 0x45; *c++ = 0x84; // lea eax,[ebp-0x7c]
*c++ = 0x50; // push eax
*c++ = 0xE8; { LONG v = rel(grab, c); std::memcpy(c, &v, 4); c += 4; }
*c++ = 0xE9; { LONG v = rel(site + 9, c); std::memcpy(c, &v, 4); c += 4; }
BYTE patch[9];
std::memset(patch, 0x90, sizeof(patch));
patch[0] = 0xE9;
LONG jmp = static_cast<LONG>(reinterpret_cast<DWORD>(stub) - (site + 5));
std::memcpy(patch + 1, &jmp, 4);
return ds2::PatchBytes(site, expect, patch, sizeof(patch), "portrait: HUD portrait grab follows the real screen with HudScale (Broken World)");
}
} // namespace
namespace widescreen::hud {
// Called by the HUD scale code when it is installed (Broken World only; the UI shell is about to shrink).
bool InstallBwPortraitShift() {
static bool done = false, ok = false;
if (!done) {
done = true;
ok = ds2::ExeKind() == ds2::Exe::BrokenWorld && InstallPortraitFixBw();
}
return ok;
}
} // namespace widescreen::hud