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