From cf5f865a23e84a23e7ec27cf2d05f0a262f3b9d5 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Sat, 3 Jun 2023 08:06:49 +0300 Subject: [PATCH] Add load/save global vars (#296) --- src/game.cc | 8 +-- src/interpreter.cc | 10 ++++ src/loadsave.cc | 86 ++++++++++++++++++++++++++++ src/sfall_global_scripts.cc | 7 +++ src/sfall_global_scripts.h | 1 + src/sfall_global_vars.cc | 111 +++++++++++++++++++++++++++--------- src/sfall_global_vars.h | 18 +++--- src/sfall_opcodes.cc | 8 +-- 8 files changed, 206 insertions(+), 43 deletions(-) diff --git a/src/game.cc b/src/game.cc index c215edf..abe6613 100644 --- a/src/game.cc +++ b/src/game.cc @@ -341,8 +341,8 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4 // SFALL premadeCharactersInit(); - if (!sfallGlobalVarsInit()) { - debugPrint("Failed on sfallGlobalVarsInit"); + if (!sfall_gl_vars_init()) { + debugPrint("Failed on sfall_gl_vars_init"); return -1; } @@ -409,7 +409,7 @@ void gameReset() _init_options_menu(); // SFALL - sfallGlobalVarsReset(); + sfall_gl_vars_reset(); sfallListsReset(); messageListRepositoryReset(); sfallArraysReset(); @@ -425,7 +425,7 @@ void gameExit() sfall_gl_scr_exit(); sfallArraysExit(); sfallListsExit(); - sfallGlobalVarsExit(); + sfall_gl_vars_exit(); premadeCharactersExit(); tileDisable(); diff --git a/src/interpreter.cc b/src/interpreter.cc index b2e0cd1..ab866ce 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -14,6 +14,7 @@ #include "interpreter_lib.h" #include "memory_manager.h" #include "platform_compat.h" +#include "sfall_global_scripts.h" #include "svga.h" namespace fallout { @@ -3011,6 +3012,15 @@ Program* runScript(char* name) // 0x46E1EC void _updatePrograms() { + // CE: Implementation is different. Sfall inserts global scripts into + // program list upon creation, so engine does not diffirentiate between + // global and normal scripts. Global scripts in CE are not part of program + // list, so we need a separate call to continue execution (usually + // non-critical calls scheduled from managed windows). One more thing to + // note is that global scripts in CE cannot handle conditional/timed procs + // (which are not used anyway). + sfall_gl_scr_update(_cpuBurstSize); + ProgramListNode* curr = gInterpreterProgramListHead; while (curr != NULL) { ProgramListNode* next = curr->next; diff --git a/src/loadsave.cc b/src/loadsave.cc index ebee1e6..e5d8db6 100644 --- a/src/loadsave.cc +++ b/src/loadsave.cc @@ -46,6 +46,7 @@ #include "scripts.h" #include "settings.h" #include "sfall_global_scripts.h" +#include "sfall_global_vars.h" #include "skill.h" #include "stat.h" #include "svga.h" @@ -1588,6 +1589,73 @@ static int lsgPerformSaveGame() fileClose(_flptr); + // SFALL: Save sfallgv.sav. + snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + strcat(_gmpath, "sfallgv.sav"); + + _flptr = fileOpen(_gmpath, "wb"); + if (_flptr != NULL) { + do { + if (!sfall_gl_vars_save(_flptr)) { + debugPrint("LOADSAVE (SFALL): ** Error saving global vars **\n"); + break; + } + + // TODO: For now fill remaining sections with zeros to that Sfall + // can successfully read our global vars and skip the rest. + + int nextObjectId = 0; + if (fileWrite(&nextObjectId, sizeof(nextObjectId), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving next object id **\n"); + break; + } + + int addedYears = 0; + if (fileWrite(&addedYears, sizeof(addedYears), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving added years **\n"); + break; + } + + int fakeTraitsCount = 0; + if (fileWrite(&fakeTraitsCount, sizeof(fakeTraitsCount), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving fake traits **\n"); + break; + } + + int fakePerksCount = 0; + if (fileWrite(&fakePerksCount, sizeof(fakePerksCount), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving fake perks **\n"); + break; + } + + int fakeSelectablePerksCount = 0; + if (fileWrite(&fakeSelectablePerksCount, sizeof(fakeSelectablePerksCount), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving fake selectable perks **\n"); + break; + } + + int arraysCountOld = 0; + if (fileWrite(&arraysCountOld, sizeof(arraysCountOld), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving arrays (old fmt) **\n"); + break; + } + + int arraysCountNew = 0; + if (fileWrite(&arraysCountNew, sizeof(arraysCountNew), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving arrays (new fmt) **\n"); + break; + } + + int drugPidsCount = 0; + if (fileWrite(&drugPidsCount, sizeof(drugPidsCount), 1, _flptr) != 1) { + debugPrint("LOADSAVE (SFALL): ** Error saving drug pids **\n"); + break; + } + } while (0); + + fileClose(_flptr); + } + snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); _MapDirErase(_gmpath, "BAK"); @@ -1663,6 +1731,24 @@ static int lsgLoadGameInSlot(int slot) debugPrint("LOADSAVE: Total load data read: %ld bytes.\n", fileTell(_flptr)); fileClose(_flptr); + // SFALL: Load sfallgv.sav. + snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + strcat(_gmpath, "sfallgv.sav"); + + _flptr = fileOpen(_gmpath, "rb"); + if (_flptr != NULL) { + do { + if (!sfall_gl_vars_load(_flptr)) { + debugPrint("LOADSAVE (SFALL): ** Error loading global vars **\n"); + break; + } + + // TODO: For now silently ignore remaining sections. + } while (0); + + fileClose(_flptr); + } + snprintf(_str, sizeof(_str), "%s\\", "MAPS"); _MapDirErase(_str, "BAK"); _proto_dude_update_gender(); diff --git a/src/sfall_global_scripts.cc b/src/sfall_global_scripts.cc index 1cd65d7..067d4fb 100644 --- a/src/sfall_global_scripts.cc +++ b/src/sfall_global_scripts.cc @@ -210,4 +210,11 @@ bool sfall_gl_scr_is_loaded(Program* program) return true; } +void sfall_gl_scr_update(int burstSize) +{ + for (auto& scr : state->globalScripts) { + _interpret(scr.program, burstSize); + } +} + } // namespace fallout diff --git a/src/sfall_global_scripts.h b/src/sfall_global_scripts.h index bf8eadf..7ed11c7 100644 --- a/src/sfall_global_scripts.h +++ b/src/sfall_global_scripts.h @@ -17,6 +17,7 @@ void sfall_gl_scr_process_worldmap(); void sfall_gl_scr_set_repeat(Program* program, int frames); void sfall_gl_scr_set_type(Program* program, int type); bool sfall_gl_scr_is_loaded(Program* program); +void sfall_gl_scr_update(int burstSize); } // namespace fallout diff --git a/src/sfall_global_vars.cc b/src/sfall_global_vars.cc index b62c0dc..b01c337 100644 --- a/src/sfall_global_vars.cc +++ b/src/sfall_global_vars.cc @@ -10,72 +10,127 @@ struct SfallGlobalVarsState { std::unordered_map vars; }; -static bool sfallGlobalVarsStore(uint64_t key, int value); -static bool sfallGlobalVarsFetch(uint64_t key, int& value); +#pragma pack(push) +#pragma pack(8) -static SfallGlobalVarsState* _state; +/// Matches Sfall's `GlobalVar` to maintain binary compatibility. +struct GlobalVarEntry { + uint64_t key; + int32_t value; + int32_t unused; +}; -bool sfallGlobalVarsInit() +#pragma pack(pop) + +static bool sfall_gl_vars_store(uint64_t key, int value); +static bool sfall_gl_vars_fetch(uint64_t key, int& value); + +static SfallGlobalVarsState* sfall_gl_vars_state = nullptr; + +bool sfall_gl_vars_init() { - _state = new (std::nothrow) SfallGlobalVarsState(); - if (_state == nullptr) { + sfall_gl_vars_state = new (std::nothrow) SfallGlobalVarsState(); + if (sfall_gl_vars_state == nullptr) { return false; } return true; } -void sfallGlobalVarsReset() +void sfall_gl_vars_reset() { - _state->vars.clear(); + sfall_gl_vars_state->vars.clear(); } -void sfallGlobalVarsExit() +void sfall_gl_vars_exit() { - if (_state != nullptr) { - delete _state; - _state = nullptr; + if (sfall_gl_vars_state != nullptr) { + delete sfall_gl_vars_state; + sfall_gl_vars_state = nullptr; } } -bool sfallGlobalVarsStore(const char* key, int value) +bool sfall_gl_vars_save(File* stream) +{ + int count = static_cast(sfall_gl_vars_state->vars.size()); + if (fileWrite(&count, sizeof(count), 1, stream) != 1) { + return false; + } + + GlobalVarEntry entry = { 0 }; + for (auto& pair : sfall_gl_vars_state->vars) { + entry.key = pair.first; + entry.value = pair.second; + + if (fileWrite(&entry, sizeof(entry), 1, stream) != 1) { + return false; + } + } + + return true; +} + +bool sfall_gl_vars_load(File* stream) +{ + int count; + if (fileRead(&count, sizeof(count), 1, stream) != 1) { + return false; + } + + sfall_gl_vars_state->vars.reserve(count); + + GlobalVarEntry entry; + while (count > 0) { + if (fileRead(&entry, sizeof(entry), 1, stream) != 1) { + return false; + } + + sfall_gl_vars_state->vars[entry.key] = static_cast(entry.value); + + count--; + } + + return true; +} + +bool sfall_gl_vars_store(const char* key, int value) { if (strlen(key) != 8) { return false; } uint64_t numericKey = *(reinterpret_cast(key)); - return sfallGlobalVarsStore(numericKey, value); + return sfall_gl_vars_store(numericKey, value); } -bool sfallGlobalVarsStore(int key, int value) +bool sfall_gl_vars_store(int key, int value) { - return sfallGlobalVarsStore(static_cast(key), value); + return sfall_gl_vars_store(static_cast(key), value); } -bool sfallGlobalVarsFetch(const char* key, int& value) +bool sfall_gl_vars_fetch(const char* key, int& value) { if (strlen(key) != 8) { return false; } uint64_t numericKey = *(reinterpret_cast(key)); - return sfallGlobalVarsFetch(numericKey, value); + return sfall_gl_vars_fetch(numericKey, value); } -bool sfallGlobalVarsFetch(int key, int& value) +bool sfall_gl_vars_fetch(int key, int& value) { - return sfallGlobalVarsFetch(static_cast(key), value); + return sfall_gl_vars_fetch(static_cast(key), value); } -static bool sfallGlobalVarsStore(uint64_t key, int value) +static bool sfall_gl_vars_store(uint64_t key, int value) { - auto it = _state->vars.find(key); - if (it == _state->vars.end()) { - _state->vars.emplace(key, value); + auto it = sfall_gl_vars_state->vars.find(key); + if (it == sfall_gl_vars_state->vars.end()) { + sfall_gl_vars_state->vars.emplace(key, value); } else { if (value == 0) { - _state->vars.erase(it); + sfall_gl_vars_state->vars.erase(it); } else { it->second = value; } @@ -84,10 +139,10 @@ static bool sfallGlobalVarsStore(uint64_t key, int value) return true; } -static bool sfallGlobalVarsFetch(uint64_t key, int& value) +static bool sfall_gl_vars_fetch(uint64_t key, int& value) { - auto it = _state->vars.find(key); - if (it == _state->vars.end()) { + auto it = sfall_gl_vars_state->vars.find(key); + if (it == sfall_gl_vars_state->vars.end()) { return false; } diff --git a/src/sfall_global_vars.h b/src/sfall_global_vars.h index 184fc6f..dfa7133 100644 --- a/src/sfall_global_vars.h +++ b/src/sfall_global_vars.h @@ -1,15 +1,19 @@ #ifndef FALLOUT_SFALL_GLOBAL_VARS_H_ #define FALLOUT_SFALL_GLOBAL_VARS_H_ +#include "db.h" + namespace fallout { -bool sfallGlobalVarsInit(); -void sfallGlobalVarsReset(); -void sfallGlobalVarsExit(); -bool sfallGlobalVarsStore(const char* key, int value); -bool sfallGlobalVarsStore(int key, int value); -bool sfallGlobalVarsFetch(const char* key, int& value); -bool sfallGlobalVarsFetch(int key, int& value); +bool sfall_gl_vars_init(); +void sfall_gl_vars_reset(); +void sfall_gl_vars_exit(); +bool sfall_gl_vars_save(File* stream); +bool sfall_gl_vars_load(File* stream); +bool sfall_gl_vars_store(const char* key, int value); +bool sfall_gl_vars_store(int key, int value); +bool sfall_gl_vars_fetch(const char* key, int& value); +bool sfall_gl_vars_fetch(int key, int& value); } // namespace fallout diff --git a/src/sfall_opcodes.cc b/src/sfall_opcodes.cc index 3cabe4a..ea45d4e 100644 --- a/src/sfall_opcodes.cc +++ b/src/sfall_opcodes.cc @@ -167,9 +167,9 @@ static void opSetGlobalVar(Program* program) if ((variable.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { const char* key = programGetString(program, variable.opcode, variable.integerValue); - sfallGlobalVarsStore(key, value.integerValue); + sfall_gl_vars_store(key, value.integerValue); } else if (variable.opcode == VALUE_TYPE_INT) { - sfallGlobalVarsStore(variable.integerValue, value.integerValue); + sfall_gl_vars_store(variable.integerValue, value.integerValue); } } @@ -181,9 +181,9 @@ static void opGetGlobalInt(Program* program) int value = 0; if ((variable.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { const char* key = programGetString(program, variable.opcode, variable.integerValue); - sfallGlobalVarsFetch(key, value); + sfall_gl_vars_fetch(key, value); } else if (variable.opcode == VALUE_TYPE_INT) { - sfallGlobalVarsFetch(variable.integerValue, value); + sfall_gl_vars_fetch(variable.integerValue, value); } programStackPushInteger(program, value);