Menu screens: rock background, centred frame, logo, 3D hero

- Spielen hero panel shows the selected hero in 3D (cUI_NetGranny,
  game/ui/granny.h).
- All menu screens draw on the options' rock wall; the main menu's 1024
  frame is drawn centred over it, mouse and cursor shifted to match.
- The main menu's and options' own pictures and the main menu's 3D scene
  are left out (textures hidden by surface, scene call redirected).
- Logo top left: FX_SACREDLOGO.TGA, drawn added.
- Game textures by name (game/ui/texture.h), textured and added quads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-21 00:05:55 +02:00
1 parent 34a119433a
commit db87e276bf
16 files changed
+620 -69

No files matched your search

+111 -13
View File
@@ -21,6 +21,7 @@
// Enabled=1
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <vector>
@@ -31,6 +32,7 @@
#include "game/render/dx_driver7.h"
#include "game/ui/events.h"
#include "mods/hud.h"
#include "mods/screen.h"
#include "mods/widescreen.h"
#include "re/config.h"
#include "re/hook.h"
@@ -268,6 +270,29 @@ private:
CursorOffset cursor_;
};
// RAII: shift drawing only (the cursor is not seen shifted), for things
// already in the shifted layout's coordinates (the main menu's cursor).
class DrawShift {
public:
explicit DrawShift(int dx) : active_(!g_drawScoped && dx != 0) {
if (active_) {
g_drawScoped = true;
g_offsetX = dx;
}
}
~DrawShift() {
if (active_) {
g_drawScoped = false;
g_offsetX = 0;
}
}
DrawShift(const DrawShift&) = delete;
DrawShift& operator=(const DrawShift&) = delete;
private:
bool active_;
};
// Stores the offsets of a mouse event's x fields (game/ui/events.h); returns
// their count (0: no mouse event).
int MouseEventXOffsets(void* event, int (&offsets)[2]) {
@@ -383,13 +408,47 @@ using DrawIndexedPrimitiveFn = HRESULT(__stdcall*)(void* device, DWORD type, DWO
DrawPrimitiveFn g_drawPrimitive = nullptr;
DrawIndexedPrimitiveFn g_drawIndexedPrimitive = nullptr;
// Textures not drawn (SetHiddenTextures): the surface bound to stage 0 is
// tracked per thread (each thread draws with its own device calls).
constexpr int kHiddenTextureMax = 64;
std::atomic<void*> g_hiddenTextures[kHiddenTextureMax] = {};
thread_local void* g_boundTexture = nullptr;
using SetTextureFn = HRESULT(__stdcall*)(void* device, DWORD stage, void* surface);
SetTextureFn g_setTexture = nullptr;
HRESULT __stdcall SetTextureTracked(void* device, DWORD stage, void* surface) {
if (stage == 0) {
g_boundTexture = surface;
}
return g_setTexture(device, stage, surface);
}
bool BoundTextureHidden() {
if (!g_boundTexture) {
return false;
}
for (const auto& hidden : g_hiddenTextures) {
if (hidden.load(std::memory_order_relaxed) == g_boundTexture) {
return true;
}
}
return false;
}
HRESULT __stdcall DrawPrimitiveShifted(void* device, DWORD type, DWORD fvf, void* vertices, DWORD count,
DWORD flags) {
if (BoundTextureHidden()) {
return S_OK;
}
return g_drawPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, flags);
}
HRESULT __stdcall DrawIndexedPrimitiveShifted(void* device, DWORD type, DWORD fvf, void* vertices, DWORD count,
WORD* indices, DWORD indexCount, DWORD flags) {
if (BoundTextureHidden()) {
return S_OK;
}
return g_drawIndexedPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, indices,
indexCount, flags);
}
@@ -440,30 +499,25 @@ HRESULT __stdcall ClearShifted(void* device, DWORD count, Rect7* rects, DWORD fl
using GetViewportFn = HRESULT(__stdcall*)(void* device, Viewport7* viewport);
constexpr int kVtableGetViewport = 15; // IDirect3DDevice7 (13 SetViewport, 14 MultiplyTransform)
constexpr DWORD kClearTarget = 1; // D3DCLEAR_TARGET
// RAII for full-screen windows (character export, save games) that fill the
// 1024 frame with a picture: the render target left and right of the frame is
// cleared black, and the window draws with a viewport on the frame. The
// RAII for full-screen windows (options in a game) that fill the 1024 frame
// with a picture: the menu screens' background (mods/screen.h) goes under
// it, and the window draws with a viewport on the frame. The
// export screen's 3D scene (heroes, background) uses a projection for a
// 1024x768 viewport; across the widened one it was stretched 1.8x wide, away
// from the slot labels and hit areas (measured on a screenshot, 2026-10-18).
// Clear clips to the viewport, so the sides are cleared before it is set.
class FrameScope {
public:
FrameScope(void* window, void* device, int dx) {
const auto* w = static_cast<const uint8_t*>(window);
if (dx == 0 || g_drawScoped || !device || !g_setViewport || !g_clear || (w[kControlFlags] & 1) == 0) {
if (dx == 0 || g_drawScoped || !device || !g_setViewport || (w[kControlFlags] & 1) == 0) {
return;
}
auto getViewport = reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]);
if (FAILED(getViewport(device, &previous_))) {
return;
}
const LONG height = static_cast<LONG>(previous_.height);
Rect7 sides[] = {{0, 0, dx, height},
{dx + kLayoutWidth, 0, mods::widescreen::RenderWidth(), height}};
g_clear(device, 2, sides, kClearTarget, 0, 1.0f, 0);
mods::screen::DrawBackground(device);
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous_.minZ, previous_.maxZ};
device_ = device;
g_setViewport(device, &frame);
@@ -512,6 +566,10 @@ void OnDevice(void* device) {
g_drawIndexedPrimitive = reinterpret_cast<DrawIndexedPrimitiveFn>(vtable[26]);
re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast<void**>(&g_drawIndexedPrimitive),
reinterpret_cast<void*>(&DrawIndexedPrimitiveShifted));
// SetTexture 35.
g_setTexture = reinterpret_cast<SetTextureFn>(vtable[35]);
re::AttachRuntimeHook("D3D7 SetTexture", reinterpret_cast<void**>(&g_setTexture),
reinterpret_cast<void*>(&SetTextureTracked));
// Clear 10, SetViewport 13.
g_clear = reinterpret_cast<ClearFn>(vtable[10]);
re::AttachRuntimeHook("D3D7 Clear", reinterpret_cast<void**>(&g_clear), reinterpret_cast<void*>(&ClearShifted));
@@ -796,6 +854,41 @@ int HandleCentred(void* event, int (*body)(void* context), void* context) {
return body(context);
}
// Offset of the main menu's 1024 frame while it is drawn centred (set each
// menu frame by RenderMenuFrame; 0 while a 16:9 screen draws itself).
std::atomic<int> g_menuFrameDx = 0;
void SetHiddenTextures(void* const* surfaces, int count) {
for (int i = 0; i < kHiddenTextureMax; ++i) {
g_hiddenTextures[i].store(i < count ? surfaces[i] : nullptr, std::memory_order_relaxed);
}
}
int MenuFrameOffsetX() {
return mods::widescreen::MenuIsShowing() ? g_menuFrameDx.load() : 0;
}
int RenderMenuFrame(void* device, bool framed, int (*body)(void* context), void* context) {
const int width = mods::widescreen::RenderWidth();
const int dx = framed && width > kLayoutWidth ? (width - kLayoutWidth) / 2 : 0;
g_menuFrameDx = dx;
Viewport7 previous;
auto getViewport =
device ? reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]) : nullptr;
if (dx == 0 || g_drawScoped || !g_setViewport || !getViewport || FAILED(getViewport(device, &previous))) {
return body(context);
}
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous.minZ, previous.maxZ};
g_setViewport(device, &frame);
g_drawScoped = true; // the windows inside draw in the frame's coordinates
g_offsetX = dx;
const int result = body(context);
g_offsetX = 0;
g_drawScoped = false;
g_setViewport(device, &previous);
return result;
}
int CanvasOffsetX() {
return OffsetFor(Anchor::Canvas);
}
@@ -830,12 +923,17 @@ int __fastcall UiTaskbar_HitTest(void* self, void* edx, int x, int y) {
return UiTaskbar_HitTest_original(self, edx, mods::hud::HitTestX(self, x), y);
}
// The item carried by the mouse cursor (0x6555e0, drawn by the render loop
// after the UI) is placed like UI items: relative to x = 512 of the layout.
// Items are only picked up from centred windows.
// The cursor and the item it carries (0x6555e0, drawn by the render loop
// after the UI) are placed like UI items: relative to x = 512 of the layout.
// Items are only picked up from centred windows. In the main menu the cursor
// is in the centred frame's coordinates (MenuFrameOffsetX).
int __fastcall MouseCursor_DrawItem(void* self, void* edx, int a, int b);
RE_HOOK(0x006555e0, MouseCursor_DrawItem);
int __fastcall MouseCursor_DrawItem(void* self, void* edx, int a, int b) {
if (mods::widescreen::MenuIsShowing()) {
mods::hud::DrawShift shift(mods::hud::MenuFrameOffsetX());
return MouseCursor_DrawItem_original(self, edx, a, b);
}
mods::hud::DrawOffset offset(nullptr, mods::hud::OffsetFor(Anchor::Center));
return MouseCursor_DrawItem_original(mods::hud::CursorFor(self), edx, a, b);
}