Characters: Laden in the Esc menu, timed autosave
- Esc menu: "Exportieren" became "Laden"; it opens the load window, always in load mode. In a game every world loads (the current one reloads); a "(neu)" world stops the game (cCommand_armaStop) and starts the new one without the main menu. The character being played cannot be deleted. - Silent autosave every sacredK.ini [characters] AutosaveMinutes (default 10, 0 = off) into the character's autosave file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
feebe72ab7
commit
732d9c2abb
4 files changed
+157
-67
No files matched your search
+21
-9
@@ -261,21 +261,34 @@ game the character's.
|
||||
number). Story autosaves (engine event 0xf subtype 2 with the flag at +0x18,
|
||||
noted in `cEngine::ReceiveEvent`) go into the autosave file.
|
||||
|
||||
Silent autosave: 10 minutes after the last save of any kind (or the game's
|
||||
start), `SaveOnTick` does the direct save marked as an autosave, so it goes
|
||||
into the autosave file. While a window is open or a save runs it waits for
|
||||
the next tick. A character never saved yet gets its character file from it.
|
||||
Interval: `sacredK.ini` `[characters] AutosaveMinutes` (default 10, 0 off).
|
||||
|
||||
Direct save (K key, Esc menu "Speichern"): on the next engine tick, as the
|
||||
quicksave does (0x618ebb): `cUI_Manager::CanOpenWindow` (0x75a690),
|
||||
`cEngine::CaptureScreen` (0x613710, 1), description +0x6a, row +0x68 = 0,
|
||||
flag +0x54 |= 0x40. The Esc menu line keeps its action (Show
|
||||
`UI_WND_SAVEGAME`); `cUI_Manager::ReceiveEvent` (0x757120) turns that into the
|
||||
direct save while a game runs. Its text is patched at 0x6bc1f7 (id 0x41b is
|
||||
used by other windows too). Loading is from the main menu only.
|
||||
used by other windows too).
|
||||
|
||||
The Esc menu has no "Exportieren" (request 2026-10-20): `cUI_EscMenu::BuildLines`
|
||||
(0x6bb870, called by the constructor and on an event) makes five
|
||||
cUI_StaticText64 lines in the vector +0x158, 0x24 apart; each handles its own
|
||||
events. After it the UI_ESC_EXPORT line is removed and deleted, the lines
|
||||
below move up, and the window (0x108 x 0xc8) loses 0x24 of height, staying
|
||||
centred. With it the export screen (cUI_Character mode 5) is unreachable;
|
||||
its mod (charexport.cpp, an erase button for export slots) was deleted.
|
||||
The Esc menu: "Exportieren" became "Laden" (requests 2026-10-20). The line
|
||||
(text id 0x419 pushed at 0x6bc272, now "Laden") keeps its command, which shows
|
||||
UI_CHARACTER (the export screen) and hides the menu; in a game
|
||||
`UiManager_ReceiveEvent` turns that into showing the load window. The load
|
||||
window always opens in load mode (`cUI_Savegame::Show` vf24 0x71cfb0 picks
|
||||
save mode in a game; `SetMode` 0x71c4f0 (2) after it). In a game every
|
||||
world can be loaded, the current one too (a reload from its last save; the
|
||||
game's in-game load, engine flag 0x200). A "(neu)" world needs a new engine:
|
||||
the running game stops as "Beenden" stops it (`cCommand_armaStop`, execute
|
||||
0x755b30, vtable 0x88ee2c: posts 0x8001 to the main window, whose handler
|
||||
deletes the engine), and when the engine is gone (`OnGameEnd`) the new game's
|
||||
start command follows (0x8002), without the main menu. The character being
|
||||
played cannot be deleted. The export screen (cUI_Character
|
||||
mode 5) is unreachable; its mod (charexport.cpp, an erase button for export slots) was deleted.
|
||||
|
||||
### Migration
|
||||
|
||||
@@ -286,6 +299,5 @@ kept, the newest file overall is the shared hero.
|
||||
|
||||
## Open
|
||||
|
||||
- Silent autosave every 10 minutes.
|
||||
- Quickload (row 0) does not know characters.
|
||||
- Multiplayer (see `todo.md`).
|
||||
+11
-3
@@ -184,14 +184,22 @@ From the characters rework (`docs/characters.md`, request of 2026-10-19):
|
||||
are one byte per difficulty in cStatsManager +0x5704 (read 0x424be0) and
|
||||
travel in the save header (+0x88..+0x98) and the hero file. Done
|
||||
2026-10-20 (docs/characters.md).
|
||||
- **Silent autosave every 10 minutes** (a setting later) into the
|
||||
autosave file, without the busy dialog, like the direct save
|
||||
(`SaveOnTick`) but marked as an autosave.
|
||||
- **Autosave interval in the options window** (now only
|
||||
`sacredK.ini` `[characters] AutosaveMinutes`).
|
||||
- **Quickload (row 0) does not know characters.** Ctrl+Z saves into the
|
||||
current character, but the quickload event (0x116, engine +0x68 = row,
|
||||
flag 0x200) loads `GAME00.PAK`, which is not listed; make it reload the
|
||||
current character's world (or its newer autosave).
|
||||
|
||||
## Two saves in a row (2026-10-20, not reproduced)
|
||||
|
||||
Once two real saves ("saved", not "autosaved") went into the same character
|
||||
1.8 s apart, about 77 s after loading, with the timed autosave off. Save
|
||||
flag 0x40 is set in cEngine::ReceiveEvent at 0x618876 (quicksave),
|
||||
0x618f08 and 0x6190c5 (save event 0xf: +0xc row, +0x18 autosave flag). A
|
||||
log of the event that sets the flag (dwords 0..9) did not catch it again;
|
||||
add it back if it shows.
|
||||
|
||||
## Savegames screen redesign ("Spielen")
|
||||
|
||||
Asked 2026-10-20: redesign the load window (cUI_Savegame, the "Spielen"
|
||||
|
||||
+122
-55
@@ -56,6 +56,7 @@
|
||||
#include "mods/hud.h"
|
||||
#include "mods/input.h"
|
||||
#include "mods/userfolders.h"
|
||||
#include "re/config.h"
|
||||
#include "re/hook.h"
|
||||
#include "re/patch.h"
|
||||
#include "re/log.h"
|
||||
@@ -368,7 +369,11 @@ struct Current {
|
||||
std::string file;
|
||||
} g_current;
|
||||
|
||||
bool g_autosavePending = false;
|
||||
// The next cEngine::Save (render thread) is an autosave: a story autosave
|
||||
// (Engine_ReceiveEvent) or the timed one (SaveOnTick).
|
||||
std::atomic<bool> g_autosavePending{false};
|
||||
bool g_inGame = false; // a game runs (the load window opens from the Esc menu too)
|
||||
std::atomic<uint64_t> g_lastSaveAt{0}; // GetTickCount64 of the last save of any kind
|
||||
|
||||
std::string Description(const json& meta) {
|
||||
return Convert(FromUtf8(meta.value("name", std::string())) + L" - " + ClassName(meta.value("class", 0u)),
|
||||
@@ -436,7 +441,8 @@ void RebuildList() {
|
||||
highest = std::max(highest, std::min(d + 1, kDifficultyCount - 1));
|
||||
}
|
||||
}
|
||||
// New games need the shared hero; multiplayer games are not ours.
|
||||
// New games need the shared hero (in a game the running one stops
|
||||
// first, StartNewWorld); multiplayer games are not ours.
|
||||
const bool canStart = hasHero && !*reinterpret_cast<const uint8_t*>(kMultiplayer);
|
||||
for (int c = 0; c < kCampaignCount; ++c) {
|
||||
for (int d = 0; d < kDifficultyCount; ++d) {
|
||||
@@ -840,7 +846,6 @@ const Row* EditableRow(uint8_t* window) {
|
||||
|
||||
// "Neuer Charakter": in the main menu's load window (not in a game, not in
|
||||
// multiplayer).
|
||||
bool g_inGame = false;
|
||||
|
||||
bool NewCharacterAllowed(uint8_t* window) {
|
||||
return *reinterpret_cast<uint32_t*>(window + kSavegameMode) == kSavegameModeLoad && !g_inGame &&
|
||||
@@ -968,7 +973,12 @@ void __fastcall Savegame_Action(uint8_t* self, void* edx, int action) {
|
||||
GameCampaign() = static_cast<uint16_t>(row->choices[row->chosen].campaign);
|
||||
}
|
||||
}
|
||||
if (const Row* row = action == kSavegameActionDelete ? SelectedRow(self) : nullptr) {
|
||||
const Row* toDelete = action == kSavegameActionDelete ? SelectedRow(self) : nullptr;
|
||||
if (toDelete && g_inGame && toDelete->file == g_current.file) {
|
||||
re::Log("characters: %s is being played, not deleted", toDelete->file.c_str());
|
||||
return;
|
||||
}
|
||||
if (const Row* row = toDelete) {
|
||||
const std::string deleted = CharactersDir() + "deleted\\";
|
||||
CreateDirectoryA(deleted.c_str(), nullptr);
|
||||
for (const std::string& file : {row->file, AutosavePath(row->file)}) {
|
||||
@@ -1026,6 +1036,33 @@ void StartNewWorld(uint8_t* window, const Row& row, int campaign, int difficulty
|
||||
reinterpret_cast<ExecuteFn>(0x00755ac0)(&command, nullptr);
|
||||
}
|
||||
|
||||
// A new world from a running game: the game stops as the Esc menu's "Beenden"
|
||||
// stops it (cCommand_armaStop, execute 0x755b30, thiscall, plain ret: posts
|
||||
// 0x8001 to the main window, whose handler 0x811fa6 deletes the engine), and
|
||||
// the new game starts once the engine is gone (OnGameEnd): its start command
|
||||
// (0x8002) is handled right after the stop, without the main menu.
|
||||
struct PendingStart {
|
||||
uint8_t* window = nullptr;
|
||||
Row row;
|
||||
Choice world;
|
||||
} g_pendingStart;
|
||||
|
||||
void StopGameThenStart(uint8_t* window, const Row& row, const Choice& world) {
|
||||
g_pendingStart = {window, row, world};
|
||||
re::Log("characters: stopping the game for a new world of %s", row.file.c_str());
|
||||
struct StopCommand {
|
||||
uint32_t vtable;
|
||||
} command{0x0088ee2c};
|
||||
using ExecuteFn = void(__fastcall*)(void*, void*);
|
||||
reinterpret_cast<ExecuteFn>(0x00755b30)(&command, nullptr);
|
||||
}
|
||||
|
||||
void StartPendingWorld() {
|
||||
if (const PendingStart pending = std::exchange(g_pendingStart, {}); pending.window) {
|
||||
StartNewWorld(pending.window, pending.row, pending.world.campaign, pending.world.difficulty);
|
||||
}
|
||||
}
|
||||
|
||||
// A new character in `campaign`: the game's character creation, as the main
|
||||
// menu's campaign entries opened it (window command show UI_CHARACTER, the
|
||||
// campaign set first); its first save makes the character file.
|
||||
@@ -1091,7 +1128,11 @@ void DialogChoose(uint8_t* window, size_t index) {
|
||||
Savegame_Action_original(window, nullptr, kSavegameActionRescan);
|
||||
} else if (const Row row = g_rows[g_dialog.row]; row.choices[index].newWorld) {
|
||||
CloseDialog();
|
||||
if (g_inGame) {
|
||||
StopGameThenStart(window, row, row.choices[index]);
|
||||
} else {
|
||||
StartNewWorld(window, row, row.choices[index].campaign, row.choices[index].difficulty);
|
||||
}
|
||||
} else {
|
||||
g_rows[g_dialog.row].chosen = index;
|
||||
GameCampaign() = static_cast<uint16_t>(row.choices[index].campaign);
|
||||
@@ -1337,8 +1378,8 @@ uint32_t __fastcall Engine_Save(uint8_t* self, void* edx, const char* path);
|
||||
RE_HOOK(0x00619c90, Engine_Save);
|
||||
uint32_t __fastcall Engine_Save(uint8_t* self, void* edx, const char* path) {
|
||||
EnsureFolders();
|
||||
const bool autosave = g_autosavePending;
|
||||
g_autosavePending = false;
|
||||
const bool autosave = g_autosavePending.exchange(false);
|
||||
g_lastSaveAt = GetTickCount64();
|
||||
const std::string work = WorkDir();
|
||||
DeleteFileA((work + kWorkWorld).c_str());
|
||||
const uint32_t result = Engine_Save_original(self, edx, kGameWorld);
|
||||
@@ -1390,16 +1431,35 @@ constexpr size_t kEngineSaveDescription = 0x6a; // char[], "QUICKSAVE" for quic
|
||||
constexpr char kSaveDescription[] = "SAVE"; // not shown: the list shows the characters
|
||||
std::atomic<bool> g_saveRequested{false};
|
||||
|
||||
// Silent autosave (request of 2026-10-19: every 10 minutes, a setting later):
|
||||
// the same save, into the autosave file (Engine_Save with g_autosavePending),
|
||||
// sacredK.ini [characters] AutosaveMinutes (default 10, 0: off) after the
|
||||
// last save of any kind. While a window is open or a
|
||||
// save runs it waits for the next tick. A character never saved yet gets its
|
||||
// file from it (the list shows character files only).
|
||||
constexpr int kAutosaveMinutesDefault = 10;
|
||||
|
||||
uint64_t AutosaveIntervalMs() {
|
||||
static const int minutes = re::ConfigInt("characters", "AutosaveMinutes", kAutosaveMinutesDefault);
|
||||
return minutes > 0 ? uint64_t(minutes) * 60 * 1000 : UINT64_MAX;
|
||||
}
|
||||
|
||||
void SaveOnTick(uint8_t* engine) {
|
||||
if (!g_saveRequested.exchange(false)) {
|
||||
const bool requested = g_saveRequested.exchange(false);
|
||||
const bool autosaveDue = GetTickCount64() - g_lastSaveAt >= AutosaveIntervalMs();
|
||||
if (!requested && !autosaveDue) {
|
||||
return;
|
||||
}
|
||||
auto& flags = *reinterpret_cast<uint32_t*>(engine + kEngineFlags);
|
||||
uint32_t* ui = game::UiManager();
|
||||
if (!ui || !game::UiCanOpenWindow(ui) || (flags & kEngineSaving)) {
|
||||
if (requested) {
|
||||
re::Log("characters: no save now (a window is open or a save is running)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
g_autosavePending = !requested;
|
||||
g_lastSaveAt = GetTickCount64();
|
||||
using CaptureFn = void(__fastcall*)(void*, void*, int);
|
||||
reinterpret_cast<CaptureFn>(0x00613710)(engine, nullptr, 1);
|
||||
std::memcpy(engine + kEngineSaveDescription, kSaveDescription, sizeof(kSaveDescription));
|
||||
@@ -1408,69 +1468,68 @@ void SaveOnTick(uint8_t* engine) {
|
||||
}
|
||||
|
||||
// cUI_Manager::ReceiveEvent (vf00 0x757120, thiscall (event), ret 4) routes
|
||||
// window commands by name (game/ui/windows.h); in a game, showing the save
|
||||
// window (the Esc menu line) saves instead. The main menu's load window
|
||||
// still opens.
|
||||
// window commands by name (game/ui/windows.h). In a game:
|
||||
// - showing the save window (the Esc menu's "Speichern") saves instead;
|
||||
// - showing UI_CHARACTER (the Esc menu's "Laden", the former "Exportieren"
|
||||
// line, whose command shows the export screen) opens the load window.
|
||||
// The main menu's load window opens as before.
|
||||
constexpr size_t kUiEventCode = 4;
|
||||
constexpr size_t kUiEventSubtype = 8;
|
||||
constexpr uint32_t kUiEventWindowSubtype = 5;
|
||||
constexpr size_t kUiEventWindowName = 0x48; // char[0x20]
|
||||
constexpr char kCharacterWindowName[] = "UI_CHARACTER";
|
||||
|
||||
thread_local bool g_openingLoadWindow = false; // our own Show of the save window passes
|
||||
|
||||
bool IsShowCommand(const uint8_t* event, const char* window, size_t size) {
|
||||
return *reinterpret_cast<const uint32_t*>(event + kUiEventCode) ==
|
||||
static_cast<uint32_t>(game::WindowCommand::Show) &&
|
||||
*reinterpret_cast<const uint32_t*>(event + kUiEventSubtype) == kUiEventWindowSubtype &&
|
||||
std::strncmp(reinterpret_cast<const char*>(event + kUiEventWindowName), window, size) == 0;
|
||||
}
|
||||
|
||||
int __fastcall UiManager_ReceiveEvent(void* self, void* edx, uint8_t* event);
|
||||
RE_HOOK(0x00757120, UiManager_ReceiveEvent);
|
||||
int __fastcall UiManager_ReceiveEvent(void* self, void* edx, uint8_t* event) {
|
||||
if (g_inGame && *reinterpret_cast<uint32_t*>(event + kUiEventCode) == static_cast<uint32_t>(game::WindowCommand::Show) &&
|
||||
*reinterpret_cast<uint32_t*>(event + kUiEventSubtype) == kUiEventWindowSubtype &&
|
||||
std::strncmp(reinterpret_cast<const char*>(event + kUiEventWindowName), kSaveWindowName,
|
||||
sizeof(kSaveWindowName)) == 0) {
|
||||
if (g_inGame && !g_openingLoadWindow) {
|
||||
if (IsShowCommand(event, kSaveWindowName, sizeof(kSaveWindowName))) {
|
||||
RequestSave();
|
||||
return 1;
|
||||
}
|
||||
if (IsShowCommand(event, kCharacterWindowName, sizeof(kCharacterWindowName))) {
|
||||
g_openingLoadWindow = true;
|
||||
game::SendWindowCommand(static_cast<uint32_t*>(self), kSaveWindowName, game::WindowCommand::Show);
|
||||
g_openingLoadWindow = false;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return UiManager_ReceiveEvent_original(self, edx, event);
|
||||
}
|
||||
|
||||
// The Esc menu's line "Spielstände" (built by 0x6bb870, text id 0x41b pushed
|
||||
// at 0x6bc1f7; the id is used by other windows too) reads "Speichern".
|
||||
constexpr uint32_t kEscMenuSaveLineText = 0x006bc1f7;
|
||||
constexpr uint32_t kSavegamesText = 0x41b;
|
||||
|
||||
// The Esc menu without "Exportieren" (request of 2026-10-20; exported heroes
|
||||
// are gone). cUI_EscMenu::BuildLines (0x6bb870, thiscall, plain ret; called
|
||||
// by the constructor 0x6bae60 and on an event, 0x6bc497) makes five
|
||||
// cUI_StaticText64 lines (constructor 0x753470) in the vector at +0x158, 0x24
|
||||
// apart (y 8 .. 0x98); each line handles its own events. The window is
|
||||
// 0x108 x 0xc8, centred (0x6bae9b). The export line goes, the lines below it
|
||||
// move up and the window loses one line's height, staying centred.
|
||||
constexpr size_t kEscMenuLines = 0x158;
|
||||
constexpr char kEscExportLine[] = "UI_ESC_EXPORT";
|
||||
constexpr int kEscLinePitch = 0x24;
|
||||
constexpr int16_t kEscMenuHeight = 0xc8;
|
||||
|
||||
void __fastcall EscMenu_BuildLines(uint8_t* self, void* edx);
|
||||
RE_HOOK(0x006bb870, EscMenu_BuildLines);
|
||||
void __fastcall EscMenu_BuildLines(uint8_t* self, void* edx) {
|
||||
EscMenu_BuildLines_original(self, edx);
|
||||
bool removed = false;
|
||||
for (uint8_t* line : game::ControlsIn(self, kEscMenuLines)) {
|
||||
auto& rect = *reinterpret_cast<game::UiRect*>(line + game::kControlRect);
|
||||
if (removed) {
|
||||
rect.y -= kEscLinePitch;
|
||||
} else if (game::ControlNamed(line, kEscExportLine)) {
|
||||
game::RemoveControl(self, kEscMenuLines, line);
|
||||
game::DeleteControl(line);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
auto& window = *reinterpret_cast<game::UiRect*>(self + game::kControlRect);
|
||||
if (removed && window.h == kEscMenuHeight) {
|
||||
window.h -= kEscLinePitch;
|
||||
window.y += kEscLinePitch / 2;
|
||||
}
|
||||
if (!removed) {
|
||||
re::Log("characters: Esc menu has no %s line", kEscExportLine);
|
||||
// cUI_Savegame::Show (vf24 0x71cfb0, thiscall (shown), ret 4) picks load mode
|
||||
// (2) in the main menu and save mode (1) in a game (0x71cfd2). Saving is
|
||||
// direct now: the window always opens for loading (SetMode 0x71c4f0,
|
||||
// thiscall (mode), ret 4; it also disables the hidden "Speichern" button).
|
||||
void __fastcall Savegame_Show(uint8_t* self, void* edx, char shown);
|
||||
RE_HOOK(0x0071cfb0, Savegame_Show);
|
||||
void __fastcall Savegame_Show(uint8_t* self, void* edx, char shown) {
|
||||
Savegame_Show_original(self, edx, shown);
|
||||
if (shown && *reinterpret_cast<uint32_t*>(self + kSavegameMode) == kSavegameModeSave) {
|
||||
using SetModeFn = void(__fastcall*)(void*, void*, uint32_t);
|
||||
reinterpret_cast<SetModeFn>(0x0071c4f0)(self, nullptr, kSavegameModeLoad);
|
||||
}
|
||||
}
|
||||
|
||||
// The Esc menu (cUI_EscMenu::BuildLines 0x6bb870): "Spielstände" (text id
|
||||
// 0x41b pushed at 0x6bc1f7; the id is used by other windows too) reads
|
||||
// "Speichern"; "Exportieren" (text id 0x419 pushed at 0x6bc272, its command
|
||||
// shows UI_CHARACTER and hides the menu) reads "Laden" (request of
|
||||
// 2026-10-20).
|
||||
constexpr uint32_t kEscMenuSaveLineText = 0x006bc1f7;
|
||||
constexpr uint32_t kSavegamesText = 0x41b;
|
||||
constexpr uint32_t kEscMenuExportLineText = 0x006bc272;
|
||||
constexpr uint32_t kExportText = 0x419;
|
||||
|
||||
// The load window's second line of a row showed the (preview) file name; it
|
||||
// shows the level and the chosen difficulty instead. Row texts are set every
|
||||
// frame (0x71d830): line 1 the save's name (+0x204), line 2 the file name (+4,
|
||||
@@ -1529,14 +1588,22 @@ void Startup() {
|
||||
re::RedirectCall(kRowFileNameCall, kStaticSetText, reinterpret_cast<const void*>(&SetRowSecondLine));
|
||||
static const uint32_t kSaveText = game::RegisterText(L"Speichern");
|
||||
re::PatchPushImm32(kEscMenuSaveLineText, kSavegamesText, kSaveText);
|
||||
static const uint32_t kLoadText = game::RegisterText(L"Laden");
|
||||
re::PatchPushImm32(kEscMenuExportLineText, kExportText, kLoadText);
|
||||
}
|
||||
|
||||
struct Init {
|
||||
Init() {
|
||||
userfolders::SetGameSaveSubfolder(kGameSaveSubfolder);
|
||||
game::OnEngineTick(&SaveOnTick);
|
||||
game::OnGameStart([] { g_inGame = true; });
|
||||
game::OnGameEnd([] { g_inGame = false; });
|
||||
game::OnGameStart([] {
|
||||
g_inGame = true;
|
||||
g_lastSaveAt = GetTickCount64(); // the autosave counts from the start
|
||||
});
|
||||
game::OnGameEnd([] {
|
||||
g_inGame = false;
|
||||
StartPendingWorld();
|
||||
});
|
||||
}
|
||||
} init;
|
||||
|
||||
|
||||
@@ -334,10 +334,13 @@
|
||||
0x0061b790 cEngine::Load # (path, quiet), ret 8: reads a GAME.PAK
|
||||
0x00619c90 cEngine::Save # (path), ret 4; render thread, engine +0x54 & 0x40
|
||||
0x0060dc00 cEngine::InitGame # (int), ret 4: new game, start hero
|
||||
0x0071c4f0 cUI_Savegame::SetMode # (mode), ret 4: 1 save, 2 load (+0x75c)
|
||||
0x0071cfb0 cUI_Savegame::Show # vf24 (bool), ret 4: picks save mode in a game
|
||||
0x0071c520 cUI_Savegame::Action # (action), ret 4: 2 save, 4 load, 8 delete, 10 rescan, 0xb default
|
||||
0x0071e330 cUI_Savegame::Scan # (dir, multiplayer), ret 8: "./SAVE/*.PAK"
|
||||
0x0071be90 cUI_Savegame::SendSaveLoad # (kind, row): engine event 0xf
|
||||
0x00755ac0 cCommand_armaPlay::execute # posts 0x8002 (mode, argument) to the main window
|
||||
0x00755b30 cCommand_armaStop::execute # vf04, plain ret: ends the game, posts 0x8001 to the main window (engine deleted, main menu)
|
||||
0x007d7c40 Session_Instance
|
||||
0x007d7cf0 Session::SetHeroClass # (class) -> +0x44, ret 4
|
||||
0x00417e50 GameSetup_Instance # 0x12e6c bytes; +0 set by both character-screen starts
|
||||
|
||||
Reference in new issue
Block a user