mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-22 23:18:01 +03:00
Use global/group save file format compatible with System4
After this, xsystem4 uses the global/group save format used by the original System4, instead of the JSON format. Existing JSON save files can still be loaded. This makes save files interoperable between System4 and xsystem4 in some games, including Sengoku Rance and Toushin Toshi III.
This commit is contained in:
+1
-2
@@ -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);
|
||||
|
||||
|
||||
+296
-65
@@ -14,6 +14,7 @@
|
||||
* along with this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
+1
-1
Submodule subprojects/libsys4 updated: 187a463044...b869f6cc8d
Reference in New Issue
Block a user