From db87e276bfd9aea7355cbb4c40e20cc8149d5139 Mon Sep 17 00:00:00 2001 From: Torhan Bartel Date: Wed, 21 Oct 2026 00:05:55 +0200 Subject: [PATCH] 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) --- docs/characters.md | 14 +++- docs/screens.md | 48 ++++++++++-- docs/todo.md | 14 +++- docs/widescreen.md | 6 +- src/game/ui/draw.h | 45 +++++++++++ src/game/ui/granny.h | 67 ++++++++++++++++ src/game/ui/texture.h | 34 ++++++++ src/mods/borderless.cpp | 1 + src/mods/characters.cpp | 73 +++++++++++++++++- src/mods/hud.cpp | 124 +++++++++++++++++++++++++---- src/mods/hud.h | 16 +++- src/mods/screen.cpp | 167 +++++++++++++++++++++++++++++++++++++--- src/mods/screen.h | 15 +++- src/mods/widescreen.cpp | 31 ++++---- src/mods/widescreen.h | 14 ++-- tools/names.txt | 20 +++++ 16 files changed, 620 insertions(+), 69 deletions(-) create mode 100644 src/game/ui/granny.h create mode 100644 src/game/ui/texture.h diff --git a/docs/characters.md b/docs/characters.md index ccab834..83798bb 100644 --- a/docs/characters.md +++ b/docs/characters.md @@ -189,8 +189,16 @@ below; our own background). Built on the shared screen kit Its entries are only needed for the load. - Left: the characters, newest save first (name, class, level; "im Spiel" for the one being played). Click selects, double click plays. -- Middle: name, class and level, the world played last with its date. (A - render of the hero is planned.) +- Middle: name, class and level, the world played last with its date, and + the hero in 3D with its equipment (2026-10-20): the game's cUI_NetGranny + (game/ui/granny.h), the control the export screen and the multiplayer + lobby showed heroes with. The character's hero file goes to + `_workpreview.pax`, LoadHeroFile (path, 0, info) reads its hero info + (class, equipment looks) without touching the player hero. Drawn without + its name label, with the white light set, size factor 3.8 (game 2.5); its + x is moved by (1365 - 1024) / 2, as it centres the model as in a 1024 + layout. Migrated characters without the shared hero show none until + played. - Right: a grid campaigns x difficulties. A cell shows the save's date, "+ Autosave" when the autosave is newer, "Story abgeschlossen", "Neu" for a difficulty that is open but has no world, "–" when closed. Open: Bronze and @@ -306,5 +314,5 @@ kept, the newest file overall is the shared hero. ## Open - Quickload (row 0) does not know characters (not needed, user 2026-10-20). -- Spielen screen: a render of the hero; scrolling the list beyond 9 characters. +- Spielen screen: scrolling the list beyond 9 characters. - Multiplayer (see `todo.md`). diff --git a/docs/screens.md b/docs/screens.md index 01b38a3..c273648 100644 --- a/docs/screens.md +++ b/docs/screens.md @@ -2,8 +2,12 @@ Request (2026-10-20): the game's menu screens are laid out for 1024x768; we design our own UIs for them. There is no 16:9 artwork for the game, so the -background is our own (option 3 of the ones discussed: dark, no picture), and -the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is +background is our own, and the approach is shared by all menu screens. The +background (user, 2026-10-20: "textured dark", the flat gradient looked +black): the options window's rock wall, `GUI_OPTION_BACKGROUND.TGA` +(4 x 3 tiles of 256), scaled to cover the render target, tinted down, +shaded towards the bottom and the sides. Game textures by name: +`game/ui/texture.h`, drawn with `game::DrawTextured` (game/ui/draw.h). Target: 16:9, 1920x1080, which is 1365x768 in UI units; wider screens only get more background at the sides (not designed for ultrawide). @@ -16,9 +20,7 @@ the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is layer's offset machinery with `Anchor::Canvas`; drawing, the cursor and the window's hit tests shifted alike). Mouse events go through `screen::Handle` (`hud::HandleCanvas`). Keys stay the window's. -- In the main menu the game renders a classic 1024x768 frame (presented 4:3). - While a screen's window is shown, `widescreen::SetWideMenuScreen` makes the - menu thread render and present the full width. +- The main menu thread renders the full width (no 4:3 frame any more). - Parts: `screen::Panel` (dark backdrop and the game's frame, `cUI_Frame`, game/ui/frame.h, made anew when its rect changes), `screen::Button` (a button of the game's with the look of one of the window's, @@ -33,6 +35,42 @@ the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is - Inside the canvas scope the HUD layer does not stretch "full 1024 layout" quads (`g_canvasScope`). +## Main menu frame (2026-10-20) + +Request: the background on all screens, the main menu's and the options +window's own pictures removed. + +- cUI_Manager's UI render (`0x7587b0`, hooked in mods/screen.cpp) on the + menu thread: the background first, then `hud::RenderMenuFrame` runs the + render with drawing shifted by (render width - 1024) / 2 and a viewport on + the frame (3D parts). Windows inside draw in frame coordinates (their own + HUD scopes are nested, so they do nothing). +- Mouse positions are given to the game in frame coordinates: the + borderless mod's `ToGame` subtracts `hud::MenuFrameOffsetX`. The cursor + sprite (`cMouseCursor::DrawItem` 0x6555e0, drawn after the UI) is shifted + by drawing only. +- While a 16:9 screen is shown the frame is not shifted (offset 0): the + screen draws itself across the width. +- Full-screen windows in a game (options, `hud::RenderFramed`) get the + background beside the frame instead of black. +- The windows' pictures are left out by texture: the HUD layer tracks the + texture bound to stage 0 (`SetTexture`, device vtable 35) and skips quads + drawn with a hidden one (`hud::SetHiddenTextures`). Logged on the menu + thread (texture ids requested from the cache, by caller; names by trying + the names in the texture pak): main menu GUI_UW_OPTION1..12 + UI_MAIN_SKY, + options UW_OPTION_1_01..12. This install uses the Underworld versions; the + Ancaria ones (GUI_OPTION1..12, ...) were not seen and are not listed. +- The main menu's 3D scene (sky UI_UW_MAIN_CENTER, water with the SACRED + logo UI_MAIN_REF; render 0x75e620 on the scene object 0x75df10) is called + from cUI_MainMenu::Render (0x7122c0) at 0x712bd9; that call is redirected + to an empty function. cUI_Character (0x70f41b) still draws it. +- Logo top left (2026-10-21), 48 from the edges, 124 high: FX_SACREDLOGO.TGA, + the emblem (horns around a ring of runes) glowing blue on black without + alpha, drawn added (blend ONE/ONE, `game::DrawTexturedAdded`) as the + game's effects draw it. Other candidates: GUI_MAIN_LOGO (plain golden + lettering), SACREDSYMBOL01/02 (model atlases), the emblem on the loading + pictures (PAK\loading*.bmp, not in the paks). Screens may overlap it. + ## Screens - Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen diff --git a/docs/todo.md b/docs/todo.md index 63e132c..113d61d 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -211,7 +211,15 @@ space far better. No 16:9 artwork exists for the game (user, 2026-10-20): we design our own UIs, using the width beyond the 1024 frame (now drawn in a centred 1024x768 viewport with black sides, hud.cpp FrameScope). Design for 16:9 (1920x1080, 1365x768 UI units), not ultrawide (user). Screen done -2026-10-20 (docs/characters.md, "Spielen screen"; docs/screens.md). Open: -the render of the selected hero (the hero panel shows text only; reuse -cUI_Character's 3D hero?), list scrolling beyond 9 characters, larger +2026-10-20 (docs/characters.md, "Spielen screen"; docs/screens.md). Hero +in 3D done 2026-10-20. Open: list scrolling beyond 9 characters, larger heading font. + +## Fine-tuning the Spielen and options screens (2026-10-21) + +The user is very satisfied with the menu rework for now (rock background on +all menu screens, centred menu frame, logo, Spielen screen with the 3D +hero), but both the Spielen screen and the options window still need a lot +of fine-tuning. Details to be collected when we get to it; known so far: +list scrolling beyond 9 characters, larger heading font, the options +window's layout in the width beyond its 1024 frame. diff --git a/docs/widescreen.md b/docs/widescreen.md index a9c9309..bcd4112 100644 --- a/docs/widescreen.md +++ b/docs/widescreen.md @@ -67,9 +67,11 @@ Elsewhere (unknown / UI, leave alone for now): `0x40694a`, `0x4ec036`, | `0x006469d0` | loading screen: GDI BitBlt of bitmap + progress bar + text into the back buffer (fixed 1024x768) | render thread | | `0x006a0c60` | video/intro playback (unexamined) | | -The widescreen mod gives these frames a 1024x768 D3D viewport (set at +The widescreen mod gives the loading screen a 1024x768 D3D viewport (set at `BeginScene` when the scene depth `+0x1d8` is 0) and the borderless mod -presents only that area, scaled to the monitor. +presents only that area, scaled to the monitor. The main menu thread renders +the full width since 2026-10-20: its 1024x768 frame is drawn centred on the +menu screens' background (docs/screens.md, "Main menu frame"). ## Direct3D 7 render target limit diff --git a/src/game/ui/draw.h b/src/game/ui/draw.h index 0da4112..7b928bc 100644 --- a/src/game/ui/draw.h +++ b/src/game/ui/draw.h @@ -10,6 +10,12 @@ namespace game { constexpr int kDeviceDrawPrimitive = 25; // IDirect3DDevice7 vtable slots constexpr int kDeviceSetTexture = 35; +constexpr int kDeviceSetRenderState = 20; +constexpr int kDeviceGetRenderState = 21; +constexpr DWORD kStateSourceBlend = 19; // D3DRENDERSTATETYPE +constexpr DWORD kStateDestinationBlend = 20; +constexpr DWORD kStateAlphaBlendEnable = 27; +constexpr DWORD kBlendOne = 2; // D3DBLEND constexpr DWORD kTriangleStrip = 5; constexpr DWORD kFvfScreenColourTexture = 0x1c4; // XYZRHW | DIFFUSE | SPECULAR | TEX1 @@ -19,6 +25,45 @@ struct ScreenVertex { float u, v; }; +// Draws (x0, y0)-(x1, y1) with `surface` (a texture's Direct3D surface, +// game/ui/texture.h) from (u0, v0) to (u1, v1), tinted by `colour`. +inline void DrawTextured(void* device, void* surface, float x0, float y0, float x1, float y1, float u0, float v0, + float u1, float v1, uint32_t colour = 0xffffffff) { + void** vtable = *static_cast(device); + using SetTextureFn = HRESULT(__stdcall*)(void*, DWORD, void*); + using DrawPrimitiveFn = HRESULT(__stdcall*)(void*, DWORD, DWORD, void*, DWORD, DWORD); + reinterpret_cast(vtable[kDeviceSetTexture])(device, 0, surface); + ScreenVertex quad[4] = {{x0, y0, 0, 1, colour, 0, u0, v0}, + {x1, y0, 0, 1, colour, 0, u1, v0}, + {x0, y1, 0, 1, colour, 0, u0, v1}, + {x1, y1, 0, 1, colour, 0, u1, v1}}; + reinterpret_cast(vtable[kDeviceDrawPrimitive])(device, kTriangleStrip, kFvfScreenColourTexture, + quad, 4, 0); + reinterpret_cast(vtable[kDeviceSetTexture])(device, 0, nullptr); +} + +// DrawTextured added onto what is drawn (source and destination blend ONE): +// for the effect textures, drawn on black without alpha. +inline void DrawTexturedAdded(void* device, void* surface, float x0, float y0, float x1, float y1, float u0, + float v0, float u1, float v1) { + void** vtable = *static_cast(device); + using SetRenderStateFn = HRESULT(__stdcall*)(void*, DWORD, DWORD); + using GetRenderStateFn = HRESULT(__stdcall*)(void*, DWORD, DWORD*); + const auto set = reinterpret_cast(vtable[kDeviceSetRenderState]); + const auto get = reinterpret_cast(vtable[kDeviceGetRenderState]); + DWORD blending = 0, source = 0, destination = 0; + get(device, kStateAlphaBlendEnable, &blending); + get(device, kStateSourceBlend, &source); + get(device, kStateDestinationBlend, &destination); + set(device, kStateAlphaBlendEnable, TRUE); + set(device, kStateSourceBlend, kBlendOne); + set(device, kStateDestinationBlend, kBlendOne); + DrawTextured(device, surface, x0, y0, x1, y1, u0, v0, u1, v1); + set(device, kStateAlphaBlendEnable, blending); + set(device, kStateSourceBlend, source); + set(device, kStateDestinationBlend, destination); +} + // Fills (x0, y0)-(x1, y1), ARGB colours per corner: top left, top right, // bottom left, bottom right (blended between them). inline void FillCorners(void* device, float x0, float y0, float x1, float y1, uint32_t topLeft, uint32_t topRight, diff --git a/src/game/ui/granny.h b/src/game/ui/granny.h new file mode 100644 index 0000000..3f9d12d --- /dev/null +++ b/src/game/ui/granny.h @@ -0,0 +1,67 @@ +#pragma once +// cUI_NetGranny (vtable 0x894c8c, 0x634 bytes, window name UI_WND_GRANNY): the +// game's 3D view of a hero with its equipment, as the export screen's slots +// (cUI_Character +0x1b4, 0x710f31) and the multiplayer lobby showed them. +// +// The model comes from a hero info (0x22c bytes: class at +4, the equipment's +// looks), which LoadHeroFile (0x61d190, cdecl (path, 0, info)) reads from a +// hero file without touching the player hero (0x70bf2c). +// +// Render (vf14 -> vf44 0x6f17f0 (device, 0, 1), only while visible; the last +// argument adds the name and level below the model, 0x6f1260). With +0x53d +// clear the model stands at the rect's horizontal centre, its feet at +// rect.y + h * k, projected with the creatures' ortho camera (x -512..512 +// around the screen centre, widened by the widescreen mod); size factor +// +0x630 (2.5 from the constructor). Two lights are made on the first render +// (0x6f0f10): a dim blue set (the lobby) with +0x53f clear, a white one with +// it set. With +0x53d set (cUI_Character) the model is placed in a +// perspective scene instead. + +#include +#include + +#include "game/ui/tabs.h" +#include "game/ui/text.h" + +namespace game { + +constexpr size_t kNetGrannySize = 0x634; +constexpr size_t kNetGrannyInScene = 0x53d; // u8, see above +constexpr size_t kNetGrannyWhiteLight = 0x53f; // u8, before the first render +constexpr size_t kNetGrannyScale = 0x630; // float +constexpr size_t kHeroInfoSize = 0x22c; + +// cUI_NetGranny::cUI_NetGranny (0x6f06f0, thiscall (rect by value), ret 0xc). +inline uint8_t* NewNetGranny(UiRect rect) { + void* memory = GameNew(kNetGrannySize); + using ConstructFn = uint8_t*(__fastcall*)(void*, void*, UiRect); + return memory ? reinterpret_cast(0x006f06f0)(memory, nullptr, rect) : nullptr; +} + +// The model alone, without its name and level (vf44, thiscall (device, +// 0, label), ret 0xc). +inline void RenderNetGranny(uint8_t* granny, void* device) { + using RenderFn = void(__fastcall*)(void*, void*, void*, uint32_t, uint8_t); + reinterpret_cast((*reinterpret_cast(granny))[0x44 / 4])(granny, nullptr, device, 0, 0); +} + +// Shows the hero of `info` (0x6f4b20, thiscall (info, x, y, z, turn), ret +// 0x14; the four floats place it in the perspective scene only). +inline void SetNetGrannyHero(uint8_t* granny, const void* info) { + using SetFn = void(__fastcall*)(void*, void*, const void*, float, float, float, float); + reinterpret_cast(0x006f4b20)(granny, nullptr, info, 0.0f, 0.0f, 0.0f, 0.0f); +} + +// No hero (0x6f4ae0, thiscall ()). +inline void ClearNetGrannyHero(uint8_t* granny) { + reinterpret_cast(0x006f4ae0)(granny, nullptr); +} + +// The hero info of a hero file (a path as the game's file functions take it); +// false if it cannot be read. `info` holds kHeroInfoSize bytes. +inline bool ReadHeroInfo(const char* path, void* info) { + using LoadFn = uint8_t(__cdecl*)(const char*, uint32_t, void*); + return reinterpret_cast(0x0061d190)(path, 0, info) != 0; +} + +} // namespace game diff --git a/src/game/ui/texture.h b/src/game/ui/texture.h new file mode 100644 index 0000000..252894f --- /dev/null +++ b/src/game/ui/texture.h @@ -0,0 +1,34 @@ +#pragma once +// The game's textures by name (a .TGA in the texture paks), as cGranny's +// effects load them (0x401232): the texture manager (0x656c80, cdecl (), +// created on first use, kept at 0x13e57b4) gives the id of a name (0x657620, +// thiscall (name), ret 4); the texture cache (0x13e57b8) loads it on demand +// (0x65f010, thiscall (id, 0), ret 8), its Direct3D surface at +0x14 (as +// cUI_Button's render binds it, 0x72afb7). + +#include +#include + +namespace game { + +constexpr uintptr_t kTextureCache = 0x013e57b8; +constexpr size_t kTextureSurface = 0x14; + +inline uint32_t TextureId(const char* name) { + void* manager = reinterpret_cast(0x00656c80)(); + using IdFn = uint32_t(__fastcall*)(void*, void*, const char*); + return manager ? reinterpret_cast(0x00657620)(manager, nullptr, name) : 0; +} + +// The Direct3D surface of texture `id`, loaded if needed; null if none. +inline void* TextureSurface(uint32_t id) { + void* cache = *reinterpret_cast(kTextureCache); + if (!cache || !id) { + return nullptr; + } + using GetFn = uint8_t*(__fastcall*)(void*, void*, uint32_t, uint32_t); + uint8_t* texture = reinterpret_cast(0x0065f010)(cache, nullptr, id, 0); + return texture ? *reinterpret_cast(texture + kTextureSurface) : nullptr; +} + +} // namespace game diff --git a/src/mods/borderless.cpp b/src/mods/borderless.cpp index 9f557a5..f86965c 100644 --- a/src/mods/borderless.cpp +++ b/src/mods/borderless.cpp @@ -138,6 +138,7 @@ POINT ToGame(POINT p) { POINT result; result.x = std::clamp(MulDiv(p.x - layout.client.left, layout.gameWidth, width), 0, layout.gameWidth - 1); result.y = std::clamp(MulDiv(p.y - layout.client.top, layout.gameHeight, height), 0, layout.gameHeight - 1); + result.x -= mods::hud::MenuFrameOffsetX(); // the main menu's centred frame return result; } diff --git a/src/mods/characters.cpp b/src/mods/characters.cpp index 9e76b60..13916b2 100644 --- a/src/mods/characters.cpp +++ b/src/mods/characters.cpp @@ -49,6 +49,7 @@ #include "game/ui/button.h" #include "game/ui/control.h" #include "game/ui/events.h" +#include "game/ui/granny.h" #include "game/ui/text.h" #include "game/ui/texts.h" #include "game/ui/windows.h" @@ -386,6 +387,8 @@ std::atomic g_autosavePending{false}; bool g_inGame = false; // a game runs (the load window opens from the Esc menu too) std::atomic g_lastSaveAt{0}; // GetTickCount64 of the last save of any kind +void ForgetHeroModel(); // the files may have changed: read the hero anew + std::string Description(const json& meta) { return Convert(FromUtf8(meta.value("name", std::string())) + L" - " + ClassName(meta.value("class", 0u)), CP_ACP); @@ -417,6 +420,7 @@ void RebuildList() { DeleteFileA((work + file).c_str()); } g_rows.clear(); + ForgetHeroModel(); for (const std::string& name : FilesIn(CharactersDir(), "*.sacredK")) { if (EndsWith(name, kAutosaveExtension) || g_rows.size() >= kMaxRows) { continue; @@ -616,8 +620,10 @@ std::atomic g_loadRow{-1}; // Layout on the canvas (1365x768): three panels side by side (sizes in the // frames' 64 steps, screen::kFrameStep), centred, a row of buttons below. -constexpr int kPanelTop = 96; // the title above the frames' ornament -constexpr int kPanelHeight = 8 * screen::kFrameStep; +// Top: room for the heading and the frames' ornament; the buttons end +// above the screen's bottom. +constexpr int kPanelTop = 164 + screen::kFrameOrnament; +constexpr int kPanelHeight = 7 * screen::kFrameStep; constexpr int kListWidth = 5 * screen::kFrameStep; constexpr int kHeroWidth = 6 * screen::kFrameStep; constexpr int kWorldsWidth = 6 * screen::kFrameStep; @@ -1329,6 +1335,64 @@ void DrawList(void* device) { } } +// The selected character's hero in 3D (game/ui/granny.h): its hero file is +// unpacked to the game's save folder (_work) and read as a hero info. A +// migrated character without the shared hero has none until it is played. +constexpr float kHeroModelScale = 3.8f; // the game's 2.5 fills a third of the panel +constexpr int kHeroModelFeetAboveBottom = 24; +constexpr char kWorkPreviewHero[] = "preview.pax"; +constexpr char kGamePreviewHero[] = "Save\\preview.pax"; + +struct HeroModel { + uint8_t* control = nullptr; + std::string file; // whose hero it shows + bool hasHero = false; +} g_heroModel; + +void ForgetHeroModel() { + g_heroModel.file.clear(); +} + +void ShowHeroOf(const Row& row) { + if (row.file == g_heroModel.file) { + return; + } + g_heroModel.file = row.file; + g_heroModel.hasHero = false; + CharacterFile character(row.file); + std::optional hero = character.Open() ? character.Read("hero") : std::nullopt; + std::vector info(game::kHeroInfoSize); + if (hero && WriteFileReplacing(WorkDir() + kWorkPreviewHero, *hero) && + game::ReadHeroInfo(kGamePreviewHero, info.data())) { + game::SetNetGrannyHero(g_heroModel.control, info.data()); + g_heroModel.hasHero = true; + } else { + game::ClearNetGrannyHero(g_heroModel.control); + } + DeleteFileA((WorkDir() + kWorkPreviewHero).c_str()); +} + +// The model stands centred in `rect` (canvas coordinates). The control centres +// it as in a 1024 wide layout (x - 512 around the screen centre); the canvas +// is centred too, so its x is moved by the difference. +void DrawHeroModel(void* device, const Row& row, game::UiRect rect) { + rect.x -= (screen::kWidth - 1024) / 2; + if (!g_heroModel.control) { + g_heroModel.control = game::NewNetGranny(rect); + if (!g_heroModel.control) { + return; + } + g_heroModel.control[game::kNetGrannyWhiteLight] = 1; + *reinterpret_cast(g_heroModel.control + game::kNetGrannyScale) = kHeroModelScale; + game::SetControlShown(g_heroModel.control, true); + } + *reinterpret_cast(g_heroModel.control + game::kControlRect) = rect; + ShowHeroOf(row); + if (g_heroModel.hasHero) { + game::RenderNetGranny(g_heroModel.control, device); + } +} + void DrawHero(void* device) { const game::UiRect content = g_heroPanel.Draw(device, kHeroRect); const Row* row = SelectedCharacter(); @@ -1348,6 +1412,9 @@ void DrawHero(void* device) { line(CampaignName(last->campaign) + L" · " + DifficultyName(last->difficulty), screen::kTextColour); line(FormatTime(last->savedAt), screen::kDimColour); } + // The control puts the feet at 5/6 of its rect's height (0x88f04c). + const int feet = content.y + content.h - kHeroModelFeetAboveBottom; + DrawHeroModel(device, *row, {content.x, y, content.w, static_cast((feet - y) * 6 / 5)}); } void DrawCell(void* device, const Row& row, int campaign, int difficulty) { @@ -1375,7 +1442,7 @@ void DrawCell(void* device, const Row& row, int campaign, int difficulty) { if (cell.autosave && cell.world) { screen::Text(device, bottom, L"+ Autosave", screen::kHeadingColour, game::kTextCenter); } else if (row.storyCompleted[difficulty]) { - screen::Text(device, bottom, L"Story abgeschlossen", screen::kDimColour, game::kTextCenter); + screen::Text(device, bottom, L"abgeschlossen", screen::kDimColour, game::kTextCenter); } } diff --git a/src/mods/hud.cpp b/src/mods/hud.cpp index 2fee5d5..2b45fab 100644 --- a/src/mods/hud.cpp +++ b/src/mods/hud.cpp @@ -21,6 +21,7 @@ // Enabled=1 #include +#include #include #include #include @@ -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 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(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((*static_cast(device))[kVtableGetViewport]); if (FAILED(getViewport(device, &previous_))) { return; } - const LONG height = static_cast(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(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(vtable[26]); re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast(&g_drawIndexedPrimitive), reinterpret_cast(&DrawIndexedPrimitiveShifted)); + // SetTexture 35. + g_setTexture = reinterpret_cast(vtable[35]); + re::AttachRuntimeHook("D3D7 SetTexture", reinterpret_cast(&g_setTexture), + reinterpret_cast(&SetTextureTracked)); // Clear 10, SetViewport 13. g_clear = reinterpret_cast(vtable[10]); re::AttachRuntimeHook("D3D7 Clear", reinterpret_cast(&g_clear), reinterpret_cast(&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 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((*static_cast(device))[kVtableGetViewport]) : nullptr; + if (dx == 0 || g_drawScoped || !g_setViewport || !getViewport || FAILED(getViewport(device, &previous))) { + return body(context); + } + Viewport7 frame = {static_cast(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); } diff --git a/src/mods/hud.h b/src/mods/hud.h index 697adcb..1184833 100644 --- a/src/mods/hud.h +++ b/src/mods/hud.h @@ -26,8 +26,8 @@ int VisibleDialogTooltips(void** out, int max); // shifted) or event handler (mouse position shifted by the window's offset). int RenderCentred(void* window, int (*body)(void* context), void* context); int HandleCentred(void* event, int (*body)(void* context), void* context); -// RenderCentred for full-screen windows: black beside the 1024 frame, drawn -// with a viewport on it (as the save game and export screens). +// RenderCentred for full-screen windows: the menu screens' background beside +// the 1024 frame, drawn with a viewport on it (as the options in a game). int RenderFramed(void* window, void* device, int (*body)(void* context), void* context); // 16:9 screens (mods/screen.h): laid out on a 1365x768 canvas, centred in @@ -40,4 +40,16 @@ int CanvasOffsetX(); int RenderCanvas(void* window, int (*body)(void* context), void* context); int HandleCanvas(void* event, int (*body)(void* context), void* context); +// The main menu (mods/screen.cpp): with `framed`, body(context) draws the +// menu's 1024x768 frame centred in the render width (shifted, with a +// viewport on it); mouse positions are then given in frame coordinates +// (MenuFrameOffsetX, applied by the borderless mod). Without, the menu draws +// unshifted (a 16:9 screen is shown). +int RenderMenuFrame(void* device, bool framed, int (*body)(void* context), void* context); +int MenuFrameOffsetX(); + +// Quads drawn with one of these textures (Direct3D surfaces) are left out, +// on every thread, until the list changes (at most 64). +void SetHiddenTextures(void* const* surfaces, int count); + } // namespace mods::hud diff --git a/src/mods/screen.cpp b/src/mods/screen.cpp index d59e653..23cbde8 100644 --- a/src/mods/screen.cpp +++ b/src/mods/screen.cpp @@ -6,25 +6,38 @@ #include #include #include +#include #include +#include "game/addresses.h" +#include "game/render/dx_driver7.h" #include "game/ui/button.h" #include "game/ui/control.h" #include "game/ui/draw.h" #include "game/ui/events.h" #include "game/ui/frame.h" +#include "game/ui/texture.h" #include "game/ui/texts.h" #include "mods/widescreen.h" +#include "re/hook.h" +#include "re/patch.h" namespace mods::screen { namespace { -// Background (decided 2026-10-20: our own, no picture): dark, a little warm -// at the top, darker towards the bottom and the sides. -constexpr uint32_t kBackgroundTop = 0xff1c1712; -constexpr uint32_t kBackgroundBottom = 0xff070605; -constexpr uint32_t kVignette = 0xa0000000; +// Background (user, 2026-10-20: "textured dark", then "doesn't need to be that +// dark, but apply it to all screens"): the options window's rock wall, +// GUI_OPTION_BACKGROUND.TGA (4 x 3 tiles of 256, a 1024x768 +// picture), scaled to cover the render target, a little darkened, darker +// towards the bottom and the sides. +constexpr int kBackgroundColumns = 4; +constexpr int kBackgroundRows = 3; +constexpr float kBackgroundTile = 256.0f; +constexpr uint32_t kBackgroundTint = 0xffa0a0a0; +constexpr uint32_t kShadeTop = 0x10000000; +constexpr uint32_t kShadeBottom = 0x70000000; +constexpr uint32_t kVignette = 0x70000000; constexpr int kVignetteWidth = 240; constexpr uint32_t kPanelBackdrop = 0xb4000000; constexpr int kHeadingHeight = 30; @@ -33,8 +46,8 @@ constexpr int kHeadingHeight = 30; // frames). std::atomic g_frame{1}; -// The windows whose screen is shown: the menu thread renders the full width -// while any is. +// The windows whose screen is shown: the main menu's frame is not drawn +// centred while any is (the screen draws itself across the width). std::mutex g_shownLock; std::set g_shown; @@ -45,19 +58,98 @@ void NoteShown(const void* window, bool shown) { } else { g_shown.erase(window); } - widescreen::SetWideMenuScreen(!g_shown.empty()); +} + +// The windows' own pictures, replaced by the background (user, 2026-10-20: +// "the previous backgrounds from settings and mainMenu can be removed"), 12 +// tiles each (logged: the textures the menu thread asks for, by name): the +// main menu GUI_UW_OPTION<1..12>.TGA with the sky above it UI_MAIN_SKY.TGA, +// the options window UW_OPTION_1_<01..12>.TGA. The main menu's 3D scene +// behind its picture is not drawn either (game::RemoveMenuScene). +std::vector PictureTextureIds() { + std::vector ids = {game::TextureId("UI_MAIN_SKY.TGA")}; + char name[40]; + for (int i = 1; i <= 12; ++i) { + wsprintfA(name, "GUI_UW_OPTION%d.TGA", i); + ids.push_back(game::TextureId(name)); + wsprintfA(name, "UW_OPTION_1_%02d.TGA", i); + ids.push_back(game::TextureId(name)); + } + return ids; +} + +// Their surfaces (loaded now, as the window is about to draw them; the cache +// may have made them anew) go to the HUD layer's hidden list. +void HidePictures() { + static const std::vector ids = PictureTextureIds(); + std::vector surfaces; + for (uint32_t id : ids) { + if (void* surface = game::TextureSurface(id)) { + surfaces.push_back(surface); + } + } + hud::SetHiddenTextures(surfaces.data(), static_cast(surfaces.size())); +} + +bool ShownAny() { + std::lock_guard guard(g_shownLock); + return !g_shown.empty(); +} + +// The game's logo top left (user, 2026-10-21: "the bright blueish one"), +// kLogoMargin from the edges: FX_SACREDLOGO.TGA, the emblem (horns around a +// ring of runes) glowing blue on black, no alpha, added onto the background as +// the game's effects draw it; the glow within u kLogoU0..U1 of the square +// texture, the full height (measured on a screenshot of it drawn whole). +constexpr char kLogoTexture[] = "FX_SACREDLOGO.TGA"; +constexpr float kLogoU0 = 0.13f, kLogoU1 = 0.87f; +static_assert(kLogoWidth == static_cast(kLogoHeight * (kLogoU1 - kLogoU0))); + +void DrawLogo(void* device) { + if (void* logo = game::TextureSurface(game::TextureId(kLogoTexture))) { + const float edge = static_cast(kLogoMargin); + game::DrawTexturedAdded(device, logo, edge, edge, edge + kLogoWidth, edge + kLogoHeight, kLogoU0, 0, kLogoU1, + 1); + } +} + +void DrawRockWall(void* device, float width, float height) { + const float scale = std::max(width / (kBackgroundColumns * kBackgroundTile), + height / (kBackgroundRows * kBackgroundTile)); + const float tile = kBackgroundTile * scale; + const float left = (width - kBackgroundColumns * tile) / 2; + const float top = (height - kBackgroundRows * tile) / 2; + for (int row = 0; row < kBackgroundRows; ++row) { + for (int column = 0; column < kBackgroundColumns; ++column) { + char name[40]; + wsprintfA(name, "GUI_OPTION_BACKGROUND%d%d.TGA", row, column); + if (void* surface = game::TextureSurface(game::TextureId(name))) { + const float x = left + column * tile, y = top + row * tile; + game::DrawTextured(device, surface, x, y, x + tile, y + tile, 0, 0, 1, 1, kBackgroundTint); + } + } + } +} + + +} // namespace + +bool AnyShown() { + return ShownAny(); } void DrawBackground(void* device) { const float width = static_cast(std::max(widescreen::RenderWidth(), kWidth)); const float height = static_cast(kHeight); - game::FillVertical(device, 0, 0, width, height, kBackgroundTop, kBackgroundBottom); + HidePictures(); + game::FillRect(device, 0, 0, width, height, 0xff000000); + DrawRockWall(device, width, height); + game::FillVertical(device, 0, 0, width, height, kShadeTop, kShadeBottom); game::FillHorizontal(device, 0, 0, kVignetteWidth, height, kVignette, 0); game::FillHorizontal(device, width - kVignetteWidth, 0, width, height, 0, kVignette); + DrawLogo(device); } -} // namespace - int Render(void* window, void* device, int (*body)(void* context), void* context) { const bool shown = game::ControlShown(window); NoteShown(window, shown); @@ -151,4 +243,57 @@ bool DoubleClick::Click(int target) { return twice; } + } // namespace mods::screen + +// The main menu (cUI_Manager's UI render 0x7587b0, thiscall (1 argument), ret +// 4, from the menu thread loop 0x756aa1 and the in-game render 0x60ea74): on +// the menu thread the background goes first, then the menu's 1024x768 frame +// is drawn centred over it (hud::RenderMenuFrame); a 16:9 screen draws its own. +namespace game { + +uint32_t __fastcall UiManager_Render(void* self, void* edx, uint32_t argument); +RE_HOOK(0x007587b0, UiManager_Render); +uint32_t __fastcall UiManager_Render(void* self, void* edx, uint32_t argument) { + using namespace mods; + if (!widescreen::IsMenuThread() || widescreen::IsClassicFrame() || widescreen::RenderWidth() == 0) { + return UiManager_Render_original(self, edx, argument); + } + auto* driver = *reinterpret_cast(addr::g_dxDriver); + void* device = driver ? driver->device : nullptr; + const bool framed = !screen::AnyShown(); + if (framed && device) { + screen::DrawBackground(device); + } + struct Call { + void* self; + void* edx; + uint32_t argument; + uint32_t result; + } call{self, edx, argument, 0}; + hud::RenderMenuFrame( + device, framed, + [](void* context) { + auto* c = static_cast(context); + c->result = UiManager_Render_original(c->self, c->edx, c->argument); + return 0; + }, + &call); + return call.result; +} + + +// cUI_MainMenu::Render (vf14 0x7122c0) draws a 3D scene (sky UI_UW_MAIN_CENTER, +// water UI_MAIN_REF; 0x75e620, thiscall (1 argument), ret 4, on the scene +// object 0x75df10) behind its picture: the call at 0x712bd9 goes nowhere. +constexpr uint32_t kMainMenuSceneCall = 0x00712bd9; +constexpr uint32_t kRenderMenuScene = 0x0075e620; + +void __fastcall SkipMenuScene(void*, void*, uint32_t) {} + +void RemoveMenuScene() { + re::RedirectCall(kMainMenuSceneCall, kRenderMenuScene, reinterpret_cast(&SkipMenuScene)); +} +RE_STARTUP(MenuSceneStartup, RemoveMenuScene); + +} // namespace game diff --git a/src/mods/screen.h b/src/mods/screen.h index d758f3d..18df739 100644 --- a/src/mods/screen.h +++ b/src/mods/screen.h @@ -33,6 +33,12 @@ constexpr int kMargin = kFrameOrnament + 16; // canvas edge to a panel constexpr int kPanelGap = 96; // between two panels' rects, as the options window's columns constexpr int kPanelPadding = 16; // panel rect to its content +// The logo in the background's top left corner (render target coordinates; +// screens may overlap it). +constexpr int kLogoMargin = 48; +constexpr int kLogoHeight = 124; +constexpr int kLogoWidth = kLogoHeight * 74 / 100; // the emblem's aspect (screen.cpp) + constexpr uint16_t kFont = 1; constexpr uint32_t kTextColour = 0xffe6d6aa; // as the options' texts constexpr uint32_t kHeadingColour = 0xfff0c060; // as the options' headings @@ -41,9 +47,14 @@ constexpr uint32_t kRowColour = 0x40302418; constexpr uint32_t kRowHoverColour = 0x80403020; constexpr uint32_t kRowSelectedColour = 0xc0604818; +// The background of the menu screens across the whole render target (also +// beside the main menu's centred frame and full-screen windows in a game). +void DrawBackground(void* device); +// Whether a screen is shown (the main menu then does not draw centred). +bool AnyShown(); + // Draws the screen of `window`: the background across the whole render -// target, then body(context) in canvas coordinates. While `window` is shown -// the main menu thread renders the full width (widescreen::SetWideMenuScreen). +// target, then body(context) in canvas coordinates. int Render(void* window, void* device, int (*body)(void* context), void* context); // Runs body(context) with the event's mouse position in canvas coordinates. int Handle(void* event, int (*body)(void* context), void* context); diff --git a/src/mods/widescreen.cpp b/src/mods/widescreen.cpp index 5372eac..11ea663 100644 --- a/src/mods/widescreen.cpp +++ b/src/mods/widescreen.cpp @@ -63,14 +63,14 @@ bool g_pushesPatched = false; int g_renderWidth = 0; int g_renderHeight = 0; -// Frames from the main menu thread and the loading screen are laid out for -// 1024x768 ("classic"); everything else is the widened in-game view. +// The loading screen is laid out for 1024x768 ("classic"); the main menu +// thread renders the full width too (its windows drawn centred by +// mods/screen.cpp), the in-game view is widened. std::atomic g_menuThread = 0; -// Whether the most recent scene (on any thread) was a classic one: the main -// menu is showing. UI input is processed outside the menu thread. -std::atomic g_lastSceneClassic = false; +// Whether the most recent scene (on any thread) was the main menu's or the +// loading screen's. UI input is processed outside the menu thread. +std::atomic g_lastSceneMenu = false; thread_local int g_loadingScreenDepth = 0; -std::atomic g_wideMenuScreen = false; // D3DVIEWPORT7 (d3d.h is not in current Windows SDKs). struct Viewport7 { @@ -340,18 +340,15 @@ int RenderWidth() { } bool MenuIsShowing() { - return g_lastSceneClassic; + return g_lastSceneMenu; } bool IsClassicFrame() { - if (g_renderWidth == 0) { - return false; - } - return g_loadingScreenDepth > 0 || (GetCurrentThreadId() == g_menuThread.load() && !g_wideMenuScreen); + return g_renderWidth != 0 && g_loadingScreenDepth > 0; } -void SetWideMenuScreen(bool shown) { - g_wideMenuScreen = shown; +bool IsMenuThread() { + return GetCurrentThreadId() == g_menuThread.load(); } } // namespace mods::widescreen @@ -376,13 +373,13 @@ void __fastcall DxDriver7_LoadingScreen(DxDriver7* self, void* edx, uint32_t pro --g_loadingScreenDepth; } -// Menus draw their 3D parts for a 1024x768 screen: give them a 1024x768 -// viewport (the borderless mod then presents just that area, scaled), and the -// world the full render size. +// The loading screen draws for a 1024x768 screen: give it a 1024x768 +// viewport (the borderless mod then presents just that area, scaled), and +// everything else the full render size. int __fastcall DxDriver7_BeginScene(DxDriver7* self, void* edx) { mods::hud::OnDevice(self->device); if (self->sceneDepth == 0) { - g_lastSceneClassic = IsClassicFrame(); + g_lastSceneMenu = IsClassicFrame() || IsMenuThread(); } if (g_renderWidth != 0 && self->sceneDepth == 0 && self->device) { const bool classic = IsClassicFrame(); diff --git a/src/mods/widescreen.h b/src/mods/widescreen.h index c82b0a2..6a10c6a 100644 --- a/src/mods/widescreen.h +++ b/src/mods/widescreen.h @@ -11,20 +11,18 @@ namespace mods::widescreen { // the world view to match. No-op when disabled. void ApplyRenderSize(game::DxDisplayMode& mode, HWND window); -// True while the current thread renders a frame laid out for 1024x768 (main -// menu, loading screen); the borderless mod then presents only that area. +// True while the current thread renders a frame laid out for 1024x768 (the +// loading screen); the borderless mod then presents only that area. bool IsClassicFrame(); // Applied render width (0 while the original 1024x768 is in use). int RenderWidth(); -// True while the main menu / loading screen (classic layout) is on screen, -// regardless of the calling thread. +// True while the main menu or the loading screen is on screen, regardless of +// the calling thread. bool MenuIsShowing(); -// While a 16:9 menu screen (mods/screen.h) is shown from the main menu, the -// menu thread renders and presents the full render size instead of the -// classic 1024x768. -void SetWideMenuScreen(bool shown); +// True on the main menu's render thread (cUI_Manager's thread loop). +bool IsMenuThread(); } // namespace mods::widescreen diff --git a/tools/names.txt b/tools/names.txt index 831513b..e494f73 100644 --- a/tools/names.txt +++ b/tools/names.txt @@ -351,3 +351,23 @@ 0x00711fa0 cUI_MainMenu::HandleEvent # vf10: sets g_campaign 0x182cdc8 by entry name (NEWGAME 0, NEWADDON 1) 0x00753d70 cUI_StaticText64FX::cUI_StaticText64FX # (name, rect, parent, ?, text id, command, flags) 0x0070e2a0 cUI_Character::Show # vf24 (shown), ret 4: single player + g_campaign 1 -> mode 7 (Underworld import, level 25+) + +# cUI_NetGranny: 3D hero view (game/ui/granny.h) +0x006f06f0 cUI_NetGranny::cUI_NetGranny # (rect by value), ret 0xc +0x006f17f0 cUI_NetGranny::Render # vf44 (device, 0, label), ret 0xc +0x006f4b20 cUI_NetGranny::SetHero # (hero info, x, y, z, turn), ret 0x14 +0x006f4ae0 cUI_NetGranny::ClearHero +0x006f0f10 cUI_NetGranny::SetLights # (on): +0x53f white set +0x006f4d70 cUI_NetGranny::InitCamera +0x0070beb0 cUI_Character::LoadExportedHeroes # Hero00..07.pax -> slot grannies +0x1b4 + +# Textures by name (game/ui/texture.h) +0x00656c80 TextureManager_Instance # cdecl (), kept at 0x13e57b4 +0x00657620 TextureManager::IdOf # (name), ret 4 +0x0065f010 TextureCache::Get # (id, 0), ret 8; cache at 0x13e57b8, D3D surface +0x14 + +# Main menu (mods/screen.cpp) +0x007122c0 cUI_MainMenu::Render # vf14 +0x0075df10 MenuScene_Instance # 0x1a84 bytes, kept at 0x17ecb40 +0x0075e620 MenuScene::Render # (1 argument), ret 4: sky + water (UI_UW_MAIN_CENTER, UI_MAIN_REF) +0x0075e820 MenuScene::Update # (seconds)