diff --git a/CMakeLists.txt b/CMakeLists.txt index 37399ff..872d929 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_library(sacredK SHARED src/game/engine/stream.cpp src/game/ui/texts.cpp src/game/engine/art.cpp + src/mods/monitor.cpp src/mods/borderless.cpp src/mods/widescreen.cpp src/mods/hud.cpp diff --git a/docs/options.md b/docs/options.md index 754841c..2a9b8c5 100644 --- a/docs/options.md +++ b/docs/options.md @@ -132,3 +132,32 @@ the borders: screenshots 2026-10-20). One row *Autosave (Min.)*: Aus / 5 / lighter one). A click selects, OK writes `[characters] AutosaveMinutes` (`characters::SetAutosaveMinutes`, the timer uses it at once), Cancel drops it. + +## Allgemein: preferred monitor (2026-10-25) + +Request: a setting for the monitor the game starts on; with none set, or the +preferred monitor not connected, the system's main monitor. + +- `src/mods/monitor.cpp`: the monitors (`EnumDisplayMonitors`), named and + told apart through the display configuration (`QueryDisplayConfig`, + `DisplayConfigGetDeviceInfo`: the GDI name `\.\DISPLAYn` of each path's + source, the monitor's EDID name and device path of its target). The device + path is the id: it stays the same across restarts and when monitors are + rearranged, unlike `\.\DISPLAYn`. Kept in `sacredK.ini` + `[display] Monitor=` (empty: the main monitor). +- `GameMonitorRect` is asked where the screen is decided at startup: the + borderless window covers that monitor (`borderless.cpp`), and the render + size follows its aspect ratio (`widescreen.cpp` `ApplyRenderSize`). The + log line `monitor: WxH at (x,y)` says which one was used and why. + A change applies at the next start. +- In the options (user's choice of place): the *Internet-Optionen* frame + (Datendurchsatz, multiplayer only) becomes *Bildschirm*. Its controls + (`UI_CFG_LANSPEED` label, DSL / ISDN radio buttons, everything inside + `UI_FRAME_INTERNET`) and the title label `UI_LABEL_WAN` are hidden; the + game keeps the saved Datendurchsatz. The title text (key `UI_CFG_WAN`) is + overridden with "Bildschirm" before the window is made. +- One row: *Monitor* and a box with the choice (Hauptmonitor, then each + monitor as "name (WxH)", "Nicht verbunden" for a preferred monitor that is + missing). A click opens the list upwards (the frame is the lowest one); a + click on an entry picks it, any click closes the list. A changed choice + shows "Ab dem nächsten Spielstart" below the box. OK saves, Cancel drops. diff --git a/src/game/ui/texts.cpp b/src/game/ui/texts.cpp index a22d4c6..7108c4e 100644 --- a/src/game/ui/texts.cpp +++ b/src/game/ui/texts.cpp @@ -156,11 +156,16 @@ void SetTextOverride(uint32_t id, const std::wstring& text) { entry->string = {begin, begin + entry->text.size(), begin + entry->text.size()}; } -std::wstring GameTextByKey(const char* key) { +uint32_t GameTextIdByKey(const char* key) { // The key's hash (0x80e010, cdecl), looked up "by key" as GetTextByKey does. using HashFn = uint32_t(__cdecl*)(const char*); const uint32_t hash = reinterpret_cast(0x0080e010)(key); - return hash ? GameText(hash | 0x80000000) : std::wstring(); + return hash ? hash | 0x80000000 : 0; +} + +std::wstring GameTextByKey(const char* key) { + const uint32_t id = GameTextIdByKey(key); + return id ? GameText(id) : std::wstring(); } std::wstring WideStringText(const void* string) { diff --git a/src/game/ui/texts.h b/src/game/ui/texts.h index b9350f5..72d6fed 100644 --- a/src/game/ui/texts.h +++ b/src/game/ui/texts.h @@ -33,6 +33,9 @@ void SetTextOverride(uint32_t id, const std::wstring& text); // The game's text under key `key` (TextManager::GetTextByKey 0x672fd0), e.g. // "UI_ATTRIB_STRENGTH"; empty if there is none. std::wstring GameTextByKey(const char* key); +// The id of the game's text under key `key` (0 if the key has no hash), e.g. +// for SetTextOverride. +uint32_t GameTextIdByKey(const char* key); // A game wide string ({begin, end, capacity end}, e.g. a tooltip's text) as // text, and replaced by `value` (grown with the game's allocator). diff --git a/src/mods/borderless.cpp b/src/mods/borderless.cpp index 8d2d873..3df7c89 100644 --- a/src/mods/borderless.cpp +++ b/src/mods/borderless.cpp @@ -3,7 +3,7 @@ // The game renders at a fixed resolution (1024x768). Unmodified, windowed mode // shows that as a 1024x768 popup centred on the desktop; fullscreen mode // switches the display mode. With this mod the game always runs windowed, the -// popup covers the whole monitor, and each frame is scaled up into the largest +// popup covers the whole monitor (mods/monitor.h: the preferred one), and each frame is scaled up into the largest // centred rectangle with the game's aspect ratio (black bars elsewhere). // // Mouse positions are mapped back from that rectangle to game pixels, both in @@ -35,6 +35,7 @@ #include "game/render/dx_driver7.h" #include "mods/hud.h" #include "mods/input.h" +#include "mods/monitor.h" #include "mods/widescreen.h" #include "re/config.h" #include "game/addresses.h" @@ -90,9 +91,7 @@ RECT FitRect(int areaWidth, int areaHeight, int width, int height, bool integerS void ApplyBorderless(game::DxDriver7* driver) { HWND window = driver->window; - MONITORINFO monitor = {sizeof(monitor)}; - GetMonitorInfoW(MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST), &monitor); - const RECT& area = monitor.rcMonitor; + const RECT area = mods::monitor::GameMonitorRect(); const int areaWidth = area.right - area.left; const int areaHeight = area.bottom - area.top; @@ -171,7 +170,7 @@ int __fastcall DxDriver7_SetDisplayMode(DxDriver7* self, void* edx, void* device } DxDisplayMode windowed = *mode; windowed.fullscreen = 0; - mods::widescreen::ApplyRenderSize(windowed, self->window); + mods::widescreen::ApplyRenderSize(windowed); g_viewport.active = false; const int result = DxDriver7_SetDisplayMode_original(self, edx, deviceCaps, &windowed, unknown); if (result) { diff --git a/src/mods/monitor.cpp b/src/mods/monitor.cpp new file mode 100644 index 0000000..4442352 --- /dev/null +++ b/src/mods/monitor.cpp @@ -0,0 +1,160 @@ +// Preferred monitor (request of 2026-10-25): a setting in the graphics options +// picks the monitor the game starts on; with none set, or the preferred one +// not connected, the system's main monitor. +// +// The game window and its render size are decided when the display mode is +// set at startup (borderless.cpp, widescreen.cpp ApplyRenderSize): both ask +// GameMonitorRect. A change applies at the next start. +// +// Monitors are told apart by their device path (DisplayConfigGetDeviceInfo, +// target name: "\\?\DISPLAY#GSM5B7F#..."), which survives restarts, monitor +// order changes and other ports' changes, unlike the GDI names \\.\DISPLAYn. +// +// sacredK.ini: +// [display] +// Monitor= ; empty: the main monitor + +#include "mods/monitor.h" + +#include + +#include "re/config.h" +#include "re/log.h" + +namespace mods::monitor { +namespace { + +constexpr const char* kSection = "display"; +constexpr const char* kKey = "Monitor"; + +std::string Utf8(const wchar_t* text) { + const int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, nullptr, 0, nullptr, nullptr); + if (size <= 1) { + return {}; + } + std::string result(static_cast(size - 1), '\0'); + WideCharToMultiByte(CP_UTF8, 0, text, -1, result.data(), size, nullptr, nullptr); + return result; +} + +struct Target { + std::wstring gdiName; // \\.\DISPLAYn of the source showing it + std::string id; + std::wstring name; +}; + +// The active display paths: which monitor shows which GDI display. +std::vector Targets() { + UINT32 pathCount = 0; + UINT32 modeCount = 0; + std::vector paths; + std::vector modes; + LONG result; + do { + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount) != ERROR_SUCCESS) { + return {}; + } + paths.resize(pathCount); + modes.resize(modeCount); + result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(), + nullptr); + } while (result == ERROR_INSUFFICIENT_BUFFER); + if (result != ERROR_SUCCESS) { + return {}; + } + paths.resize(pathCount); + std::vector targets; + for (const DISPLAYCONFIG_PATH_INFO& path : paths) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME source = {}; + source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source.header.size = sizeof(source); + source.header.adapterId = path.sourceInfo.adapterId; + source.header.id = path.sourceInfo.id; + DISPLAYCONFIG_TARGET_DEVICE_NAME target = {}; + target.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + target.header.size = sizeof(target); + target.header.adapterId = path.targetInfo.adapterId; + target.header.id = path.targetInfo.id; + if (DisplayConfigGetDeviceInfo(&source.header) != ERROR_SUCCESS || + DisplayConfigGetDeviceInfo(&target.header) != ERROR_SUCCESS) { + continue; + } + targets.push_back({source.viewGdiDeviceName, Utf8(target.monitorDevicePath), target.monitorFriendlyDeviceName}); + } + return targets; +} + +BOOL CALLBACK AddMonitor(HMONITOR handle, HDC, LPRECT, LPARAM data) { + auto* gdi = reinterpret_cast*>(data); + MONITORINFOEXW info = {}; + info.cbSize = sizeof(info); + if (GetMonitorInfoW(handle, &info)) { + gdi->push_back(info); + } + return TRUE; +} + +} // namespace + +std::vector List() { + std::vector gdi; + EnumDisplayMonitors(nullptr, nullptr, &AddMonitor, reinterpret_cast(&gdi)); + const std::vector targets = Targets(); + std::vector monitors; + for (const MONITORINFOEXW& info : gdi) { + Monitor monitor{"", L"", info.rcMonitor, (info.dwFlags & MONITORINFOF_PRIMARY) != 0}; + for (const Target& target : targets) { + if (target.gdiName == info.szDevice) { // mirrored displays: the first monitor + monitor.id = target.id; + monitor.name = target.name; + break; + } + } + if (monitor.id.empty()) { + monitor.id = Utf8(info.szDevice); // no display config (remote desktop, ...) + } + if (monitor.name.empty()) { + monitor.name = L"Monitor " + std::to_wstring(monitors.size() + 1); + } + monitors.push_back(monitor); + } + std::stable_sort(monitors.begin(), monitors.end(), + [](const Monitor& a, const Monitor& b) { return a.primary && !b.primary; }); + return monitors; +} + +std::string PreferredId() { + return re::ConfigString(kSection, kKey, ""); +} + +void SetPreferredId(const std::string& id) { + re::SetConfigString(kSection, kKey, id.c_str()); +} + +RECT GameMonitorRect() { + const std::vector monitors = List(); + const std::string preferred = PreferredId(); + const Monitor* chosen = nullptr; + for (const Monitor& monitor : monitors) { + if ((!preferred.empty() && monitor.id == preferred) || (!chosen && monitor.primary)) { + chosen = &monitor; + } + } + if (!chosen) { + // No monitor reported: the main monitor's rectangle as Windows has it. + MONITORINFO info = {sizeof(info)}; + GetMonitorInfoW(MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY), &info); + return info.rcMonitor; + } + const bool found = !preferred.empty() && chosen->id == preferred; + static bool logged = false; + if (!logged) { + logged = true; + re::Log("monitor: %ls %ldx%ld at (%ld,%ld)%s", chosen->name.c_str(), chosen->rect.right - chosen->rect.left, + chosen->rect.bottom - chosen->rect.top, chosen->rect.left, chosen->rect.top, + found ? "" : preferred.empty() ? ", main monitor" : ", preferred monitor not connected: main monitor"); + } + return chosen->rect; +} + +} // namespace mods::monitor diff --git a/src/mods/monitor.h b/src/mods/monitor.h new file mode 100644 index 0000000..8aa786f --- /dev/null +++ b/src/mods/monitor.h @@ -0,0 +1,31 @@ +#pragma once +// The monitor the game runs on (request of 2026-10-25): a preferred monitor +// from the options, or the system's main monitor when none is set or the +// preferred one is not connected. See monitor.cpp. + +#include +#include + +#include + +namespace mods::monitor { + +struct Monitor { + std::string id; // stable across restarts and reordering: the monitor's device path + std::wstring name; // the monitor's own name (EDID), e.g. "LG ULTRAGEAR" + RECT rect; // desktop coordinates + bool primary; +}; + +// The connected monitors, the main monitor first. +std::vector List(); + +// The preferred monitor's id from sacredK.ini, empty for none. +std::string PreferredId(); +void SetPreferredId(const std::string& id); + +// The rectangle of the monitor to run on: the preferred one if connected, +// else the main monitor. +RECT GameMonitorRect(); + +} // namespace mods::monitor diff --git a/src/mods/options.cpp b/src/mods/options.cpp index d66ae35..b09864a 100644 --- a/src/mods/options.cpp +++ b/src/mods/options.cpp @@ -41,6 +41,7 @@ #include "mods/hud.h" #include "mods/input.h" #include "mods/lootfilter.h" +#include "mods/monitor.h" #include "re/hook.h" #include "re/log.h" @@ -684,6 +685,160 @@ bool HandleSavesEvent(Window& w, void* event) { } } +// --- monitor (Allgemein page) ---------------------------------------------------- + +// Preferred monitor (request of 2026-10-25, mods/monitor.h) in place of the +// Internet-Optionen frame (Datendurchsatz, multiplayer only; the user's +// choice): its controls are hidden (the game keeps the saved value), its title +// reads "Bildschirm" (text UI_CFG_WAN, set by 0x672a20 / 0x724d50 when the +// window is made). One row: "Monitor" and a dropdown box; the list opens +// upwards (the frame is the lowest one), a click picks, a click elsewhere +// closes it. OK saves the choice, which applies at the next start. +constexpr const char* kScreenFrameName = "UI_FRAME_INTERNET"; +constexpr const char* kScreenTitleName = "UI_LABEL_WAN"; +constexpr const char* kScreenTitleKey = "UI_CFG_WAN"; +constexpr int kScreenLabelWidth = 0x70; +constexpr int kArrowWidth = 9; // the box's down arrow, 5 rows + +struct ScreenChoice { + std::string id; // empty: the main monitor + std::wstring text; +}; + +game::UiRect g_screenFrame = {}; +std::vector g_screenChoices; +std::string g_screenPending; +bool g_screenOpen = false; +int g_screenHover = -1; // list entry under the mouse + +void CreateScreenControls(Window& w) { + for (const Control& c : w.controls) { + if (IsFrame(c.control) && game::ControlNamed(c.control, kScreenFrameName)) { + g_screenFrame = *reinterpret_cast(c.control + game::kControlRect); + } + } + for (Control& c : w.controls) { + const game::UiRect r = *reinterpret_cast(c.control + game::kControlRect); + const bool inside = r.x >= g_screenFrame.x && r.y >= g_screenFrame.y && + r.x < g_screenFrame.x + g_screenFrame.w && r.y < g_screenFrame.y + g_screenFrame.h; + if (c.group == Group::Common && + (game::ControlNamed(c.control, kScreenTitleName) || (inside && !IsFrame(c.control)))) { + c.group = Group::Hidden; + } + } +} + +void LoadScreenPending() { + g_screenChoices = {{"", L"Hauptmonitor"}}; + for (const mods::monitor::Monitor& m : mods::monitor::List()) { + g_screenChoices.push_back({m.id, m.name + L" (" + std::to_wstring(m.rect.right - m.rect.left) + L"x" + + std::to_wstring(m.rect.bottom - m.rect.top) + L")"}); + } + g_screenPending = mods::monitor::PreferredId(); + g_screenOpen = false; + g_screenHover = -1; +} + +const wchar_t* ScreenPendingText() { + for (const ScreenChoice& choice : g_screenChoices) { + if (choice.id == g_screenPending) { + return choice.text.c_str(); + } + } + return L"Nicht verbunden"; +} + +game::UiRect ScreenRowRect(void* window, int dx, int width) { + const game::UiRect origin = game::ControlScreenRect(window); + return {origin.x + g_screenFrame.x + kTextInset + dx, + origin.y + g_screenFrame.y + (g_screenFrame.h - kBoxHeight) / 2, static_cast(width), + static_cast(kBoxHeight)}; +} + +game::UiRect ScreenBoxRect(void* window) { + return ScreenRowRect(window, kScreenLabelWidth, g_screenFrame.w - 2 * kTextInset - kScreenLabelWidth); +} + +// List entry `index`, upwards from the box: the last entry right above it. +game::UiRect ScreenEntryRect(void* window, int index) { + game::UiRect r = ScreenBoxRect(window); + r.y -= static_cast(g_screenChoices.size() - index) * kBoxHeight; + return r; +} + +int ScreenEntryAt(void* window, int x, int y) { + for (int i = 0; i < static_cast(g_screenChoices.size()); ++i) { + if (game::Inside(ScreenEntryRect(window, i), x, y)) { + return i; + } + } + return -1; +} + +void DrawScreen(const Window& w, void* device) { + void* window = w.window; + game::DrawText(device, kFont, game::CenterLineVertically(kFont, ScreenRowRect(window, 0, kScreenLabelWidth)), + L"Monitor", kTextColour, 0); + const game::UiRect box = ScreenBoxRect(window); + FillBox(device, box, g_screenOpen ? kSavesBoxHoverColour : kSavesBoxColour); + game::UiRect text = box; + text.x += 6; + text.w = static_cast(text.w - 6 - kArrowWidth - 8); + game::DrawText(device, kFont, game::CenterLineVertically(kFont, text), ScreenPendingText(), kTextColour, 0); + // The arrow: rows narrowing to a point. + const int arrowX = box.x + box.w - kArrowWidth - 6; + const int arrowY = box.y + (box.h - 5) / 2; + for (int row = 0; row < 5; ++row) { + FillBox(device, {arrowX + row, arrowY + row, static_cast(kArrowWidth - 2 * row), 1}, kTextColour); + } + if (g_screenPending != mods::monitor::PreferredId()) { + game::UiRect hint = {box.x, box.y + box.h + 2, box.w, static_cast(game::FontLineHeight(kFont))}; + game::DrawText(device, kFont, hint, L"Ab dem nächsten Spielstart", kUnboundColour, 0); + } + if (!g_screenOpen) { + return; + } + for (int i = 0; i < static_cast(g_screenChoices.size()); ++i) { + const game::UiRect entry = ScreenEntryRect(window, i); + const bool selected = g_screenChoices[i].id == g_screenPending; + FillBox(device, entry, selected ? kBoxSelectedColour : i == g_screenHover ? kBoxHoverColour : kBoxColour); + game::UiRect label = entry; + label.x += 6; + game::DrawText(device, kFont, game::CenterLineVertically(kFont, label), g_screenChoices[i].text.c_str(), + kTextColour, 0); + } +} + +bool HandleScreenEvent(Window& w, void* event) { + switch (game::MouseEventKind(event)) { + case game::MouseEvent::Move: + g_screenHover = + g_screenOpen ? ScreenEntryAt(w.window, game::EventField(event, 0xc), game::EventField(event, 0x10)) : -1; + return false; + case game::MouseEvent::ButtonDown: { + const int x = game::EventField(event, game::kMouseButtonX); + const int y = game::EventField(event, game::kMouseButtonY); + if (g_screenOpen) { + const int entry = ScreenEntryAt(w.window, x, y); + if (entry >= 0 && game::EventField(event, game::kMouseButton) == game::kLeftButton) { + g_screenPending = g_screenChoices[entry].id; + } + g_screenOpen = false; + g_screenHover = -1; + return true; // the click only closes the list + } + if (game::EventField(event, game::kMouseButton) == game::kLeftButton && + game::Inside(ScreenBoxRect(w.window), x, y)) { + g_screenOpen = true; + return true; + } + return false; + } + default: + return false; + } +} + // --- hooks --------------------------------------------------------------------- struct RenderCall { @@ -706,6 +861,7 @@ int RenderBody(void* context) { LoadPending(); LoadLootPending(); g_autosavePending = mods::characters::AutosaveMinutes(); + LoadScreenPending(); } w->wasVisible = visible; const uint16_t page = game::ActiveTab(w->sheet); @@ -720,6 +876,8 @@ int RenderBody(void* context) { } if (!visible || page != kPageCommon) { g_savesHover = -1; + g_screenOpen = false; + g_screenHover = -1; } if (!visible) { return result; @@ -731,6 +889,7 @@ int RenderBody(void* context) { DrawLootPage(*w, call->device); } else if (page == kPageCommon && g_pendingLoaded) { DrawSaves(*w, call->device); + DrawScreen(*w, call->device); } return result; } @@ -773,7 +932,7 @@ int EventBody(void* context) { return 1; } if (w && w->sheet && game::ControlShown(call->window) && game::ActiveTab(w->sheet) == kPageCommon && - g_pendingLoaded && HandleSavesEvent(*w, call->event)) { + g_pendingLoaded && (HandleScreenEvent(*w, call->event) || HandleSavesEvent(*w, call->event))) { return 1; } if (w && w->sheet && game::ControlShown(call->window) && game::ActiveTab(w->sheet) == kPageKeys && @@ -824,11 +983,16 @@ void* __fastcall UiOptions_Construct(void* self, void* edx, const char* name, Ui RE_HOOK(0x00716570, UiOptions_Construct); void* __fastcall UiOptions_Construct(void* self, void* edx, const char* name, UiRect rect, void* parent, uint32_t flags) { + static const bool titled = [] { + game::SetTextOverride(game::GameTextIdByKey(kScreenTitleKey), L"Bildschirm"); + return true; + }(); void* window = UiOptions_Construct_original(self, edx, name, rect, parent, flags); if (window) { g_windows.push_back({static_cast(window)}); Window& w = g_windows.back(); Classify(w); + CreateScreenControls(w); CreateKeyControls(w); CreateLootControls(w); CreateSavesControls(w); @@ -873,6 +1037,7 @@ void __fastcall UiOptions_Apply(void* self, void* edx) { mods::input::SetBindingKeys(g_pending); mods::lootfilter::SetModes(g_lootPending); mods::characters::SetAutosaveMinutes(g_autosavePending); + mods::monitor::SetPreferredId(g_screenPending); } } diff --git a/src/mods/widescreen.cpp b/src/mods/widescreen.cpp index 67f7e74..ea45d40 100644 --- a/src/mods/widescreen.cpp +++ b/src/mods/widescreen.cpp @@ -26,6 +26,7 @@ #include "mods/hud.h" #include "mods/loadingscreen.h" #include "mods/megamap.h" +#include "mods/monitor.h" #include #include @@ -270,17 +271,16 @@ void PatchLetterbox(int width) { } // namespace -void ApplyRenderSize(game::DxDisplayMode& mode, HWND window) { +void ApplyRenderSize(game::DxDisplayMode& mode) { if (!Enabled()) { return; } - MONITORINFO monitor = {sizeof(monitor)}; - GetMonitorInfoW(MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY), &monitor); + const RECT monitor = mods::monitor::GameMonitorRect(); // Default: keep the original 768 pixels of world height (so everything // keeps its on-screen size once scaled to the monitor) and extend the width // to the monitor's aspect ratio. Height=0 renders at the monitor height 1:1. - const int monitorWidth = monitor.rcMonitor.right - monitor.rcMonitor.left; - const int monitorHeight = monitor.rcMonitor.bottom - monitor.rcMonitor.top; + const int monitorWidth = monitor.right - monitor.left; + const int monitorHeight = monitor.bottom - monitor.top; int height = re::ConfigInt("widescreen", "Height", static_cast(kOriginalHeight)); if (height <= 0) { height = monitorHeight; diff --git a/src/mods/widescreen.h b/src/mods/widescreen.h index bdc5ce7..ce77a2f 100644 --- a/src/mods/widescreen.h +++ b/src/mods/widescreen.h @@ -9,7 +9,7 @@ namespace mods::widescreen { // Called by the borderless mod before the renderer creates its surfaces: // sets the render size to the monitor (or the configured size) and resizes // the world view to match. No-op when disabled. -void ApplyRenderSize(game::DxDisplayMode& mode, HWND window); +void ApplyRenderSize(game::DxDisplayMode& mode); // True while the current thread draws the load / save progress screen. bool IsLoadingScreen();