90 lines
5.1 KiB
C++
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
|