Map: zoomed-view symbols centred, 10x reveal and enemy range; progress screen on the menu background

- Megamap / TAB map zoomed view: heroes' markers and the other symbols
  (integer 200h centre immediates) follow the view's centre.
- Map reveal: the hero's reveal stamp scaled 10x (mods/mapreveal.cpp,
  docs/map-reveal.md); creatures on the zoomed map shown within 10x the
  sight range.
- Load / save progress screen drawn on the rock background across the full
  width, the picture blended over it (mods/loadingscreen.cpp); the classic
  1024x768 frame is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-25 02:07:59 +02:00
1 parent 3f03228c29
commit b452558122
18 files changed
+435 -40

No files matched your search

+2 -1
View File
@@ -16,7 +16,8 @@ constexpr uint32_t GetMouseClientPos = 0x0066e500;
constexpr uint32_t MainWindowProc = 0x008122a0;
constexpr uint32_t DxDriver7_SetDisplayMode = 0x00645390;
constexpr uint32_t DxDriver7_Flip = 0x00645f70;
constexpr uint32_t DxDriver7_BeginScene = 0x00646f50;
constexpr uint32_t DxDriver7_BeginScene = 0x00646f50; // thiscall, plain ret; nests (+0x1d8)
constexpr uint32_t DxDriver7_EndScene = 0x00646fd0; // thiscall, plain ret
constexpr uint32_t DxDriver7_LoadingScreen = 0x006469d0; // GDI progress screen, 1024x768
constexpr uint32_t UiManager_ThreadLoop = 0x00756940; // main menu render loop (own thread)
constexpr uint32_t Engine_RenderThreadLoop = 0x0060e3f0; // in-game render loop
+47
View File
@@ -0,0 +1,47 @@
#pragma once
// The explored map (fog of war on the large map and the TAB map), measured
// 2026-10-25. docs/map-reveal.md.
//
// Each sector (64x64 tiles) holds a 32x32 bit mask at +0x180 (one bit per 2x2
// tiles, 128 bytes; Sector_SetExplored 0x622bf0, copied out by 0x622c60 and
// in from the save by 0x622c80). The zoomed map view builds its fog texture
// (FOGOFWAR01.TGA) from it (0x639030).
#include <cstdint>
namespace game {
// The world's sector grid (ecx of World_SetExplored).
inline void* World() { return *reinterpret_cast<void**>(0x00ad14e0); }
// World_SetExplored (0x638f00, thiscall (tile x, tile y, explored), ret 0xc):
// sets the bit of the tile's sector; tiles outside the world (>= 0x2000) or in
// a sector that isn't loaded are ignored.
inline void SetExplored(int tileX, int tileY) {
using Fn = void(__fastcall*)(void*, void*, int, int, int);
reinterpret_cast<Fn>(0x00638f00)(World(), nullptr, tileX, tileY, 1);
}
// RevealAroundHero (0x599730, creature thiscall (tile x, tile y, sight), ret
// 0xc): for the local hero, when its tile changed (last tile 0xad2384 /
// 0xad2388), sets every tile of a stamp centred on it explored. Sight 0 (day):
// a 29x29 circle (radius 14); sight 1..3 (night, weather; chosen by the
// callers from the weather object 0x417e50) a 15x15 stamp, the smaller ones
// (radius 7, 5, 3) at 0x88f328 + (sight - 1) * 225.
constexpr uintptr_t kRevealAroundHero = 0x00599730;
inline int32_t LastRevealTileX() { return *reinterpret_cast<const int32_t*>(0x00ad2384); }
inline int32_t LastRevealTileY() { return *reinterpret_cast<const int32_t*>(0x00ad2388); }
struct RevealStamp {
const uint8_t* cells; // size x size bytes, non-zero = revealed
int size;
};
inline RevealStamp RevealStampFor(int sight) {
if (sight <= 0) {
return {reinterpret_cast<const uint8_t*>(0x0088f5cc), 29};
}
return {reinterpret_cast<const uint8_t*>(0x0088f328) + (sight - 1) * 15 * 15, 15};
}
} // namespace game
+1 -11
View File
@@ -56,14 +56,12 @@ struct Viewport {
HWND window = nullptr;
bool runInBackground = false;
Layout full; // the whole back buffer
Layout classic; // its top-left 1024x768 (menus, loading screen; widescreen mod)
};
Viewport g_viewport;
std::atomic<bool> g_classicShown = false; // which layout the last presented frame used
const Layout& CurrentLayout() {
return g_classicShown ? g_viewport.classic : g_viewport.full;
return g_viewport.full;
}
bool Enabled() {
@@ -116,10 +114,8 @@ void ApplyBorderless(game::DxDriver7* driver) {
viewport.window = window;
viewport.runInBackground = re::ConfigInt("borderless", "RunInBackground", 1) != 0;
viewport.full = makeLayout(driver->mode.width, driver->mode.height);
viewport.classic = makeLayout(std::min<int>(driver->mode.width, 1024), std::min<int>(driver->mode.height, 768));
viewport.active = true;
g_viewport = viewport;
g_classicShown = false;
// Paint the bars: the window class background brush is black.
InvalidateRect(window, nullptr, TRUE);
@@ -188,12 +184,6 @@ int __fastcall DxDriver7_Flip(DxDriver7* self, void* edx) {
// The original computes the blit rectangles once, for a centred window of
// exactly the game's size; override them on every present.
if (g_viewport.active && self->window == g_viewport.window) {
// Menus and the loading screen only fill the top-left 1024x768 of a
// widened back buffer; show just that part, scaled like before.
const bool classic = mods::widescreen::IsClassicFrame();
if (g_classicShown.exchange(classic) != classic) {
InvalidateRect(self->window, nullptr, TRUE); // repaint the bars for the new layout
}
const Layout& layout = CurrentLayout();
self->blitSource = {0, 0, layout.gameWidth, layout.gameHeight};
self->blitDest = layout.screen;
+1 -1
View File
@@ -66,7 +66,7 @@ int OffsetFor(Anchor anchor) {
// The main menu's frame shifts the windows in it; over a 16:9 screen
// (no frame) dialogs are centred like in a game (user, 2026-10-21).
const bool inMenuFrame = mods::widescreen::MenuIsShowing() && g_menuFrameDx != 0;
if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() || inMenuFrame ||
if (!enabled || width <= kLayoutWidth || mods::widescreen::IsLoadingScreen() || inMenuFrame ||
anchor == Anchor::Screen) {
return 0;
}
+134
View File
@@ -0,0 +1,134 @@
// The load / save progress screen on the menu screens' background (request of
// 2026-10-25).
//
// The game's progress screen (DxDriver7::LoadingScreen 0x6469d0, thiscall
// (progress, text)) draws with GDI into the back buffer's DC (surface +0xbc,
// GetDC vf 0x44 / ReleaseDC vf 0x68): the 1024x768 picture (HBITMAP +0x200)
// at 0, 0, the progress bar (+0x204 / +0x208) and the text at y 700, then
// Flip. It only redraws when the bar moves.
//
// The mod: first the menu background across the whole render width in a
// Direct3D scene (screen::DrawBackground), then the game's drawing with the
// DC's origin moved to the centred 1024 frame. The game asks for a memory DC
// compatible with the back buffer's DC right after getting it
// (CreateCompatibleDC(hdc)), which is where the origin is set.
#include <windows.h>
#include <algorithm>
#include <cstdint>
#include "game/addresses.h"
#include "game/render/dx_driver7.h"
#include "mods/loadingscreen.h"
#include "mods/screen.h"
#include "mods/widescreen.h"
#include "re/hook.h"
namespace mods::loadingscreen {
namespace {
constexpr int kLayoutWidth = 1024;
using BeginSceneFn = char(__fastcall*)(game::DxDriver7* self, void* edx);
using EndSceneFn = void(__fastcall*)(game::DxDriver7* self, void* edx);
} // namespace
// While the progress screen draws on this thread: its frame's x.
thread_local int g_offsetX = 0;
void Begin(game::DxDriver7* driver) {
g_offsetX = 0;
const int width = widescreen::RenderWidth();
if (width <= kLayoutWidth || !driver || !driver->device || !driver->backBuffer) {
return;
}
if (reinterpret_cast<BeginSceneFn>(static_cast<uintptr_t>(game::addr::DxDriver7_BeginScene))(driver, nullptr)) {
screen::DrawBackground(driver->device);
reinterpret_cast<EndSceneFn>(static_cast<uintptr_t>(game::addr::DxDriver7_EndScene))(driver, nullptr);
}
g_offsetX = (width - kLayoutWidth) / 2;
}
void End() {
g_offsetX = 0;
}
} // namespace mods::loadingscreen
namespace mods::loadingscreen {
namespace {
constexpr int kLayoutHeight = 768;
// The game's pictures are artwork on black (PAK\loading0.bmp, loadgame.bmp,
// ...). Blended instead of copied: each pixel's alpha is its brightest
// channel, colours stay as they are (premultiplied), so black shows the
// background and the glow fades into it.
bool BlendPicture(HDC target, HDC source) {
const int offsetX = g_offsetX;
g_offsetX = 0; // our own GDI calls below pass the hooks unchanged
BITMAPINFO info = {};
info.bmiHeader.biSize = sizeof(info.bmiHeader);
info.bmiHeader.biWidth = kLayoutWidth;
info.bmiHeader.biHeight = -kLayoutHeight;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biCompression = BI_RGB;
void* bits = nullptr;
HBITMAP picture = CreateDIBSection(source, &info, DIB_RGB_COLORS, &bits, nullptr, 0);
if (!picture) {
g_offsetX = offsetX;
return false;
}
HDC copy = CreateCompatibleDC(source);
HGDIOBJ previous = SelectObject(copy, picture);
BitBlt(copy, 0, 0, kLayoutWidth, kLayoutHeight, source, 0, 0, SRCCOPY);
GdiFlush();
auto* pixel = static_cast<uint8_t*>(bits);
for (int i = 0; i < kLayoutWidth * kLayoutHeight; ++i, pixel += 4) {
pixel[3] = std::max({pixel[0], pixel[1], pixel[2]});
}
const BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
const bool done =
GdiAlphaBlend(target, 0, 0, kLayoutWidth, kLayoutHeight, copy, 0, 0, kLayoutWidth, kLayoutHeight, blend) != 0;
SelectObject(copy, previous);
DeleteDC(copy);
DeleteObject(picture);
g_offsetX = offsetX;
return done;
}
} // namespace
} // namespace mods::loadingscreen
// The picture: BitBlt(hdc, 0, 0, 1024, 768, picture DC, 0, 0, SRCCOPY), or
// BLACKNESS without a picture (then nothing is drawn over the background).
BOOL WINAPI BitBltForLoadingScreen(HDC target, int x, int y, int width, int height, HDC source, int sx, int sy,
DWORD rop);
RE_HOOK_API(BitBlt, BitBltForLoadingScreen);
BOOL WINAPI BitBltForLoadingScreen(HDC target, int x, int y, int width, int height, HDC source, int sx, int sy,
DWORD rop) {
using namespace mods::loadingscreen;
if (g_offsetX != 0 && x == 0 && y == 0 && width == 1024 && height == 768) {
if (rop == BLACKNESS) {
return TRUE;
}
if (rop == SRCCOPY && source && BlendPicture(target, source)) {
return TRUE;
}
}
return BitBltForLoadingScreen_original(target, x, y, width, height, source, sx, sy, rop);
}
HDC WINAPI CreateCompatibleDCForLoadingScreen(HDC hdc);
RE_HOOK_API(CreateCompatibleDC, CreateCompatibleDCForLoadingScreen);
HDC WINAPI CreateCompatibleDCForLoadingScreen(HDC hdc) {
if (hdc && mods::loadingscreen::g_offsetX != 0) {
SetViewportOrgEx(hdc, mods::loadingscreen::g_offsetX, 0, nullptr);
}
return CreateCompatibleDCForLoadingScreen_original(hdc);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
// The load / save progress screen on the menu background (loadingscreen.cpp).
#include "game/render/dx_driver7.h"
namespace mods::loadingscreen {
// Around DxDriver7::LoadingScreen (widescreen.cpp's hook): Begin draws the
// background and centres the game's GDI drawing, End stops centring.
void Begin(game::DxDriver7* driver);
void End();
} // namespace mods::loadingscreen
+108
View File
@@ -0,0 +1,108 @@
// A larger vision range for the explored map (request of 2026-10-25: "increase
// the base vision range for the map's fog of war reveal by x10 or so").
//
// Measured (game/engine/explored.h): RevealAroundHero stamps a fixed pattern
// around the hero into the sectors' explored masks whenever the hero's tile
// changes, a 14-tile circle by day, smaller ones at night.
//
// The mod: after the original revealed at a new tile, the same pattern scaled
// up kScale times is revealed too (by day a 140-tile circle), one cell (2x2
// tiles) at a time through the game's own World_SetExplored. Only loaded
// sectors can be marked; they reach further than the scaled stamp.
//
// Creatures on the zoomed map views (large map, TAB map; 0x6cc950): one off
// explored ground, or a hostile one, is only drawn within a sight range of the
// hero (|dx| and |dy| in world units, about 53.7 per tile): 650 (about 12
// tiles), 300 where 0x63d280 holds for the hero's area, 350 when the weather
// object's 0x41ba40 is false; plus bonuses from a skill (0x355) and the hero's
// equipment values. The three base values are scaled by kScale as well.
#include <cstdint>
#include <cstring>
#include "game/engine/explored.h"
#include "re/hook.h"
#include "re/log.h"
#include "re/patch.h"
namespace mods::mapreveal {
namespace {
constexpr int kScale = 10;
constexpr int kCellTiles = 2; // one mask bit per 2x2 tiles
// The stamp cell a tile offset falls into: cell c covers offsets
// c * kScale - kScale / 2 .. c * kScale + kScale / 2 - 1.
int StampOffset(int tiles) {
const int shifted = tiles + kScale / 2;
return (shifted >= 0 ? shifted : shifted - kScale + 1) / kScale;
}
// mov dword ptr [esp+14h], <float>: the map's creature sight range.
struct SightRange {
uint32_t instruction;
float value;
};
constexpr SightRange kCreatureSightRanges[] = {
{0x006ce677, 650.0f},
{0x006ce6b5, 300.0f},
{0x006ce6cf, 350.0f},
};
uint32_t FloatBits(float value) {
uint32_t bits;
std::memcpy(&bits, &value, sizeof bits);
return bits;
}
void Startup() {
int failed = 0;
for (const SightRange& range : kCreatureSightRanges) {
failed += !re::PatchInstructionDword(range.instruction, FloatBits(range.value), FloatBits(range.value * kScale));
}
if (failed) {
re::Log("mapreveal: %d creature sight range patches FAILED", failed);
}
}
void RevealScaled(int tileX, int tileY, int sight) {
const game::RevealStamp stamp = game::RevealStampFor(sight);
const int centre = stamp.size / 2;
const int radius = centre * kScale + kScale / 2;
// Cells start at even tiles (sector origins are multiples of 64).
const int left = (tileX - radius) & ~(kCellTiles - 1);
const int top = (tileY - radius) & ~(kCellTiles - 1);
for (int y = top; y <= tileY + radius; y += kCellTiles) {
const int row = centre + StampOffset(y + kCellTiles / 2 - tileY);
if (row < 0 || row >= stamp.size) {
continue;
}
for (int x = left; x <= tileX + radius; x += kCellTiles) {
const int column = centre + StampOffset(x + kCellTiles / 2 - tileX);
if (column >= 0 && column < stamp.size && stamp.cells[row * stamp.size + column]) {
game::SetExplored(x, y);
}
}
}
}
} // namespace
RE_STARTUP(MapRevealStartup, Startup);
} // namespace mods::mapreveal
void __fastcall RevealAroundHero(void* creature, void* edx, int tileX, int tileY, int sight);
RE_HOOK(game::kRevealAroundHero, RevealAroundHero);
void __fastcall RevealAroundHero(void* creature, void* edx, int tileX, int tileY, int sight) {
const int lastX = game::LastRevealTileX();
const int lastY = game::LastRevealTileY();
RevealAroundHero_original(creature, edx, tileX, tileY, sight);
// The original revealed (local hero, new tile) iff it moved its last tile
// here; it ignores tile 0, 0.
const bool revealed = (tileX != 0 || tileY != 0) && (lastX != tileX || lastY != tileY) &&
game::LastRevealTileX() == tileX && game::LastRevealTileY() == tileY;
if (revealed && sight >= 0 && sight <= 3) {
mods::mapreveal::RevealScaled(tileX, tileY, sight);
}
}
+26
View File
@@ -51,6 +51,20 @@ constexpr uint32_t kZoomView = 0x006cc950;
constexpr uint32_t kZoomCentreUses[] = {0x006ccb7f, 0x006cf782};
constexpr uint32_t kZoomMarkerX = 0x006ce156;
constexpr float kZoomMarkerFromCentre = 256.0f;
// The heroes' markers in the zoomed view: a 6 px quad at x = d + 509 / 515
// (fadd [509.0] 0x6cf1bd, fadd [515.0] 0x6cf1dd), i.e. the centre 512 +- 3
// (2026-10-25: they stayed at the 1024 layout's centre).
constexpr uint32_t kZoomHeroMarkerLeft = 0x006cf1bd;
constexpr uint32_t kZoomHeroMarkerRight = 0x006cf1dd;
constexpr uintptr_t kZoomHeroMarkerLeftF = 0x00894780; // 509.0
constexpr uintptr_t kZoomHeroMarkerRightF = 0x00894778; // 515.0
constexpr float kZoomHeroMarkerHalf = 3.0f;
// The other symbols in the zoomed view take the centre as an integer
// immediate 200h (mov reg / add reg, 200h): a quad x = 200h - w / 200h + w
// (0x6ced82, 0x6cedb0), icons (0x6cf47a, 0x6cf4ac) and labels centred on
// their x (0x6cf5e4) (2026-10-25: they stayed at the 1024 layout's centre).
// Rewritten whenever the centre changes.
constexpr uint32_t kZoomCentreImmediates[] = {0x006ced82, 0x006cedb0, 0x006cf47a, 0x006cf4ac, 0x006cf5e4};
// The dark band around the view (10 vertices, mov [esp+..], imm32): its right
// outer edge x 1024 and inner edge x 954.
constexpr uint32_t kBandRightOuter[] = {0x006ca774, 0x006ca808};
@@ -60,6 +74,9 @@ constexpr int kBandWidth = 70;
float g_halfWidth = 512.0f;
float g_zoomCentre = 512.0f;
float g_zoomMarkerX = 768.0f;
float g_zoomHeroMarkerLeft = 509.0f;
float g_zoomHeroMarkerRight = 515.0f;
uint32_t g_zoomCentreImmediate = kLayoutWidth / 2;
uint32_t FloatBits(float value) {
uint32_t bits;
@@ -183,6 +200,8 @@ void PatchLayout(int width) {
failed += !re::RedirectOperand(instruction, game::addr::kScreenHalfWidthF, &g_zoomCentre);
}
failed += !re::RedirectOperand(kZoomMarkerX, game::addr::kScreenHeightF, &g_zoomMarkerX);
failed += !re::RedirectOperand(kZoomHeroMarkerLeft, kZoomHeroMarkerLeftF, &g_zoomHeroMarkerLeft);
failed += !re::RedirectOperand(kZoomHeroMarkerRight, kZoomHeroMarkerRightF, &g_zoomHeroMarkerRight);
failed += re::PatchAll(kBandRightOuter, FloatBits(1024.0f), FloatBits(static_cast<float>(width)));
failed += re::PatchAll(kBandRightInner, FloatBits(1024.0f - kBandWidth),
FloatBits(static_cast<float>(width - kBandWidth)));
@@ -196,6 +215,13 @@ void __cdecl SetZoomCentre() {
const int width = std::max(mods::widescreen::RenderWidth(), kLayoutWidth);
g_zoomCentre = width / 2.0f - static_cast<float>(mods::hud::DrawOffsetX());
g_zoomMarkerX = g_zoomCentre + kZoomMarkerFromCentre;
g_zoomHeroMarkerLeft = g_zoomCentre - kZoomHeroMarkerHalf;
g_zoomHeroMarkerRight = g_zoomCentre + kZoomHeroMarkerHalf;
const auto immediate = static_cast<uint32_t>(width / 2 - mods::hud::DrawOffsetX());
if (immediate != g_zoomCentreImmediate &&
re::PatchAll(kZoomCentreImmediates, g_zoomCentreImmediate, immediate) == 0) {
g_zoomCentreImmediate = immediate;
}
}
} // namespace mods::megamap
+1 -1
View File
@@ -259,7 +259,7 @@ 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) {
if (!widescreen::IsMenuThread() || widescreen::IsLoadingScreen() || widescreen::RenderWidth() == 0) {
return UiManager_Render_original(self, edx, argument);
}
auto* driver = *reinterpret_cast<DxDriver7**>(addr::g_dxDriver);
+11 -11
View File
@@ -24,6 +24,7 @@
#include "mods/widescreen.h"
#include "mods/hud.h"
#include "mods/loadingscreen.h"
#include "mods/megamap.h"
#include <algorithm>
@@ -63,9 +64,9 @@ bool g_pushesPatched = false;
int g_renderWidth = 0;
int g_renderHeight = 0;
// 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.
// Every frame uses the full render width: the main menu thread (its windows
// drawn centred by mods/screen.cpp), the loading screen (centred on the menu
// background, mods/loadingscreen.cpp), the widened in-game view.
std::atomic<DWORD> g_menuThread = 0;
// 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.
@@ -343,7 +344,7 @@ bool MenuIsShowing() {
return g_lastSceneMenu;
}
bool IsClassicFrame() {
bool IsLoadingScreen() {
return g_renderWidth != 0 && g_loadingScreenDepth > 0;
}
@@ -369,22 +370,21 @@ void __fastcall UiManager_ThreadLoop(void* self, void* edx) {
void __fastcall DxDriver7_LoadingScreen(DxDriver7* self, void* edx, uint32_t progress, const char* text) {
++g_loadingScreenDepth;
mods::loadingscreen::Begin(self);
DxDriver7_LoadingScreen_original(self, edx, progress, text);
mods::loadingscreen::End();
--g_loadingScreenDepth;
}
// 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.
// Every frame gets the full render size as its viewport.
int __fastcall DxDriver7_BeginScene(DxDriver7* self, void* edx) {
mods::hud::OnDevice(self->device);
if (self->sceneDepth == 0) {
g_lastSceneMenu = IsClassicFrame() || IsMenuThread();
g_lastSceneMenu = IsLoadingScreen() || IsMenuThread();
}
if (g_renderWidth != 0 && self->sceneDepth == 0 && self->device) {
const bool classic = IsClassicFrame();
Viewport7 viewport = {0, 0, classic ? 1024u : static_cast<DWORD>(g_renderWidth),
classic ? 768u : static_cast<DWORD>(g_renderHeight), 0.0f, 1.0f};
Viewport7 viewport = {0, 0, static_cast<DWORD>(g_renderWidth), static_cast<DWORD>(g_renderHeight), 0.0f,
1.0f};
using SetViewportFn = HRESULT(__stdcall*)(void* device, Viewport7* viewport);
auto setViewport = reinterpret_cast<SetViewportFn>((*static_cast<void***>(self->device))[13]);
setViewport(self->device, &viewport);
+2 -3
View File
@@ -11,9 +11,8 @@ 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 (the
// loading screen); the borderless mod then presents only that area.
bool IsClassicFrame();
// True while the current thread draws the load / save progress screen.
bool IsLoadingScreen();
// Applied render width (0 while the original 1024x768 is in use).
int RenderWidth();