Framework for Sacred 2 Gold, game log mirror, borderless fullscreen
Ported from sacredK with module-relative addresses, hooks by exported name and a SHA-256 check of the six game modules (Community Patch 2.65.1). Borderless windowed at monitor size replaces exclusive fullscreen, whose focus loss during loading made the game quit and crash on exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
8a7bdfd99f
44 files changed
+2505
No files matched your search
@@ -0,0 +1,52 @@
|
||||
# Borderless fullscreen
|
||||
|
||||
Request (2026-10-31): make the game start without crashing, then borderless
|
||||
fullscreen as the first feature.
|
||||
|
||||
## The start-up crash
|
||||
|
||||
Measured 2026-10-31 (3440x1440, options `video.fullscreen = 1`):
|
||||
|
||||
- Started through the launcher from a background process, Windows doesn't
|
||||
hand the game window the foreground. In exclusive fullscreen Direct3D then
|
||||
minimises it (style gains `WS_MINIMIZE` after ~8 s, during "SETUP AI..." on
|
||||
the loading screen), and the game quits ~20 s later. On the way out
|
||||
`s2logicai.dll`'s static destructors call a pure virtual function (a global
|
||||
allocator object at `0x101BF774` is already destroyed), the game's
|
||||
`s2_purecall()` handler (`sacred2.exe` 0x489ada) breaks into the debugger:
|
||||
exception 0x80000003 in KERNELBASE.
|
||||
- Started with focus (or directly, unmodified), it reaches the main menu.
|
||||
- `cDX9Base::flip` minimises the window and sets device flag 4 when Present
|
||||
returns `D3DERR_DEVICELOST`.
|
||||
|
||||
So the crash was exclusive fullscreen losing the focus; it is gone with
|
||||
borderless.
|
||||
|
||||
## How the mod works (`src/mods/borderless.cpp`)
|
||||
|
||||
`cDX9Base::_initDX(const sInitParams&)` (s2render 0x1029a736, exported, the
|
||||
only device set-up, reached through `initDX` / `cDX9Advance::initAdvance`)
|
||||
has a windowed path: style `0x12c00000`, size clamped to
|
||||
`GetSystemMetrics(SM_CXSCREEN/SM_CYSCREEN)` minus the frame, window centred,
|
||||
back buffer of that size, presentation interval default. See
|
||||
`src/game/render/dx9base.h` for `sInitParams` and the addresses.
|
||||
|
||||
- The hook passes `fullscreen = 0` and the primary monitor's size.
|
||||
- At startup the four uses of the style constant (0x1029ab49, ab56, ab5b,
|
||||
ab62) become `WS_POPUP | WS_VISIBLE`, so the game's own code makes a
|
||||
frameless window covering the monitor at (0,0).
|
||||
- The launcher passes its foreground right to the game
|
||||
(`AllowSetForegroundWindow`).
|
||||
|
||||
Verified 2026-10-31 by automated runs: started without focus the game keeps
|
||||
loading in the background; with focus it reaches the main menu, survives
|
||||
switching to another window for 6 s and back, and quits cleanly on a close
|
||||
request (no crash report, `MemLeaks_sacred2.xml` written).
|
||||
|
||||
## Consequences
|
||||
|
||||
- The resolution and fullscreen settings in Optionen no longer apply; the
|
||||
game always renders at the monitor's resolution.
|
||||
- Always the primary monitor.
|
||||
- In windowed mode the game presents with vsync (interval default); the
|
||||
options' vsync flag only applies to exclusive fullscreen.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Function map
|
||||
|
||||
Game functions we hook, call or have rewritten. Address = module + VA at the
|
||||
module's preferred base (as in Ghidra).
|
||||
|
||||
| Module | Address | Function | Status | Source |
|
||||
|---|---|---|---|---|
|
||||
| orcSystem.dll | 0x10018c60 | `Orc::Log::logMessage(const String&, LogMessageLevel)` (virtual, `__thiscall`, ret 8) | hooked: mirrored into sacred2K.log | `src/game/orc/log.cpp` |
|
||||
| s2render.dll | 0x1029a736 | `cDX9Base::_initDX(const sInitParams&)` (`__thiscall`, ret 4) | hooked: always windowed at monitor size; window style patched to borderless | `src/mods/borderless.cpp` |
|
||||
| s2render.dll | 0x1029d81e | `cDX9Base::flip()` | known: minimises on `D3DERR_DEVICELOST` | `docs/borderless.md` |
|
||||
| sacred2.exe | 0x00489ada | `s2_purecall()` handler: logs, then `DebugBreak` | known | `docs/borderless.md` |
|
||||
@@ -0,0 +1,39 @@
|
||||
# Game modules
|
||||
|
||||
Surveyed 2026-10-31 on Sacred 2 Gold (Steam) with Community Patch 2.65.1.
|
||||
|
||||
`sacred2.exe` (client, UI) statically imports the game DLLs, so all of them are
|
||||
mapped before our DLL's `DllMain` runs. All DLLs prefer image base 0x10000000
|
||||
and none is ASLR-enabled; the loader relocates all but the first, so their
|
||||
running bases differ from Ghidra's. `re::Address{module, va}` holds the
|
||||
Ghidra VA and resolves it at runtime; `sacred2K.log` lists the running bases.
|
||||
|
||||
| Module | Role | Exports | Notes |
|
||||
|---|---|---|---|
|
||||
| `sacred2.exe` | client: main loop, UI, input | 1 | image base 0x400000, sections `.text .rdata .rdata .data .data .rdata .common .rsrc`; "linker 4.0", PE date 2010 (modified by the Community Patch) |
|
||||
| `orcSystem.dll` | "Orc" engine base: `Orc::String`, `Log`, `LogManager`, files, timers, singletons | 3231 | 2009-07 |
|
||||
| `s2core.dll` | shared types: kernel events (`cEvent*`), world/sector positions, waypoints, type manager | 1966 | |
|
||||
| `s2logic.dll` | game logic: creatures, items, quests, dialogs, factions, drops, balance (`cLogicMgr`, `c*Mgr` singletons) | 5398 | |
|
||||
| `s2logicai.dll` | AI | 176 | |
|
||||
| `s2render.dll` | renderer (D3D9, Granny, PhysX) | 2258 | |
|
||||
|
||||
Other modules, not in the build check yet: `s2logicdll.dll`, `s2editor.dll`,
|
||||
`s2vista.dll`, the `plugin_*.dll` set from `plugins.cfg` (file system, zip,
|
||||
particle FX, Bink, Miles, OpenAL; loaded at runtime), third-party libraries
|
||||
(`granny2`, `binkw32`, `mss32`, PhysX, `stlport.5.0`, MSVC 8 runtime). `s2gs.exe`
|
||||
is the dedicated server.
|
||||
|
||||
Toolchain: MSVC 8 (VS 2005), STLport 5.0 strings and containers, C++ classes
|
||||
exported with decorated names (`?method@Class@@...`), so most functions of the
|
||||
DLLs are named. `tools/exports.mjs` lists them; Ghidra demangles them.
|
||||
|
||||
## Orc::String
|
||||
|
||||
STLport string with a 16-byte inline buffer, 0x18 bytes; see
|
||||
`src/game/orc/string.h`.
|
||||
|
||||
## Data
|
||||
|
||||
`pak\*.zip` (graphics, sectors, sound), `scripts\` (client/server/shared Lua-like
|
||||
text configs), `locale\de_DE` (German texts `global.res`, item name CSVs,
|
||||
speech). User options: `%LOCALAPPDATA%\Ascaron Entertainment\Sacred 2\options.txt`.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Todo
|
||||
|
||||
- Engine tick hook calling `re::EngineHeartbeat`, so the hang watchdog
|
||||
(`src/re/crash.cpp`) works.
|
||||
- Hooks in modules loaded at runtime (`plugin_*.dll`, `s2logicdll.dll`):
|
||||
resolve them when the module loads (LoadLibrary hook) and add them to the
|
||||
build check.
|
||||
- Log levels: find `LogMessageLevel` names and the threshold, so filtered
|
||||
messages can be shown too.
|
||||
- Autotest (start, menu clicks, screenshots) as in sacredK, once the menus are
|
||||
known.
|
||||
Reference in new issue
Block a user