From 994afedd33de0e298425933945a47e6dacb3a138 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Tue, 17 Sep 2019 20:09:37 -0700 Subject: GLK: ADRIFT: Encapsulation serialization code into their own classes --- engines/glk/adrift/adrift.cpp | 6 +- engines/glk/adrift/sclibrar.cpp | 10 +- engines/glk/adrift/scmemos.cpp | 7 +- engines/glk/adrift/scprotos.h | 8 - engines/glk/adrift/scrunner.cpp | 11 +- engines/glk/adrift/scserial.cpp | 615 ----------------------------------- engines/glk/adrift/serialization.cpp | 517 +++++++++++++++++++++++++++++ engines/glk/adrift/serialization.h | 143 ++++++++ engines/glk/module.mk | 2 +- 9 files changed, 682 insertions(+), 637 deletions(-) delete mode 100644 engines/glk/adrift/scserial.cpp create mode 100644 engines/glk/adrift/serialization.cpp create mode 100644 engines/glk/adrift/serialization.h diff --git a/engines/glk/adrift/adrift.cpp b/engines/glk/adrift/adrift.cpp index b3768643a0..ed83944595 100644 --- a/engines/glk/adrift/adrift.cpp +++ b/engines/glk/adrift/adrift.cpp @@ -23,6 +23,7 @@ #include "glk/adrift/adrift.h" #include "glk/adrift/os_glk.h" #include "glk/adrift/scprotos.h" +#include "glk/adrift/serialization.h" namespace Glk { namespace Adrift { @@ -39,11 +40,12 @@ void Adrift::runGame() { } Common::Error Adrift::readSaveData(Common::SeekableReadStream *rs) { - return ser_load_game((sc_gameref_t)gsc_game, if_read_saved_game, rs) ? Common::kNoError : Common::kReadingFailed; + LoadSerializer ser((sc_gameref_t)gsc_game, if_read_saved_game, rs); + return ser.load() ? Common::kNoError : Common::kReadingFailed; } Common::Error Adrift::writeGameData(Common::WriteStream *ws) { - ser_save_game((sc_gameref_t)gsc_game, if_write_saved_game, ws); + SaveSerializer ser((sc_gameref_t)gsc_game, if_write_saved_game, ws); return Common::kNoError; } diff --git a/engines/glk/adrift/sclibrar.cpp b/engines/glk/adrift/sclibrar.cpp index 51e4224eeb..c17a3a7ae3 100644 --- a/engines/glk/adrift/sclibrar.cpp +++ b/engines/glk/adrift/sclibrar.cpp @@ -20,9 +20,10 @@ * */ -#include "glk/adrift/scare.h" +#include "glk/adrift/adrift.h" #include "glk/adrift/scprotos.h" #include "glk/adrift/scgamest.h" +#include "glk/adrift/serialization.h" namespace Glk { namespace Adrift { @@ -8205,7 +8206,7 @@ sc_bool lib_cmd_get_off(sc_gameref_t game) { */ sc_bool lib_cmd_save(sc_gameref_t game) { if (if_confirm(SC_CONF_SAVE)) { - if (ser_save_game_prompted(game)) + if (g_vm->saveGame().getCode() == Common::kNoError) if_print_string("Ok.\n"); else if_print_string("Save failed.\n"); @@ -8217,12 +8218,13 @@ sc_bool lib_cmd_save(sc_gameref_t game) { sc_bool lib_cmd_restore(sc_gameref_t game) { if (if_confirm(SC_CONF_RESTORE)) { - if (ser_load_game_prompted(game)) { + if (g_vm->loadGame().getCode() == Common::kNoError) { if_print_string("Ok.\n"); game->is_running = FALSE; game->do_restore = TRUE; - } else + } else { if_print_string("Restore failed.\n"); + } } game->is_admin = TRUE; diff --git a/engines/glk/adrift/scmemos.cpp b/engines/glk/adrift/scmemos.cpp index 88720f28bf..c26f6840c0 100644 --- a/engines/glk/adrift/scmemos.cpp +++ b/engines/glk/adrift/scmemos.cpp @@ -22,6 +22,7 @@ #include "glk/adrift/scare.h" #include "glk/adrift/scprotos.h" +#include "glk/adrift/serialization.h" namespace Glk { namespace Adrift { @@ -201,7 +202,8 @@ void memo_save_game(sc_memo_setref_t memento, sc_gameref_t game) { memo->length = 0; /* Serialize the given game into this memo. */ - ser_save_game(game, memo_save_game_callback, memo); + SaveSerializer ser(game, memo_save_game_callback, memo); + ser.save(); /* * If serialization worked (failure would be a surprise), advance the @@ -263,7 +265,8 @@ sc_bool memo_load_game(sc_memo_setref_t memento, sc_gameref_t game) { * Deserialize the given game from this memo; failure would be somewhat * of a surprise here. */ - status = ser_load_game(game, memo_load_game_callback, memo); + LoadSerializer ser(game, memo_load_game_callback, memo); + status = ser.load(); if (!status) sc_error("memo_load_game: warning: game load failed\n"); diff --git a/engines/glk/adrift/scprotos.h b/engines/glk/adrift/scprotos.h index 4172cfea0c..37d2bdc19d 100644 --- a/engines/glk/adrift/scprotos.h +++ b/engines/glk/adrift/scprotos.h @@ -738,14 +738,6 @@ extern sc_bool obj_shows_initial_description(sc_gameref_t game, sc_int object); extern void obj_turn_update(sc_gameref_t game); extern void obj_debug_trace(sc_bool flag); -/* Game serialization functions. */ -extern void ser_save_game(sc_gameref_t game, - sc_write_callbackref_t callback, void *opaque); -extern sc_bool ser_save_game_prompted(sc_gameref_t game); -extern sc_bool ser_load_game(sc_gameref_t game, - sc_read_callbackref_t callback, void *opaque); -extern sc_bool ser_load_game_prompted(sc_gameref_t game); - /* Locale support, and locale-sensitive functions. */ extern void loc_detect_game_locale(sc_prop_setref_t bundle); extern sc_bool loc_set_locale(const sc_char *name); diff --git a/engines/glk/adrift/scrunner.cpp b/engines/glk/adrift/scrunner.cpp index 95c8f1906a..288ca834e2 100644 --- a/engines/glk/adrift/scrunner.cpp +++ b/engines/glk/adrift/scrunner.cpp @@ -23,6 +23,7 @@ #include "glk/adrift/adrift.h" #include "glk/adrift/scprotos.h" #include "glk/adrift/scgamest.h" +#include "glk/adrift/serialization.h" namespace Glk { namespace Adrift { @@ -1734,16 +1735,16 @@ void run_save(sc_gameref_t game, sc_write_callbackref_t callback, void *opaque) assert(gs_is_game_valid(game)); assert(callback); - ser_save_game(game, callback, opaque); + SaveSerializer ser(game, callback, opaque); + ser.save(); } sc_bool run_save_prompted(sc_gameref_t game) { assert(gs_is_game_valid(game)); - return ser_save_game_prompted(game); + return g_vm->saveGame().getCode() == Common::kNoError; } - /* * run_restore_common() * run_restore() @@ -1764,8 +1765,8 @@ static sc_bool run_restore_common(sc_gameref_t game, sc_read_callbackref_t callb * callback of NULL; callback cannot be NULL for run_restore() calls. */ is_running = game->is_running; - status = callback ? ser_load_game(game, callback, opaque) - : ser_load_game_prompted(game); + LoadSerializer ser(game, callback, opaque); + status = ser.load(); if (status) { /* Loading a game clears is_running -- restore it here. */ game->is_running = is_running; diff --git a/engines/glk/adrift/scserial.cpp b/engines/glk/adrift/scserial.cpp deleted file mode 100644 index fb771de2ae..0000000000 --- a/engines/glk/adrift/scserial.cpp +++ /dev/null @@ -1,615 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "glk/adrift/adrift.h" -#include "glk/adrift/scprotos.h" -#include "glk/adrift/scgamest.h" -#include "common/textconsole.h" - -namespace Glk { -namespace Adrift { - -/* Assorted definitions and constants. */ -static const sc_char NEWLINE = '\n'; -static const sc_char CARRIAGE_RETURN = '\r'; -static const sc_char NUL = '\0'; - -enum { BUFFER_SIZE = 4096 }; - -/* Output buffer. */ -static sc_byte *ser_buffer = NULL; -static sc_int ser_buffer_length = 0; - -/* Callback and opaque pointer for use by output functions. */ -static sc_write_callbackref_t ser_callback = NULL; -static void *ser_opaque = NULL; - - -/* - * ser_flush() - * ser_buffer_character() - * - * Flush pending buffer contents; add a character to the buffer. - */ -static void ser_flush(sc_bool is_final) { - if (is_final) { - ser_callback(ser_opaque, ser_buffer, ser_buffer_length); - - sc_free(ser_buffer); - ser_buffer = nullptr; - ser_buffer_length = 0; - } -} - -static void ser_buffer_character(sc_char character) { - /* Allocate the buffer if not yet done. */ - if (!ser_buffer) { - assert(ser_buffer_length == 0); - ser_buffer = (sc_byte *)sc_malloc(BUFFER_SIZE); - } - - /* Add to the buffer, with intermediate flush if filled. */ - ser_buffer[ser_buffer_length++] = character; - if (ser_buffer_length == BUFFER_SIZE) - error("Ran out of serialization buffer"); -} - - -/* - * ser_buffer_buffer() - * ser_buffer_string() - * ser_buffer_int() - * ser_buffer_int_special() - * ser_buffer_uint() - * ser_buffer_boolean() - * - * Buffer a buffer, a string, an unsigned and signed integer, and a boolean. - */ -static void ser_buffer_buffer(const sc_char *buffer, sc_int length) { - sc_int index_; - - /* Add each character to the buffer. */ - for (index_ = 0; index_ < length; index_++) - ser_buffer_character(buffer[index_]); -} - -static void ser_buffer_string(const sc_char *string) { - /* Buffer string, followed by DOS style end-of-line. */ - ser_buffer_buffer(string, strlen(string)); - ser_buffer_character(CARRIAGE_RETURN); - ser_buffer_character(NEWLINE); -} - -static void ser_buffer_int(sc_int value) { - sc_char buffer[32]; - - /* Convert to a string and buffer that. */ - sprintf(buffer, "%ld", value); - ser_buffer_string(buffer); -} - -static void ser_buffer_int_special(sc_int value) { - sc_char buffer[32]; - - /* Weirdo formatting for compatibility. */ - sprintf(buffer, "% ld ", value); - ser_buffer_string(buffer); -} - -static void ser_buffer_uint(sc_uint value) { - sc_char buffer[32]; - - /* Convert to a string and buffer that. */ - sprintf(buffer, "%lu", value); - ser_buffer_string(buffer); -} - -static void ser_buffer_boolean(sc_bool boolean) { - /* Write a 1 for TRUE, 0 for FALSE. */ - ser_buffer_string(boolean ? "1" : "0"); -} - - -/* - * ser_save_game() - * - * Serialize a game and save its state using the given callback and opaque. - */ -void ser_save_game(sc_gameref_t game, sc_write_callbackref_t callback, void *opaque) { - const sc_var_setref_t vars = gs_get_vars(game); - const sc_prop_setref_t bundle = gs_get_bundle(game); - sc_vartype_t vt_key[3]; - sc_int index_, var_count; - assert(callback); - - /* Store the callback and opaque references, for writer functions. */ - ser_callback = callback; - ser_opaque = opaque; - - /* Write the game name. */ - vt_key[0].string = "Globals"; - vt_key[1].string = "GameName"; - ser_buffer_string(prop_get_string(bundle, "S<-ss", vt_key)); - - /* Write the counts of rooms, objects, etc. */ - ser_buffer_int(gs_room_count(game)); - ser_buffer_int(gs_object_count(game)); - ser_buffer_int(gs_task_count(game)); - ser_buffer_int(gs_event_count(game)); - ser_buffer_int(gs_npc_count(game)); - - /* Write the score and player information. */ - ser_buffer_int(game->score); - ser_buffer_int(gs_playerroom(game) + 1); - ser_buffer_int(gs_playerparent(game)); - ser_buffer_int(gs_playerposition(game)); - - /* Write player gender. */ - vt_key[0].string = "Globals"; - vt_key[1].string = "PlayerGender"; - ser_buffer_int(prop_get_integer(bundle, "I<-ss", vt_key)); - - /* - * Write encumbrance details. The player limits are constant for a given - * game, and can be extracted from properties. The current sizes and - * weights can also be recalculated from held objects, so we don't maintain - * them in the game. We can write constants here, then, and ignore - * the values on restoring. Note however that if the Adrift Runner is - * relying on these values, this may give it problems with one of our saved - * games. - */ - ser_buffer_int(90); - ser_buffer_int(0); - ser_buffer_int(90); - ser_buffer_int(0); - - /* Save rooms information. */ - for (index_ = 0; index_ < gs_room_count(game); index_++) - ser_buffer_boolean(gs_room_seen(game, index_)); - - /* Save objects information. */ - for (index_ = 0; index_ < gs_object_count(game); index_++) { - ser_buffer_int(gs_object_position(game, index_)); - ser_buffer_boolean(gs_object_seen(game, index_)); - ser_buffer_int(gs_object_parent(game, index_)); - if (gs_object_openness(game, index_) != 0) - ser_buffer_int(gs_object_openness(game, index_)); - - if (gs_object_state(game, index_) != 0) - ser_buffer_int(gs_object_state(game, index_)); - - ser_buffer_boolean(gs_object_unmoved(game, index_)); - } - - /* Save tasks information. */ - for (index_ = 0; index_ < gs_task_count(game); index_++) { - ser_buffer_boolean(gs_task_done(game, index_)); - ser_buffer_boolean(gs_task_scored(game, index_)); - } - - /* Save events information. */ - for (index_ = 0; index_ < gs_event_count(game); index_++) { - sc_int startertype, task; - - /* Get starter task, if any. */ - vt_key[0].string = "Events"; - vt_key[1].integer = index_; - vt_key[2].string = "StarterType"; - startertype = prop_get_integer(bundle, "I<-sis", vt_key); - if (startertype == 3) { - vt_key[2].string = "TaskNum"; - task = prop_get_integer(bundle, "I<-sis", vt_key); - } else - task = 0; - - /* Save event details. */ - ser_buffer_int(gs_event_time(game, index_)); - ser_buffer_int(task); - ser_buffer_int(gs_event_state(game, index_) - 1); - if (task > 0) - ser_buffer_boolean(gs_task_done(game, task - 1)); - else - ser_buffer_boolean(FALSE); - } - - /* Save NPCs information. */ - for (index_ = 0; index_ < gs_npc_count(game); index_++) { - sc_int walk; - - ser_buffer_int(gs_npc_location(game, index_)); - ser_buffer_boolean(gs_npc_seen(game, index_)); - for (walk = 0; walk < gs_npc_walkstep_count(game, index_); walk++) - ser_buffer_int_special(gs_npc_walkstep(game, index_, walk)); - } - - /* Save each variable. */ - vt_key[0].string = "Variables"; - var_count = prop_get_child_count(bundle, "I<-s", vt_key); - - for (index_ = 0; index_ < var_count; index_++) { - const sc_char *name; - sc_int var_type; - - vt_key[1].integer = index_; - - vt_key[2].string = "Name"; - name = prop_get_string(bundle, "S<-sis", vt_key); - vt_key[2].string = "Type"; - var_type = prop_get_integer(bundle, "I<-sis", vt_key); - - switch (var_type) { - case TAFVAR_NUMERIC: - ser_buffer_int(var_get_integer(vars, name)); - break; - - case TAFVAR_STRING: - ser_buffer_string(var_get_string(vars, name)); - break; - - default: - sc_fatal("ser_save_game: unknown variable type, %ld\n", var_type); - } - } - - /* Save timing information. */ - ser_buffer_uint(var_get_elapsed_seconds(vars)); - - /* Save turns count. */ - ser_buffer_uint((sc_uint) game->turns); - - /* - * Flush the last buffer contents, and drop the callback and opaque - * references. - */ - ser_flush(TRUE); - ser_callback = NULL; - ser_opaque = NULL; -} - - -/* - * ser_save_game_prompted() - * - * Serialize a game and save its state, requesting a save stream from - * the user. - */ -sc_bool ser_save_game_prompted(sc_gameref_t game) { - return g_vm->saveGame().getCode() == Common::kNoError; -} - - -/* TAS input file line counter. */ -static sc_tafref_t ser_tas = NULL; -static sc_int ser_tasline = 0; - -/* Restore error jump buffer. */ -static jmp_buf ser_tas_error; - -/* - * ser_get_string() - * ser_get_int() - * ser_get_uint() - * ser_get_boolean() - * - * Wrapper round obtaining the next TAS file line, with variants to convert - * the line content into an appropriate type. - */ -static const sc_char *ser_get_string(void) { - const sc_char *string; - - /* Get the next line, and complain if absent. */ - string = taf_next_line(ser_tas); - if (!string) { - sc_error("ser_get_string: out of TAS data at line %ld\n", ser_tasline); - longjmp(ser_tas_error, 1); - } - - ser_tasline++; - return string; -} - -static sc_int ser_get_int(void) { - const sc_char *string; - sc_int value; - - /* Get line, and scan for a single integer; return it. */ - string = ser_get_string(); - if (sscanf(string, "%ld", &value) != 1) { - sc_error("ser_get_int:" - " invalid integer at line %ld\n", ser_tasline - 1); - longjmp(ser_tas_error, 1); - } - - return value; -} - -static sc_uint ser_get_uint(void) { - const sc_char *string; - sc_uint value; - - /* Get line, and scan for a single integer; return it. */ - string = ser_get_string(); - if (sscanf(string, "%lu", &value) != 1) { - sc_error("ser_get_uint:" - " invalid integer at line %ld\n", ser_tasline - 1); - longjmp(ser_tas_error, 1); - } - - return value; -} - -static sc_bool ser_get_boolean(void) { - const sc_char *string; - sc_uint value; - - /* - * Get line, and scan for a single integer; check it's a valid-looking flag, - * and return it. - */ - string = ser_get_string(); - if (sscanf(string, "%lu", &value) != 1) { - sc_error("ser_get_boolean:" - " invalid boolean at line %ld\n", ser_tasline - 1); - longjmp(ser_tas_error, 1); - } - if (value != 0 && value != 1) { - sc_error("ser_get_boolean:" - " warning: suspect boolean at line %ld\n", ser_tasline - 1); - } - - return value != 0; -} - - -/* - * ser_load_game() - * - * Load a serialized game into the given game by repeated calls to the - * callback() function. - */ -sc_bool ser_load_game(sc_gameref_t game, sc_read_callbackref_t callback, void *opaque) { - static sc_var_setref_t new_vars; /* For setjmp safety */ - static sc_gameref_t new_game; /* For setjmp safety */ - - const sc_filterref_t filter = gs_get_filter(game); - const sc_prop_setref_t bundle = gs_get_bundle(game); - sc_vartype_t vt_key[3]; - sc_int index_, var_count; - const sc_char *gamename; - - /* Create a TAF (TAS) reference from callbacks, for reader functions. */ - ser_tas = taf_create_tas(callback, opaque); - if (!ser_tas) - return FALSE; - - /* Reset line counter for error messages. */ - ser_tasline = 1; - - new_game = NULL; - new_vars = NULL; - - /* Set up error handling jump buffer, and handle errors. */ - if (setjmp(ser_tas_error) != 0) { - /* Destroy any temporary game and variables. */ - if (new_game) - gs_destroy(new_game); - if (new_vars) - var_destroy(new_vars); - - /* Destroy the TAF (TAS) file and return fail status. */ - taf_destroy(ser_tas); - ser_tas = NULL; - return FALSE; - } - - /* - * Read the game name, and compare with the one in the game. Fail if - * they don't match exactly. A tighter check than this would perhaps be - * preferable, say, something based on the TAF file header, but this isn't - * in the save file format. - */ - vt_key[0].string = "Globals"; - vt_key[1].string = "GameName"; - gamename = prop_get_string(bundle, "S<-ss", vt_key); - if (strcmp(ser_get_string(), gamename) != 0) - longjmp(ser_tas_error, 1); - - /* Read and verify the counts in the saved game. */ - if (ser_get_int() != gs_room_count(game) - || ser_get_int() != gs_object_count(game) - || ser_get_int() != gs_task_count(game) - || ser_get_int() != gs_event_count(game) - || ser_get_int() != gs_npc_count(game)) - longjmp(ser_tas_error, 1); - - /* Create a variables set and game to restore into. */ - new_vars = var_create(bundle); - new_game = gs_create(new_vars, bundle, filter); - var_register_game(new_vars, new_game); - - /* All set to load TAF (TAS) data into the new game. */ - - /* Restore the score and player information. */ - new_game->score = ser_get_int(); - gs_set_playerroom(new_game, ser_get_int() - 1); - gs_set_playerparent(new_game, ser_get_int()); - gs_set_playerposition(new_game, ser_get_int()); - - /* Skip player gender. */ - (void) ser_get_int(); - - /* Skip encumbrance details, not currently maintained by the game. */ - (void) ser_get_int(); - (void) ser_get_int(); - (void) ser_get_int(); - (void) ser_get_int(); - - /* Restore rooms information. */ - for (index_ = 0; index_ < gs_room_count(new_game); index_++) - gs_set_room_seen(new_game, index_, ser_get_boolean()); - - /* Restore objects information. */ - for (index_ = 0; index_ < gs_object_count(new_game); index_++) { - sc_int openable, currentstate; - - /* Bypass mutators for position and parent. Fix later? */ - new_game->objects[index_].position = ser_get_int(); - gs_set_object_seen(new_game, index_, ser_get_boolean()); - new_game->objects[index_].parent = ser_get_int(); - - vt_key[0].string = "Objects"; - vt_key[1].integer = index_; - vt_key[2].string = "Openable"; - openable = prop_get_integer(bundle, "I<-sis", vt_key); - gs_set_object_openness(new_game, index_, - openable != 0 ? ser_get_int() : 0); - - vt_key[2].string = "CurrentState"; - currentstate = prop_get_integer(bundle, "I<-sis", vt_key); - gs_set_object_state(new_game, index_, - currentstate != 0 ? ser_get_int() : 0); - - gs_set_object_unmoved(new_game, index_, ser_get_boolean()); - } - - /* Restore tasks information. */ - for (index_ = 0; index_ < gs_task_count(new_game); index_++) { - gs_set_task_done(new_game, index_, ser_get_boolean()); - gs_set_task_scored(new_game, index_, ser_get_boolean()); - } - - /* Restore events information. */ - for (index_ = 0; index_ < gs_event_count(new_game); index_++) { - sc_int startertype, task; - - /* Restore first event details. */ - gs_set_event_time(new_game, index_, ser_get_int()); - task = ser_get_int(); - gs_set_event_state(new_game, index_, ser_get_int() + 1); - - /* Verify and restore the starter task, if any. */ - if (task > 0) { - vt_key[0].string = "Events"; - vt_key[1].integer = index_; - vt_key[2].string = "StarterType"; - startertype = prop_get_integer(bundle, "I<-sis", vt_key); - if (startertype != 3) - longjmp(ser_tas_error, 1); - - /* Restore task state. */ - gs_set_task_done(new_game, task - 1, ser_get_boolean()); - } else - (void) ser_get_boolean(); - } - - /* Restore NPCs information. */ - for (index_ = 0; index_ < gs_npc_count(new_game); index_++) { - sc_int walk; - - gs_set_npc_location(new_game, index_, ser_get_int()); - gs_set_npc_seen(new_game, index_, ser_get_boolean()); - for (walk = 0; walk < gs_npc_walkstep_count(new_game, index_); walk++) - gs_set_npc_walkstep(new_game, index_, walk, ser_get_int()); - } - - /* Restore each variable. */ - vt_key[0].string = "Variables"; - var_count = prop_get_child_count(bundle, "I<-s", vt_key); - - for (index_ = 0; index_ < var_count; index_++) { - const sc_char *name; - sc_int var_type; - - vt_key[1].integer = index_; - - vt_key[2].string = "Name"; - name = prop_get_string(bundle, "S<-sis", vt_key); - vt_key[2].string = "Type"; - var_type = prop_get_integer(bundle, "I<-sis", vt_key); - - switch (var_type) { - case TAFVAR_NUMERIC: - var_put_integer(new_vars, name, ser_get_int()); - break; - - case TAFVAR_STRING: - var_put_string(new_vars, name, ser_get_string()); - break; - - default: - sc_fatal("ser_load_game: unknown variable type, %ld\n", var_type); - } - } - - /* Restore timing information. */ - var_set_elapsed_seconds(new_vars, ser_get_uint()); - - /* Restore turns count. */ - new_game->turns = (sc_int) ser_get_uint(); - - /* - * Resources tweak -- set requested to match those in the current game - * so that they remain unchanged by the gs_copy() of new_game onto - * game. This way, both the requested and the active resources in the - * game are unchanged by restore. - */ - new_game->requested_sound = game->requested_sound; - new_game->requested_graphic = game->requested_graphic; - - /* - * Quitter tweak -- set the quit jump buffer in the new game to be the - * same as the current one, so that it remains unchanged by gs_copy(). The - * one in the new game is still the unset one from gs_create(). - */ - memcpy(&new_game->quitter, &game->quitter, sizeof(game->quitter)); - - /* - * If we got this far, we successfully restored the game from the file. - * As our final act, copy the new game onto the old one. - */ - new_game->temporary = game->temporary; - new_game->undo = game->undo; - gs_copy(game, new_game); - - /* Done with the temporary game and variables. */ - gs_destroy(new_game); - var_destroy(new_vars); - - /* Done with TAF (TAS) file; destroy it and return successfully. */ - taf_destroy(ser_tas); - ser_tas = NULL; - return TRUE; -} - - -/* - * ser_load_game_prompted() - * - * Load a serialized game into the given game, requesting a restore - * stream from the user. - */ -sc_bool ser_load_game_prompted(sc_gameref_t game) { - return g_vm->loadGame().getCode() == Common::kNoError; -} - -} // End of namespace Adrift -} // End of namespace Glk diff --git a/engines/glk/adrift/serialization.cpp b/engines/glk/adrift/serialization.cpp new file mode 100644 index 0000000000..404619a223 --- /dev/null +++ b/engines/glk/adrift/serialization.cpp @@ -0,0 +1,517 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/adrift/serialization.h" +#include "glk/adrift/scprotos.h" +#include "glk/adrift/scgamest.h" + +namespace Glk { +namespace Adrift { + +/* Assorted definitions and constants. */ +static const sc_char NEWLINE = '\n'; +static const sc_char CARRIAGE_RETURN = '\r'; +static const sc_char NUL = '\0'; + +enum { BUFFER_SIZE = 4096 }; + +void SaveSerializer::save() { + const sc_var_setref_t vars = gs_get_vars(_game); + const sc_prop_setref_t bundle = gs_get_bundle(_game); + sc_vartype_t vt_key[3]; + sc_int index_, var_count; + + // Write the _game name + vt_key[0].string = "Globals"; + vt_key[1].string = "GameName"; + writeString(prop_get_string(bundle, "S<-ss", vt_key)); + + /* Write the counts of rooms, objects, etc. */ + writeInt(gs_room_count(_game)); + writeInt(gs_object_count(_game)); + writeInt(gs_task_count(_game)); + writeInt(gs_event_count(_game)); + writeInt(gs_npc_count(_game)); + + /* Write the score and player information. */ + writeInt(_game->score); + writeInt(gs_playerroom(_game) + 1); + writeInt(gs_playerparent(_game)); + writeInt(gs_playerposition(_game)); + + /* Write player gender. */ + vt_key[0].string = "Globals"; + vt_key[1].string = "PlayerGender"; + writeInt(prop_get_integer(bundle, "I<-ss", vt_key)); + + /* + * Write encumbrance details. The player limits are constant for a given + * _game, and can be extracted from properties. The current sizes and + * weights can also be recalculated from held objects, so we don't maintain + * them in the _game. We can write constants here, then, and ignore + * the values on restoring. Note however that if the Adrift Runner is + * relying on these values, this may give it problems with one of our saved + * games. + */ + writeInt(90); + writeInt(0); + writeInt(90); + writeInt(0); + + /* Save rooms information. */ + for (index_ = 0; index_ < gs_room_count(_game); index_++) + writeBool(gs_room_seen(_game, index_)); + + /* Save objects information. */ + for (index_ = 0; index_ < gs_object_count(_game); index_++) { + writeInt(gs_object_position(_game, index_)); + writeBool(gs_object_seen(_game, index_)); + writeInt(gs_object_parent(_game, index_)); + if (gs_object_openness(_game, index_) != 0) + writeInt(gs_object_openness(_game, index_)); + + if (gs_object_state(_game, index_) != 0) + writeInt(gs_object_state(_game, index_)); + + writeBool(gs_object_unmoved(_game, index_)); + } + + /* Save tasks information. */ + for (index_ = 0; index_ < gs_task_count(_game); index_++) { + writeBool(gs_task_done(_game, index_)); + writeBool(gs_task_scored(_game, index_)); + } + + /* Save events information. */ + for (index_ = 0; index_ < gs_event_count(_game); index_++) { + sc_int startertype, task; + + /* Get starter task, if any. */ + vt_key[0].string = "Events"; + vt_key[1].integer = index_; + vt_key[2].string = "StarterType"; + startertype = prop_get_integer(bundle, "I<-sis", vt_key); + if (startertype == 3) { + vt_key[2].string = "TaskNum"; + task = prop_get_integer(bundle, "I<-sis", vt_key); + } + else + task = 0; + + /* Save event details. */ + writeInt(gs_event_time(_game, index_)); + writeInt(task); + writeInt(gs_event_state(_game, index_) - 1); + if (task > 0) + writeBool(gs_task_done(_game, task - 1)); + else + writeBool(FALSE); + } + + /* Save NPCs information. */ + for (index_ = 0; index_ < gs_npc_count(_game); index_++) { + sc_int walk; + + writeInt(gs_npc_location(_game, index_)); + writeBool(gs_npc_seen(_game, index_)); + for (walk = 0; walk < gs_npc_walkstep_count(_game, index_); walk++) + writeIntSpecial(gs_npc_walkstep(_game, index_, walk)); + } + + /* Save each variable. */ + vt_key[0].string = "Variables"; + var_count = prop_get_child_count(bundle, "I<-s", vt_key); + + for (index_ = 0; index_ < var_count; index_++) { + const sc_char *name; + sc_int var_type; + + vt_key[1].integer = index_; + + vt_key[2].string = "Name"; + name = prop_get_string(bundle, "S<-sis", vt_key); + vt_key[2].string = "Type"; + var_type = prop_get_integer(bundle, "I<-sis", vt_key); + + switch (var_type) { + case TAFVAR_NUMERIC: + writeInt(var_get_integer(vars, name)); + break; + + case TAFVAR_STRING: + writeString(var_get_string(vars, name)); + break; + + default: + sc_fatal("ser_save_game: unknown variable type, %ld\n", var_type); + } + } + + /* Save timing information. */ + writeUint(var_get_elapsed_seconds(vars)); + + /* Save turns count. */ + writeUint((sc_uint)_game->turns); + + /* + * Flush the last buffer contents, and drop the callback and opaque + * references. + */ + flush(TRUE); + _callback = NULL; + _opaque = NULL; +} + +void SaveSerializer::flush(sc_bool is_final) { + if (is_final) { + _callback(_opaque, _buffer.getData(), _buffer.size()); + } +} + +void SaveSerializer::writeChar(sc_char character) { + // Validate the buffer hasn't exceeded the maximum allowable size + if (_buffer.size() == BUFFER_SIZE) + sc_error("Ran out of serialization buffer"); + + // Add to the buffer + _buffer.writeByte(character); +} + +void SaveSerializer::write(const sc_char *buffer, sc_int length) { + // Add each character to the buffer + for (int idx = 0; idx < length; ++idx) + writeChar(buffer[idx]); +} + +void SaveSerializer::writeString(const sc_char *string) { + // Write string, followed by DOS style end-of-line + write(string, strlen(string)); + writeChar(CARRIAGE_RETURN); + writeChar(NEWLINE); +} + +void SaveSerializer::writeInt(sc_int value) { + Common::String s = Common::String::format("%ld", value); + writeString(s.c_str()); +} + +void SaveSerializer::writeIntSpecial(sc_int value) { + Common::String s = Common::String::format("% ld ", value); + writeString(s.c_str()); +} + +void SaveSerializer::writeUint(sc_uint value) { + Common::String s = Common::String::format("%lu", value); + writeString(s.c_str()); +} + +void SaveSerializer::writeBool(sc_bool boolean) { + // Write a 1 for TRUE, 0 for FALSE + writeString(boolean ? "1" : "0"); +} + +/*--------------------------------------------------------------------------*/ + +/* TAS input file line counter. */ +static sc_tafref_t ser_tas = NULL; +static sc_int ser_tasline = 0; + +/* Restore error jump buffer. */ +static jmp_buf ser_tas_error; +static sc_var_setref_t new_vars; /* For setjmp safety */ +static sc_gameref_t new_game; /* For setjmp safety */ + +bool LoadSerializer::load() { + const sc_filterref_t filter = gs_get_filter(_game); + const sc_prop_setref_t bundle = gs_get_bundle(_game); + sc_vartype_t vt_key[3]; + sc_int index_, var_count; + const sc_char *gamename; + + /* Create a TAF (TAS) reference from callbacks, for reader functions. */ + ser_tas = taf_create_tas(_callback, _opaque); + if (!ser_tas) + return FALSE; + + /* Reset line counter for error messages. */ + ser_tasline = 1; + + new_game = NULL; + new_vars = NULL; + + /* Set up error handling jump buffer, and handle errors. */ + if (setjmp(ser_tas_error) != 0) { + /* Destroy any temporary _game and variables. */ + if (new_game) + gs_destroy(new_game); + if (new_vars) + var_destroy(new_vars); + + /* Destroy the TAF (TAS) file and return fail status. */ + taf_destroy(ser_tas); + ser_tas = NULL; + return FALSE; + } + + /* + * Read the _game name, and compare with the one in the _game. Fail if + * they don't match exactly. A tighter check than this would perhaps be + * preferable, say, something based on the TAF file header, but this isn't + * in the save file format. + */ + vt_key[0].string = "Globals"; + vt_key[1].string = "GameName"; + gamename = prop_get_string(bundle, "S<-ss", vt_key); + if (strcmp(readString(), gamename) != 0) + longjmp(ser_tas_error, 1); + + /* Read and verify the counts in the saved _game. */ + if (readInt() != gs_room_count(_game) + || readInt() != gs_object_count(_game) + || readInt() != gs_task_count(_game) + || readInt() != gs_event_count(_game) + || readInt() != gs_npc_count(_game)) + longjmp(ser_tas_error, 1); + + /* Create a variables set and _game to restore into. */ + new_vars = var_create(bundle); + new_game = gs_create(new_vars, bundle, filter); + var_register_game(new_vars, new_game); + + /* All set to load TAF (TAS) data into the new _game. */ + + /* Restore the score and player information. */ + new_game->score = readInt(); + gs_set_playerroom(new_game, readInt() - 1); + gs_set_playerparent(new_game, readInt()); + gs_set_playerposition(new_game, readInt()); + + /* Skip player gender. */ + (void)readInt(); + + /* Skip encumbrance details, not currently maintained by the _game. */ + (void)readInt(); + (void)readInt(); + (void)readInt(); + (void)readInt(); + + /* Restore rooms information. */ + for (index_ = 0; index_ < gs_room_count(new_game); index_++) + gs_set_room_seen(new_game, index_, readBool()); + + /* Restore objects information. */ + for (index_ = 0; index_ < gs_object_count(new_game); index_++) { + sc_int openable, currentstate; + + /* Bypass mutators for position and parent. Fix later? */ + new_game->objects[index_].position = readInt(); + gs_set_object_seen(new_game, index_, readBool()); + new_game->objects[index_].parent = readInt(); + + vt_key[0].string = "Objects"; + vt_key[1].integer = index_; + vt_key[2].string = "Openable"; + openable = prop_get_integer(bundle, "I<-sis", vt_key); + gs_set_object_openness(new_game, index_, + openable != 0 ? readInt() : 0); + + vt_key[2].string = "CurrentState"; + currentstate = prop_get_integer(bundle, "I<-sis", vt_key); + gs_set_object_state(new_game, index_, + currentstate != 0 ? readInt() : 0); + + gs_set_object_unmoved(new_game, index_, readBool()); + } + + /* Restore tasks information. */ + for (index_ = 0; index_ < gs_task_count(new_game); index_++) { + gs_set_task_done(new_game, index_, readBool()); + gs_set_task_scored(new_game, index_, readBool()); + } + + /* Restore events information. */ + for (index_ = 0; index_ < gs_event_count(new_game); index_++) { + sc_int startertype, task; + + /* Restore first event details. */ + gs_set_event_time(new_game, index_, readInt()); + task = readInt(); + gs_set_event_state(new_game, index_, readInt() + 1); + + /* Verify and restore the starter task, if any. */ + if (task > 0) { + vt_key[0].string = "Events"; + vt_key[1].integer = index_; + vt_key[2].string = "StarterType"; + startertype = prop_get_integer(bundle, "I<-sis", vt_key); + if (startertype != 3) + longjmp(ser_tas_error, 1); + + /* Restore task state. */ + gs_set_task_done(new_game, task - 1, readBool()); + } + else + (void)readBool(); + } + + /* Restore NPCs information. */ + for (index_ = 0; index_ < gs_npc_count(new_game); index_++) { + sc_int walk; + + gs_set_npc_location(new_game, index_, readInt()); + gs_set_npc_seen(new_game, index_, readBool()); + for (walk = 0; walk < gs_npc_walkstep_count(new_game, index_); walk++) + gs_set_npc_walkstep(new_game, index_, walk, readInt()); + } + + /* Restore each variable. */ + vt_key[0].string = "Variables"; + var_count = prop_get_child_count(bundle, "I<-s", vt_key); + + for (index_ = 0; index_ < var_count; index_++) { + const sc_char *name; + sc_int var_type; + + vt_key[1].integer = index_; + + vt_key[2].string = "Name"; + name = prop_get_string(bundle, "S<-sis", vt_key); + vt_key[2].string = "Type"; + var_type = prop_get_integer(bundle, "I<-sis", vt_key); + + switch (var_type) { + case TAFVAR_NUMERIC: + var_put_integer(new_vars, name, readInt()); + break; + + case TAFVAR_STRING: + var_put_string(new_vars, name, readString()); + break; + + default: + sc_fatal("ser_load_game: unknown variable type, %ld\n", var_type); + } + } + + /* Restore timing information. */ + var_set_elapsed_seconds(new_vars, readUint()); + + /* Restore turns count. */ + new_game->turns = (sc_int)readUint(); + + /* + * Resources tweak -- set requested to match those in the current _game + * so that they remain unchanged by the gs_copy() of new_game onto + * _game. This way, both the requested and the active resources in the + * _game are unchanged by restore. + */ + new_game->requested_sound = _game->requested_sound; + new_game->requested_graphic = _game->requested_graphic; + + /* + * Quitter tweak -- set the quit jump buffer in the new _game to be the + * same as the current one, so that it remains unchanged by gs_copy(). The + * one in the new _game is still the unset one from gs_create(). + */ + memcpy(&new_game->quitter, &_game->quitter, sizeof(_game->quitter)); + + /* + * If we got this far, we successfully restored the _game from the file. + * As our final act, copy the new _game onto the old one. + */ + new_game->temporary = _game->temporary; + new_game->undo = _game->undo; + gs_copy(_game, new_game); + + /* Done with the temporary _game and variables. */ + gs_destroy(new_game); + var_destroy(new_vars); + + /* Done with TAF (TAS) file; destroy it and return successfully. */ + taf_destroy(ser_tas); + ser_tas = NULL; + return TRUE; +} + +const sc_char *LoadSerializer::readString() { + const sc_char *string; + + /* Get the next line, and complain if absent. */ + string = taf_next_line(ser_tas); + if (!string) { + sc_error("readString: out of TAS data at line %ld\n", ser_tasline); + longjmp(ser_tas_error, 1); + } + + ser_tasline++; + return string; +} + +sc_int LoadSerializer::readInt() { + const sc_char *string; + sc_int value; + + // Get line, and scan for a single integer; return it + string = readString(); + if (sscanf(string, "%ld", &value) != 1) { + sc_error("readInt: invalid integer at line %ld\n", ser_tasline - 1); + longjmp(ser_tas_error, 1); + } + + return value; +} + +sc_uint LoadSerializer::readUint() { + const sc_char *string; + sc_uint value; + + // Get line, and scan for a single integer; return it + string = readString(); + if (sscanf(string, "%lu", &value) != 1) { + sc_error("readUint: invalid integer at line %ld\n", ser_tasline - 1); + longjmp(ser_tas_error, 1); + } + + return value; +} + +sc_bool LoadSerializer::readBool(void) { + const sc_char *string; + sc_uint value; + + // Get line, and scan for a single integer; check it's a valid-looking flag, and return it. + string = readString(); + if (sscanf(string, "%lu", &value) != 1) { + sc_error("readBool:" + " invalid boolean at line %ld\n", ser_tasline - 1); + longjmp(ser_tas_error, 1); + } + if (value != 0 && value != 1) { + sc_error("readBool:" + " warning: suspect boolean at line %ld\n", ser_tasline - 1); + } + + return value != 0; +} + +} // End of namespace Adrift +} // End of namespace Glk diff --git a/engines/glk/adrift/serialization.h b/engines/glk/adrift/serialization.h new file mode 100644 index 0000000000..47559a2d28 --- /dev/null +++ b/engines/glk/adrift/serialization.h @@ -0,0 +1,143 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ADRIFT_SERIALIZATION_H +#define ADRIFT_SERIALIZATION_H + +#include "common/memstream.h" +#include "common/str.h" +#include "glk/adrift/scprotos.h" + +namespace Glk { +namespace Adrift { + +/** + * Saving serializer class + */ +class SaveSerializer { +private: + sc_gameref_t _game; + sc_write_callbackref_t _callback; + void *_opaque; + Common::MemoryWriteStreamDynamic _buffer; +private: + /** + * Flush pending buffer contents + */ + void flush(sc_bool is_final); + + /** + * add a character to the buffer. + */ + void writeChar(sc_char character); + + /** + * Write a buffer + */ + void write(const sc_char *buffer, sc_int length); + + /** + * Write a string + */ + void writeString(const sc_char *string); + + /** + * Write an integer + */ + void writeInt(sc_int value); + + /** + * Write a special/long integer + */ + void writeIntSpecial(sc_int value); + + /** + * Write an unsigned integer + */ + void writeUint(sc_uint value); + + /** + * Write a boolean + */ + void writeBool(sc_bool boolean); +public: + /** + * Constructor + */ + SaveSerializer(sc_gameref_t game, sc_write_callbackref_t callback, void *opaque) : + _game(game), _callback(callback), _opaque(opaque), _buffer(DisposeAfterUse::YES) { + assert(callback); + } + + /** + * Save method + */ + void save(); +}; + +/** + * Loading serializer class + */ +class LoadSerializer { +private: + sc_gameref_t _game; + sc_read_callbackref_t _callback; + void *_opaque; +private: + /** + * Reads a string + */ + const sc_char *readString(); + + /** + * Read a signed integer + */ + sc_int readInt(); + + /** + * Read an unsigned integer + */ + sc_uint readUint(); + + /** + * Read a boolean + */ + sc_bool readBool(); +public: + /** + * Constructor + */ + LoadSerializer(sc_gameref_t game, sc_read_callbackref_t callback, void *opaque) : + _game(game), _callback(callback), _opaque(opaque) { + assert(callback); + } + + /** + * Does the loading + */ + bool load(); +}; + +} // End of namespace Adrift +} // End of namespace Glk + +#endif diff --git a/engines/glk/module.mk b/engines/glk/module.mk index bc65355179..8727b9369f 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -46,12 +46,12 @@ MODULE_OBJS := \ adrift/scresour.o \ adrift/screstrs.o \ adrift/scrunner.o \ - adrift/scserial.o \ adrift/sctaffil.o \ adrift/sctafpar.o \ adrift/sctasks.o \ adrift/scutils.o \ adrift/scvars.o \ + adrift/serialization.o \ adrift/sxfile.o \ adrift/sxglob.o \ adrift/sxmain.o \ -- cgit v1.2.3