Characters: one file per character with a world per difficulty

- CharacterNN.sacredK holds the shared hero and one world per difficulty,
  plus an autosave file; the game works in save\characters\_work
- Load window: one row per character, a difficulty dialog on Laden
  (worlds, newer autosaves, "(neu)" starts a new game with the shared
  hero), Umbenennen, delete moves the character to characters\deleted
- The world keeps its own portals and hero position when the shared hero
  is put on
- K and the Esc menu's "Speichern" save directly (quicksave path)
- One-time migration of the old save rows and exported heroes, after a
  backup
- Startup no longer hangs in the game's foreground loop (0x810af0)
- Text overrides apply to our own text ids too; shared cUI_Button2
  helpers in game/ui/button.h

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-20 01:41:42 +02:00
1 parent 5df7efc198
commit 5bccb0734e
16 files changed
+2052 -64

No files matched your search

+38
View File
@@ -256,3 +256,41 @@ SHORT WINAPI GetAsyncKeyStateWhenFocused(int key) {
}
return GetAsyncKeyStateWhenFocused_original(key);
}
// The game's "bring the window to the front" (0x810af0, thiscall (window,
// timeout ms), ret 8; WinMain calls it three times): ShowWindow, then
// SetForegroundWindow until the window is in front and SetFocus until it has
// the focus. WinMain passes 0xffffffff, which never times out. Windows refuses
// the foreground to a process when the user clicked or typed elsewhere while
// it started, so the game hung at startup (2026-10-20, four times; main
// thread in this loop, log ending at WM_ACTIVATEAPP(1, 0)). Now it asks for a
// limited time, with the input of the window in front attached (which Windows
// allows), and carries on in the background otherwise; a click brings it up.
constexpr DWORD kForegroundWaitMs = 2000;
bool __fastcall BringWindowToFront(void* self, void* edx, HWND window, DWORD timeout);
RE_HOOK(0x00810af0, BringWindowToFront);
bool __fastcall BringWindowToFront(void*, void*, HWND window, DWORD timeout) {
ShowWindow(window, SW_SHOW);
const DWORD wait = std::min(timeout, kForegroundWaitMs);
const DWORD start = GetTickCount();
while (GetForegroundWindow() != window && GetTickCount() - start < wait) {
const DWORD front = GetWindowThreadProcessId(GetForegroundWindow(), nullptr);
const DWORD self = GetCurrentThreadId();
const bool attached = front && front != self && AttachThreadInput(front, self, TRUE);
BringWindowToTop(window);
SetForegroundWindow(window);
if (attached) {
AttachThreadInput(front, self, FALSE);
}
Sleep(10);
}
const bool inFront = GetForegroundWindow() == window;
if (inFront) {
SetFocus(window);
} else {
re::Log("borderless: the game window did not get to the front, starting in the background");
}
UpdateWindow(window);
return inFront;
}