diff --git a/include/savedata.h b/include/savedata.h
index 616c124..11f2a72 100644
--- a/include/savedata.h
+++ b/include/savedata.h
@@ -26,8 +26,7 @@ void json_load_page(struct page *page, cJSON *vars, bool call_dtors);
union vm_value json_to_vm_value(enum ain_data_type type, enum ain_data_type struct_type, int array_rank, cJSON *json);
int save_json(const char *filename, cJSON *json);
-int save_globals(const char *keyname, const char *filename);
-int save_group(const char *keyname, const char *filename, const char *group_name, int *n);
+int save_globals(const char *keyname, const char *filename, const char *group_name, int *n);
int load_globals(const char *keyname, const char *filename, const char *group_name, int *n);
int delete_save_file(const char *filename);
diff --git a/src/savedata.c b/src/savedata.c
index 45c6ffa..41cd939 100644
--- a/src/savedata.c
+++ b/src/savedata.c
@@ -14,6 +14,7 @@
* along with this program; if not, see .
*/
+#include
#include
#include
#include
@@ -23,6 +24,7 @@
#include "system4.h"
#include "system4/ain.h"
#include "system4/file.h"
+#include "system4/savefile.h"
#include "system4/string.h"
#include "savedata.h"
@@ -77,27 +79,6 @@ cJSON *vm_value_to_json(enum ain_data_type type, union vm_value val)
}
}
-struct global_save_data {
- cJSON *save;
- cJSON *globals;
-};
-
-static void init_global_save_data(struct global_save_data *data, const char *keyname)
-{
- data->save = cJSON_CreateObject();
- data->globals = cJSON_CreateArray();
- cJSON_AddStringToObject(data->save, "key", keyname);
- cJSON_AddItemToObject(data->save, "globals", data->globals);
-}
-
-static void add_global(struct global_save_data *data, int global, union vm_value val)
-{
- cJSON *obj = cJSON_CreateObject();
- cJSON_AddNumberToObject(obj, "index", global);
- cJSON_AddItemToObject(obj, "value", vm_value_to_json(ain->globals[global].type.data, val));
- cJSON_AddItemToArray(data->globals, obj);
-}
-
int save_json(const char *filename, cJSON *json)
{
char *path = savedir_path(filename);
@@ -126,25 +107,6 @@ int save_json(const char *filename, cJSON *json)
}
-static int write_save_data(struct global_save_data *data, const char *filename)
-{
- int r = save_json(filename, data->save);
- cJSON_Delete(data->save);
- return r;
-}
-
-int save_globals(const char *keyname, const char *filename)
-{
- struct global_save_data data;
- init_global_save_data(&data, keyname);
-
- for (int i = 0; i < ain->nr_globals; i++) {
- add_global(&data, i, global_get(i));
- }
-
- return write_save_data(&data, filename);
-}
-
static int get_group_index(const char *name)
{
for (int i = 0; i < ain->nr_global_groups; i++) {
@@ -154,27 +116,145 @@ static int get_group_index(const char *name)
return -1;
}
-int save_group(const char *keyname, const char *filename, const char *group_name, int *n)
-{
- int group;
+static int32_t add_value_to_gsave(enum ain_data_type type, union vm_value val, struct gsave *save);
- if ((group = get_group_index(group_name)) < 0) {
- WARNING("Unregistered global group: %s", display_sjis0(group_name));
+static struct gsave_flat_array *collect_flat_arrays(struct page *page, struct gsave_flat_array *fa, struct gsave *save)
+{
+ if (page->array.rank > 1) {
+ for (int i = 0; i < page->nr_vars; i++)
+ fa = collect_flat_arrays(heap_get_page(page->values[i].i), fa, save);
+ return fa;
+ }
+ fa->nr_values = page->nr_vars;
+ fa->values = xcalloc(fa->nr_values, sizeof(struct gsave_array_value));
+ for (int i = 0; i < page->nr_vars; i++) {
+ enum ain_data_type type = variable_type(page, i, NULL, NULL);
+ fa->values[i].type = type;
+ fa->values[i].value = add_value_to_gsave(type, page->values[i], save);
+ }
+ return ++fa;
+}
+
+static int32_t add_value_to_gsave(enum ain_data_type type, union vm_value val, struct gsave *save)
+{
+ switch (type) {
+ case AIN_VOID:
+ case AIN_INT:
+ case AIN_BOOL:
+ case AIN_FUNC_TYPE:
+ case AIN_DELEGATE:
+ case AIN_LONG_INT:
+ case AIN_FLOAT:
+ return val.i;
+ case AIN_STRING:
+ return gsave_add_string(save, heap[val.i].s);
+ case AIN_STRUCT:
+ {
+ struct page *page = heap_get_page(val.i);
+ assert(page->type == STRUCT_PAGE);
+ struct ain_struct *st = &ain->structures[page->index];
+ assert(st->nr_members == page->nr_vars);
+ struct gsave_record rec = {
+ .type = GSAVE_RECORD_STRUCT,
+ .struct_name = strdup(st->name),
+ .nr_indices = st->nr_members,
+ .indices = xcalloc(st->nr_members, sizeof(int32_t)),
+ };
+ for (int i = 0; i < page->nr_vars; i++) {
+ enum ain_data_type type = st->members[i].type.data;
+ int32_t value = add_value_to_gsave(type, page->values[i], save);
+ struct gsave_keyval kv = {
+ .name = strdup(st->members[i].name),
+ .type = type,
+ .value = value,
+ };
+ rec.indices[i] = gsave_add_keyval(save, &kv);
+ }
+ return gsave_add_record(save, &rec);
+ }
+ case AIN_ARRAY_TYPE:
+ {
+ struct page *page = heap_get_page(val.i);
+ if (!page) {
+ struct gsave_array array = { .rank = -1 };
+ return gsave_add_array(save, &array);
+ }
+ assert(page->type == ARRAY_PAGE);
+ struct gsave_array array = {
+ .rank = page->array.rank,
+ .dimensions = xcalloc(page->array.rank, sizeof(int32_t)),
+ .nr_flat_arrays = 1
+ };
+ for (struct page *p = page;; p = heap_get_page(page->values[0].i)) {
+ array.dimensions[p->array.rank - 1] = p->nr_vars;
+ if (p->array.rank == 1)
+ break;
+ array.nr_flat_arrays *= p->nr_vars;
+ }
+ array.flat_arrays = xcalloc(array.nr_flat_arrays, sizeof(struct gsave_flat_array));
+ collect_flat_arrays(page, array.flat_arrays, save);
+ return gsave_add_array(save, &array);
+ }
+ case AIN_REF_TYPE:
+ return -1;
+ default:
+ WARNING("Unhandled type: %s", ain_strtype(ain, type, -1));
+ return -1;
+ }
+}
+
+int save_globals(const char *keyname, const char *filename, const char *group_name, int *n_out)
+{
+ int group = -1;
+ int nr_vars;
+ if (group_name) {
+ if ((group = get_group_index(group_name)) < 0) {
+ WARNING("Unregistered global group: %s", display_sjis0(group_name));
+ return 0;
+ }
+ nr_vars = 0;
+ for (int i = 0; i < ain->nr_globals; i++) {
+ if (ain->globals[i].group_index == group)
+ nr_vars++;
+ }
+ } else {
+ nr_vars = ain->nr_globals;
+ }
+
+ int gsave_version = AIN_VERSION_GTE(ain, 5, 0) ? 5 : 4;
+ struct gsave *save = gsave_create(gsave_version, keyname, ain->nr_globals, group_name);
+ gsave_add_globals_record(save, nr_vars);
+
+ struct gsave_global *global = save->globals;
+ for (int i = 0; i < ain->nr_globals; i++) {
+ if (group >= 0 && ain->globals[i].group_index != group)
+ continue;
+ global->name = strdup(ain->globals[i].name);
+ global->type = ain->globals[i].type.data;
+ global->value = add_value_to_gsave(global->type, global_get(i), save);
+ global++;
+ }
+
+ char *path = savedir_path(filename);
+ FILE *fp = file_open_utf8(path, "wb");
+ if (!fp) {
+ WARNING("Failed to open save file %s: %s", display_utf0(path), strerror(errno));
+ free(path);
+ gsave_free(save);
return 0;
}
+ free(path);
- struct global_save_data data;
- init_global_save_data(&data, keyname);
-
- *n = 0;
- for (int i = 0; i < ain->nr_globals; i++) {
- if (ain->globals[i].group_index != group)
- continue;
- add_global(&data, i, global_get(i));
- (*n)++;
- }
-
- return write_save_data(&data, filename);
+ bool encrypt = !AIN_VERSION_GTE(ain, 6, 0);
+ int compression_level = AIN_VERSION_GTE(ain, 6, 0) ? 1 : 9;
+ enum savefile_error error = gsave_write(save, fp, encrypt, compression_level);
+ if (error != SAVEFILE_SUCCESS)
+ WARNING("Failed to write save file: %s", savefile_strerror(error));
+ fclose(fp);
+ gsave_free(save);
+ if (n_out)
+ *n_out = nr_vars;
+ return error == SAVEFILE_SUCCESS;
}
union vm_value json_to_vm_value(enum ain_data_type type, enum ain_data_type struct_type, int array_rank, cJSON *json);
@@ -273,19 +353,16 @@ union vm_value json_to_vm_value(enum ain_data_type type, enum ain_data_type stru
}
}
-static cJSON *read_save_file(const char *filename)
+static cJSON *read_save_file(const char *path)
{
FILE *f;
long len;
char *buf;
- char *path = savedir_path(filename);
if (!(f = file_open_utf8(path, "rb"))) {
- WARNING("Failed to open save file: %s: %s", display_utf0(filename), strerror(errno));
- free(path);
+ WARNING("Failed to open save file: %s: %s", display_utf0(path), strerror(errno));
return NULL;
}
- free(path);
fseek(f, 0, SEEK_END);
len = ftell(f);
@@ -294,7 +371,7 @@ static cJSON *read_save_file(const char *filename)
buf = xmalloc(len+1);
buf[len] = '\0';
if (fread(buf, len, 1, f) != 1) {
- WARNING("Failed to read save file: %s", display_utf0(filename));
+ WARNING("Failed to read save file: %s", display_utf0(path));
free(buf);
return 0;
}
@@ -305,10 +382,10 @@ static cJSON *read_save_file(const char *filename)
return r;
}
-int load_globals(const char *keyname, const char *filename, const char *group_name, int *n)
+static int load_globals_from_json(const char *path, const char *keyname, const char *group_name, int *n)
{
int retval = 0;
- cJSON *save = read_save_file(filename);
+ cJSON *save = read_save_file(path);
if (!save)
return 0;
if (!cJSON_IsObject(save)) {
@@ -365,6 +442,160 @@ cleanup:
return retval;
}
+static int struct_member_index(struct ain_struct *st, const char *name)
+{
+ for (int i = 0; i < st->nr_members; i++) {
+ if (!strcmp(st->members[i].name, name))
+ return i;
+ }
+ return -1;
+}
+
+static union vm_value gsave_to_vm_value(struct gsave *save, enum ain_data_type type, int struct_type, int array_rank, int32_t value);
+
+static struct gsave_flat_array *gsave_load_array(struct gsave *save, struct page *page, struct gsave_flat_array *flat_array)
+{
+ assert(page->type == ARRAY_PAGE);
+ if (page->array.rank > 1) {
+ for (int i = 0; i < page->nr_vars; i++)
+ flat_array = gsave_load_array(save, heap_get_page(page->values[i].i), flat_array);
+ return flat_array;
+ }
+
+ if (page->nr_vars != flat_array->nr_values)
+ VM_ERROR("Bad save file: unexpected number of array elements");
+ for (int i = 0; i < page->nr_vars; i++) {
+ int struct_type, array_rank;
+ enum ain_data_type data_type = variable_type(page, i, &struct_type, &array_rank);
+ if (data_type != flat_array->values[i].type)
+ VM_ERROR("Bad save file: unexpected array element type");
+ union vm_value val = gsave_to_vm_value(save, data_type, struct_type, array_rank, flat_array->values[i].value);
+ page->values[i] = val;
+ }
+ return flat_array + 1;
+}
+
+static union vm_value gsave_to_vm_value(struct gsave *save, enum ain_data_type type, int struct_type, int array_rank, int32_t value)
+{
+ switch (type) {
+ case AIN_VOID:
+ case AIN_INT:
+ case AIN_BOOL:
+ case AIN_LONG_INT:
+ case AIN_FLOAT:
+ return vm_int(value);
+ case AIN_STRING:
+ {
+ int slot = heap_alloc_slot(VM_STRING);
+ heap[slot].s = string_ref(save->strings[value]);
+ return vm_int(slot);
+ }
+ case AIN_STRUCT:
+ {
+ struct gsave_record *rec = &save->records[value];
+ struct ain_struct *st = &ain->structures[struct_type];
+ if (strcmp(rec->struct_name, st->name))
+ VM_ERROR("Bad save file: structure name mismatch");
+ int slot = alloc_struct(struct_type);
+ struct page *page = heap[slot].page;
+ for (int i = 0; i < rec->nr_indices; i++) {
+ struct gsave_keyval *kv = &save->keyvals[rec->indices[i]];
+ int index = struct_member_index(st, kv->name);
+ if (index < 0)
+ VM_ERROR("Bad save file: %s has no member named %s", display_sjis0(rec->struct_name), display_sjis1(kv->name));
+ int struct_type, array_rank;
+ enum ain_data_type data_type = variable_type(page, index, &struct_type, &array_rank);
+ if (data_type != kv->type)
+ VM_ERROR("Bad save file: structure member type mismatch");
+ union vm_value val = gsave_to_vm_value(save, data_type, struct_type, array_rank, kv->value);
+ page->values[index] = val;
+ }
+ return vm_int(slot);
+ }
+ case AIN_ARRAY_TYPE:
+ {
+ struct gsave_array *array = &save->arrays[value];
+ int slot = heap_alloc_slot(VM_PAGE);
+ if (array->rank == -1) {
+ heap[slot].page = NULL;
+ return vm_int(slot);
+ }
+ if (array_rank != array->rank)
+ VM_ERROR("Bad save file: array rank mismatch");
+ union vm_value *dims = xmalloc(sizeof(union vm_value) * array_rank);
+ for (int i = 0; i < array_rank; i++)
+ dims[i].i = array->dimensions[array_rank - 1 - i];
+ struct page *page = alloc_array(array_rank, dims, type, struct_type, false);
+ heap[slot].page = page;
+ free(dims);
+ gsave_load_array(save, page, array->flat_arrays);
+ return vm_int(slot);
+ }
+ case AIN_REF_TYPE:
+ return vm_int(-1);
+ default:
+ WARNING("Unhandled data type: %s", ain_strtype(ain, type, -1));
+ return vm_int(-1);
+ }
+}
+
+int load_globals_from_gsave(struct gsave *save, const char *keyname, const char *group_name, int *n)
+{
+ if (strcmp(keyname, save->key))
+ VM_ERROR("Attempted to load save data with wrong key: %s", display_sjis0(keyname));
+ if (!group_name)
+ group_name = "";
+ if (!save->group)
+ save->group = strdup("");
+ if (strcmp(group_name, save->group))
+ VM_ERROR("Attempted to load save data with wrong group name: '%s'", display_sjis0(group_name));
+
+ for (struct gsave_global *g = save->globals; g < save->globals + save->nr_globals; g++) {
+ int global_index = ain_get_global(ain, g->name);
+ if (global_index < 0) {
+ WARNING("invalid global name %s", display_sjis0(g->name));
+ return 0;
+ }
+ struct ain_type *type = &ain->globals[global_index].type;
+ if (type->data != g->type) {
+ WARNING("%s: type mismatch", display_sjis0(g->name));
+ return 0;
+ }
+ bool call_dtors = false; // Destructors for old objects should not be called.
+ global_set(global_index, gsave_to_vm_value(save, type->data, type->struc, type->rank, g->value), call_dtors);
+ if (n)
+ (*n)++;
+ }
+
+ return 1;
+}
+
+int load_globals(const char *keyname, const char *filename, const char *group_name, int *n)
+{
+ char *path = savedir_path(filename);
+ int retval;
+
+ // First, try reading as a gsave.
+ enum savefile_error error;
+ struct gsave *save = gsave_read(path, &error);
+ switch (error) {
+ case SAVEFILE_SUCCESS:
+ free(path);
+ retval = load_globals_from_gsave(save, keyname, group_name, n);
+ gsave_free(save);
+ return retval;
+ case SAVEFILE_INVALID_SIGNATURE:
+ // If not a System4 save file, try reading as a json.
+ retval = load_globals_from_json(path, keyname, group_name, n);
+ free(path);
+ return retval;
+ default:
+ WARNING("%s: %s", display_utf0(path), savefile_strerror(error));
+ free(path);
+ return 0;
+ }
+}
+
int delete_save_file(const char *filename)
{
char *path = savedir_path(filename);
diff --git a/src/vm.c b/src/vm.c
index 042a6e0..547ff8c 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -429,7 +429,7 @@ static void system_call(enum syscall_code code)
case SYS_GLOBAL_SAVE: { // system.GlobalSave(string szKeyName, string szFileName)
int filename = stack_pop().i;
int keyname = stack_pop().i;
- stack_push(save_globals(heap_get_string(keyname)->text, heap_get_string(filename)->text));
+ stack_push(save_globals(heap_get_string(keyname)->text, heap_get_string(filename)->text, NULL, NULL));
heap_unref(filename);
heap_unref(keyname);
break;
@@ -603,7 +603,7 @@ static void system_call(enum syscall_code code)
int groupname = stack_pop().i;
int filename = stack_pop().i;
int keyname = stack_pop().i;
- stack_push(save_group(heap_get_string(keyname)->text,
+ stack_push(save_globals(heap_get_string(keyname)->text,
heap_get_string(filename)->text,
heap_get_string(groupname)->text,
&n->i));
diff --git a/subprojects/libsys4 b/subprojects/libsys4
index 187a463..b869f6c 160000
--- a/subprojects/libsys4
+++ b/subprojects/libsys4
@@ -1 +1 @@
-Subproject commit 187a463044a0b9b207f322796aa20c42322fed98
+Subproject commit b869f6cc8d6f417272de6a2f01ef66473cb06e22