495 lines
14 KiB
C++
495 lines
14 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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';
|
|
// Unused 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 = nullptr;
|
|
_opaque = nullptr;
|
|
}
|
|
|
|
void SaveSerializer::flush(sc_bool is_final) {
|
|
if (is_final) {
|
|
_callback(_opaque, _buffer.getData(), _buffer.size());
|
|
}
|
|
}
|
|
|
|
void SaveSerializer::writeChar(sc_char character) {
|
|
// 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");
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
#define CHECK if (context._break) goto ser_tas_error
|
|
|
|
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;
|
|
sc_var_setref_t new_vars = nullptr;
|
|
sc_gameref_t new_game = nullptr;
|
|
Context context;
|
|
sc_int count = 0;
|
|
|
|
// 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;
|
|
|
|
// 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(context), gamename) != 0 || context._break)
|
|
goto ser_tas_error;
|
|
|
|
// Read and verify the counts in the saved _game.
|
|
if ((readInt(context) != gs_room_count(_game) || context._break)
|
|
|| (readInt(context) != gs_object_count(_game) || context._break)
|
|
|| (readInt(context) != gs_task_count(_game) || context._break)
|
|
|| (readInt(context) != gs_event_count(_game) || context._break)
|
|
|| (readInt(context) != gs_npc_count(_game) || context._break))
|
|
goto ser_tas_error;
|
|
|
|
// 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(context); CHECK;
|
|
gs_set_playerroom(new_game, readInt(context) - 1); CHECK;
|
|
gs_set_playerparent(new_game, readInt(context)); CHECK;
|
|
gs_set_playerposition(new_game, readInt(context)); CHECK;
|
|
|
|
// Skip player gender.
|
|
(void)readInt(context); CHECK;
|
|
|
|
// Skip encumbrance details, not currently maintained by the _game.
|
|
(void)readInt(context); CHECK;
|
|
(void)readInt(context); CHECK;
|
|
(void)readInt(context); CHECK;
|
|
(void)readInt(context); CHECK;
|
|
|
|
// Restore rooms information
|
|
count = gs_room_count(new_game);
|
|
for (index_ = 0; index_ < count; ++index_) {
|
|
gs_set_room_seen(new_game, index_, readBool(context)); CHECK;
|
|
}
|
|
|
|
// Restore objects information
|
|
count = gs_object_count(new_game);
|
|
for (index_ = 0; index_ < count; ++index_) {
|
|
sc_int openable, currentstate;
|
|
|
|
// Bypass mutators for position and parent. Fix later?
|
|
new_game->objects[index_].position = readInt(context); CHECK;
|
|
gs_set_object_seen(new_game, index_, readBool(context)); CHECK;
|
|
new_game->objects[index_].parent = readInt(context); CHECK;
|
|
|
|
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(context) : 0); CHECK;
|
|
|
|
vt_key[2].string = "CurrentState";
|
|
currentstate = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
gs_set_object_state(new_game, index_,
|
|
currentstate != 0 ? readInt(context) : 0); CHECK;
|
|
|
|
gs_set_object_unmoved(new_game, index_, readBool(context)); CHECK;
|
|
}
|
|
|
|
// Restore tasks information.
|
|
for (index_ = 0; index_ < gs_task_count(new_game); index_++) {
|
|
gs_set_task_done(new_game, index_, readBool(context)); CHECK;
|
|
gs_set_task_scored(new_game, index_, readBool(context)); CHECK;
|
|
}
|
|
|
|
// Restore events information
|
|
count = gs_event_count(new_game);
|
|
for (index_ = 0; index_ < count; index_++) {
|
|
sc_int startertype, task;
|
|
|
|
// Restore first event details.
|
|
gs_set_event_time(new_game, index_, readInt(context)); CHECK;
|
|
task = readInt(context); CHECK;
|
|
gs_set_event_state(new_game, index_, readInt(context) + 1); CHECK;
|
|
|
|
// 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)
|
|
goto ser_tas_error;
|
|
|
|
// Restore task state.
|
|
gs_set_task_done(new_game, task - 1, readBool(context)); CHECK;
|
|
} else {
|
|
(void)readBool(context); CHECK;
|
|
}
|
|
}
|
|
|
|
// Restore NPCs information
|
|
count = gs_npc_count(new_game);
|
|
for (index_ = 0; index_ < count; index_++) {
|
|
sc_int walk;
|
|
|
|
gs_set_npc_location(new_game, index_, readInt(context)); CHECK;
|
|
gs_set_npc_seen(new_game, index_, readBool(context)); CHECK;
|
|
for (walk = 0; walk < gs_npc_walkstep_count(new_game, index_); walk++) {
|
|
gs_set_npc_walkstep(new_game, index_, walk, readInt(context)); CHECK;
|
|
}
|
|
}
|
|
|
|
// 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(context)); CHECK;
|
|
break;
|
|
|
|
case TAFVAR_STRING:
|
|
var_put_string(new_vars, name, readString(context)); CHECK;
|
|
break;
|
|
|
|
default:
|
|
sc_fatal("ser_load_game: unknown variable type, %ld\n", var_type);
|
|
}
|
|
}
|
|
|
|
// Restore timing information.
|
|
var_set_elapsed_seconds(new_vars, readUint(context)); CHECK;
|
|
|
|
// Restore turns count.
|
|
new_game->turns = (sc_int)readUint(context); CHECK;
|
|
|
|
/* 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;
|
|
|
|
/* 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);
|
|
return true;
|
|
|
|
ser_tas_error:
|
|
// 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);
|
|
return false;
|
|
}
|
|
|
|
const sc_char *LoadSerializer::readString(CONTEXT) {
|
|
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);
|
|
LONG_JUMP0
|
|
}
|
|
|
|
ser_tasline++;
|
|
return string;
|
|
}
|
|
|
|
sc_int LoadSerializer::readInt(CONTEXT) {
|
|
const sc_char *string;
|
|
sc_int value;
|
|
|
|
// Get line, and scan for a single integer; return it
|
|
R0FUNC0(readString, string)
|
|
if (sscanf(string, "%ld", &value) != 1) {
|
|
sc_error("readInt: invalid integer at line %ld\n", ser_tasline - 1);
|
|
LONG_JUMP0
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
sc_uint LoadSerializer::readUint(CONTEXT) {
|
|
const sc_char *string;
|
|
sc_uint value;
|
|
|
|
// Get line, and scan for a single integer; return it
|
|
R0FUNC0(readString, string)
|
|
if (sscanf(string, "%lu", &value) != 1) {
|
|
sc_error("readUint: invalid integer at line %ld\n", ser_tasline - 1);
|
|
LONG_JUMP0
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
sc_bool LoadSerializer::readBool(CONTEXT) {
|
|
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.
|
|
R0FUNC0(readString, string)
|
|
if (sscanf(string, "%lu", &value) != 1) {
|
|
sc_error("readBool: invalid boolean at line %ld\n", ser_tasline - 1);
|
|
LONG_JUMP0
|
|
}
|
|
if (value != 0 && value != 1) {
|
|
sc_error("readBool: warning: suspect boolean at line %ld\n", ser_tasline - 1);
|
|
LONG_JUMP0
|
|
}
|
|
|
|
return value != 0;
|
|
}
|
|
|
|
} // End of namespace Adrift
|
|
} // End of namespace Glk
|