From 64edcfda29c1164347eec24c76686a4fb9acc4e8 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sat, 4 Mar 2023 14:39:30 +0900 Subject: [PATCH] Support loading system3.5 v1 save files --- src/savedata.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++--- src/savedata.h | 1 + src/scenario.c | 157 +++++++++++++++++++++++++++-------- 3 files changed, 330 insertions(+), 48 deletions(-) diff --git a/src/savedata.c b/src/savedata.c index 8fee3d0..11d4663 100644 --- a/src/savedata.c +++ b/src/savedata.c @@ -23,6 +23,7 @@ #include "config.h" +#include #include #include #include @@ -44,6 +45,7 @@ typedef int emscripten_align1_int; #include "message.h" const char *save_signature[] = { + [SAVEFMT_SYS35] = "This is save data for System3.5 Win95 For NACT/ADV system (C) 1996 ALICE-SOFT", [SAVEFMT_XSYS35] = "System3.5 SavaData(c)ALICE-SOFT", [SAVEFMT_SYS36] = "System3.5 SaveData(c)ALICE-SOFT", [SAVEFMT_SYS38] = "System3.8 SaveData(c)ALICE-SOFT", @@ -473,19 +475,83 @@ int save_copyAll(int dstno, int srcno) { return status; } +static int loadpartial_sys35(char *saveTop, int filesize, struct VarRef *vref, int cnt) { + if (filesize < 0xb0) + return SAVE_LOADERR; + cnt = min(cnt, v_sliceSize(vref)); + int *var = v_resolveRef(vref); + + if (vref->page == 0) { + // SysVar section + int section_offset = LittleEndian_getDW(saveTop, 0x90); + int section_length = LittleEndian_getDW(saveTop, 0x94); + if (section_offset + 16 + section_length > filesize) + return SAVE_LOADERR; + + int sysvar_count = LittleEndian_getDW(saveTop, section_offset); + if (sysvar_count > varPage[0].size) + return SAVE_LOADERR; // resizing system page is not supported in sys3.5 + int offset = section_offset + 16 + vref->index * 2; + for (int i = 0; i < cnt; i++) { + var[i] = LittleEndian_getW(saveTop, offset); + offset += 2; + } + return SAVE_LOADOK; + } + // Arrays section + int section_offset = LittleEndian_getDW(saveTop, 0xa0); + int section_length = LittleEndian_getDW(saveTop, 0xa4); + if (section_offset + 16 + section_length > filesize) + return SAVE_LOADERR; + int page_count = LittleEndian_getDW(saveTop, section_offset + 4); + int offset = section_offset + 16; + // TODO: section size check + for (int i = 0; i < page_count; i++) { + int page = LittleEndian_getDW(saveTop, offset); offset += 4; + int count = LittleEndian_getDW(saveTop, offset); offset += 4; + int content_offset = LittleEndian_getDW(saveTop, offset); offset += 4; + offset += 4; + if (page != vref->page) + continue; + // TODO page resize + // TODO: section size check + content_offset += vref->index * 2; + for (int j = 0; j < cnt; j++) { + var[j] = LittleEndian_getW(saveTop, content_offset + j * 2); + } + return SAVE_LOADOK; + } + return SAVE_LOADERR; +} + /* データの一部ロード */ int save_loadPartial(int no, struct VarRef *vref, int cnt) { if (no >= SAVE_MAXNUMBER) return SAVE_SAVEERR; - cnt = min(cnt, v_sliceSize(vref)); - int *var = v_resolveRef(vref); - int status, filesize; char *saveTop = loadGameData(no, &status, &filesize); if (!saveTop) return status; - + if (filesize < 0x60) + goto errexit; + enum save_format format = (enum save_format)-1; + for (int i = 0; i < sizeof(save_signature) / sizeof(save_signature[0]); i++) { + if (!strcmp(save_signature[i], saveTop)) { + format = i; + break; + } + } + if (format < 0) { + WARNING("unrecognized save format"); + goto errexit; + } + + if (format == SAVEFMT_SYS35) { + int result = loadpartial_sys35(saveTop, filesize, vref, cnt); + free(saveTop); + return result; + } if (filesize <= sizeof(asd_baseHdr)) goto errexit; @@ -495,6 +561,8 @@ int save_loadPartial(int no, struct VarRef *vref, int cnt) { goto errexit; } + cnt = min(cnt, v_sliceSize(vref)); + int *var = v_resolveRef(vref); if (save_base->varSys[vref->page] == 0) goto errexit; @@ -551,6 +619,125 @@ int save_savePartial(int no, struct VarRef *vref, int cnt) { return SAVE_SAVEERR; } +static int loadall_sys35(char *saveTop, int filesize) { + if (filesize < 0xb0) + return SAVE_LOADERR; + + int page = LittleEndian_getDW(saveTop, 0x80); + int addr = LittleEndian_getDW(saveTop, 0x84); + sl_jmpFar2(page - 1, addr); + + // SysVar section + int section_offset = LittleEndian_getDW(saveTop, 0x90); + int section_length = LittleEndian_getDW(saveTop, 0x94); + if (section_offset + 16 + section_length > filesize) + return SAVE_LOADERR; + + int sysvar_count = LittleEndian_getDW(saveTop, section_offset); + if (sysvar_count > varPage[0].size) + return SAVE_LOADERR; // resizing system page is not supported in sys3.5 + int offset = section_offset + 16; + for (int i = 0; i < sysvar_count; i++) { + varPage[0].value[i] = LittleEndian_getW(saveTop, offset); + offset += 2; + } + nact->sel.MsgFontSize = LittleEndian_getW(saveTop, offset); offset += 2; + nact->msg.MsgFontSize = LittleEndian_getW(saveTop, offset); offset += 2; + cg_vspPB = LittleEndian_getW(saveTop, offset); offset += 2; + nact->msg.MsgFontColor = LittleEndian_getW(saveTop, offset); offset += 2; + nact->sel.MsgFontColor = LittleEndian_getW(saveTop, offset); offset += 2; + nact->sel.WinFrameColor = LittleEndian_getW(saveTop, offset); offset += 2; + nact->sel.WinBackgroundColor = LittleEndian_getW(saveTop, offset); offset += 2; + nact->msg.WinFrameColor = LittleEndian_getW(saveTop, offset); offset += 2; + nact->msg.WinBackgroundColor = LittleEndian_getW(saveTop, offset); offset += 2; + int left = section_offset + 16 + section_length - offset; + WARNING("0x%x bytes for window info", left); + if (left < 0) + return SAVE_LOADERR; + int win_info_count = left / 32; + if (win_info_count >= SELWINMAX) + return SAVE_LOADERR; + for (int i = 1; i <= win_info_count; i++) { + offset += 2; + nact->sel.wininfo[i].x = LittleEndian_getW(saveTop, offset); offset += 2; + nact->sel.wininfo[i].y = LittleEndian_getW(saveTop, offset); offset += 2; + int ex = LittleEndian_getW(saveTop, offset); offset += 2; + int ey = LittleEndian_getW(saveTop, offset); offset += 2; + nact->sel.wininfo[i].width = ex + 1 - nact->sel.wininfo[i].x; + nact->sel.wininfo[i].height = ey + 1 - nact->sel.wininfo[i].y; + offset += 6; + } + for (int i = 1; i <= win_info_count; i++) { + offset += 2; + nact->msg.wininfo[i].x = LittleEndian_getW(saveTop, offset); offset += 2; + nact->msg.wininfo[i].y = LittleEndian_getW(saveTop, offset); offset += 2; + int ex = LittleEndian_getW(saveTop, offset); offset += 2; + int ey = LittleEndian_getW(saveTop, offset); offset += 2; + nact->msg.wininfo[i].width = ex + 1 - nact->msg.wininfo[i].x; + nact->msg.wininfo[i].height = ey + 1 - nact->msg.wininfo[i].y; + offset += 6; + } + //assert(offset == section_offset + 16 + section_length); + + // Strings section + section_offset = LittleEndian_getDW(saveTop, 0x98); + section_length = LittleEndian_getDW(saveTop, 0x9c); + if (section_offset + 16 + section_length > filesize) + return SAVE_LOADERR; + + int strvar_count = LittleEndian_getDW(saveTop, section_offset); + int strvar_length = LittleEndian_getDW(saveTop, section_offset + 4); + offset = section_offset + 16; + if (section_length != strvar_count * strvar_length) + return SAVE_LOADERR; + if (strvar_count > svar_maxindex()) + strvar_count = svar_maxindex(); + char *buf = malloc(strvar_length + 1); + buf[strvar_length] = '\0'; + for (int i = 1; i <= strvar_count; i++) { + memcpy(buf, saveTop + offset, strvar_length); + svar_set(i, buf); + offset += strvar_length; + } + + // Arrays section + section_offset = LittleEndian_getDW(saveTop, 0xa0); + section_length = LittleEndian_getDW(saveTop, 0xa4); + if (section_offset + 16 + section_length > filesize) + return SAVE_LOADERR; + + int array_total_size = LittleEndian_getDW(saveTop, section_offset); + int page_count = LittleEndian_getDW(saveTop, section_offset + 4); + offset = section_offset + 16; + // TODO: section size check + int total = 0; + for (int i = 0; i < page_count; i++) { + int page = LittleEndian_getDW(saveTop, offset); offset += 4; + int count = LittleEndian_getDW(saveTop, offset); offset += 4; + int content_offset = LittleEndian_getDW(saveTop, offset); offset += 4; + offset += 4; + total += count; + // TODO page resize + // TODO: section size check + for (int j = 0; j < count; j++) { + varPage[page].value[j] = LittleEndian_getW(saveTop, content_offset + j * 2); + } + } + if (total != array_total_size) { + WARNING("wrong array total size"); + } else { + WARNING("correct array total size"); + } + + // Stack section + section_offset = LittleEndian_getDW(saveTop, 0xa8); + section_length = LittleEndian_getDW(saveTop, 0xac); + if (section_offset + section_length > filesize) + return SAVE_LOADERR; + + sl_loadStack(SAVEFMT_SYS35, saveTop + section_offset, section_length); + return SAVE_LOADOK; +} /* データのロード */ int save_loadAll(int no) { @@ -561,17 +748,11 @@ int save_loadAll(int no) { char *saveTop = loadGameData(no, &status, &filesize); if (!saveTop) return status; - if (filesize <= sizeof(asd_baseHdr)) + if (filesize < 0x60) goto errexit; - /* 各種データの反映 */ - asd_baseHdr *save_base = (asd_baseHdr *)saveTop; - if (save_base->version != SAVE_DATAVERSION) { - WARNING("endian mismatch"); - goto errexit; - } enum save_format format = (enum save_format)-1; for (int i = 0; i < sizeof(save_signature) / sizeof(save_signature[0]); i++) { - if (!strcmp(save_signature[i], save_base->ID)) { + if (!strcmp(save_signature[i], saveTop)) { format = i; break; } @@ -581,6 +762,21 @@ int save_loadAll(int no) { goto errexit; } + if (format == SAVEFMT_SYS35) { + int result = loadall_sys35(saveTop, filesize); + free(saveTop); + return result; + } + + if (filesize <= sizeof(asd_baseHdr)) + goto errexit; + /* 各種データの反映 */ + asd_baseHdr *save_base = (asd_baseHdr *)saveTop; + if (save_base->version != SAVE_DATAVERSION) { + WARNING("endian mismatch"); + goto errexit; + } + nact->sel.MsgFontSize = save_base->selMsgSize; nact->sel.MsgFontColor = save_base->selMsgColor; nact->sel.WinBackgroundColor = save_base->selBackColor; diff --git a/src/savedata.h b/src/savedata.h index 0862345..5f5a45a 100644 --- a/src/savedata.h +++ b/src/savedata.h @@ -38,6 +38,7 @@ enum save_format { SAVEFMT_XSYS35, + SAVEFMT_SYS35, // System3.5 v1.x SAVEFMT_SYS36, // System3.5 v2.x - System3.6 SAVEFMT_SYS38, // System3.8 - System3.9 }; diff --git a/src/scenario.c b/src/scenario.c index bd58596..c50c1ae 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -354,7 +354,7 @@ void sl_popVar(struct VarRef *vref, int cnt) { } } -enum save_stack_frame_type { +enum xsys35_stack_frame_type { SAVE_NEARJMP = 1, SAVE_FARJMP = 2, SAVE_VARIABLE = 3, @@ -367,15 +367,15 @@ enum txx_type { TxxTEXTLOC = 3 }; -struct save_stack_frame { - struct save_stack_frame *next; +struct xsys35_stack_frame { + struct xsys35_stack_frame *next; int len; int buf[]; }; -struct save_stack_frame *push_save_stack_frame(int size, struct save_stack_frame *next) { - struct save_stack_frame *f = - malloc(sizeof(struct save_stack_frame) + sizeof(int) * size); +struct xsys35_stack_frame *push_xsys35_stack_frame(int size, struct xsys35_stack_frame *next) { + struct xsys35_stack_frame *f = + malloc(sizeof(struct xsys35_stack_frame) + sizeof(int) * size); f->next = next; f->len = size; return f; @@ -391,13 +391,13 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { } // Serialize to the stack format used in old versions of xsystem35. - struct save_stack_frame *frame = NULL; + struct xsys35_stack_frame *frame = NULL; uint8_t *sp = stack_top; while (sp > stack_buf) { switch (sp[-1]) { case STACK_FARCALL: sp -= 7; - frame = push_save_stack_frame(4, frame); + frame = push_xsys35_stack_frame(4, frame); frame->buf[3] = SAVE_FARJMP; frame->buf[2] = 2; frame->buf[1] = LittleEndian_getW(sp, 0) - 1; // page @@ -405,7 +405,7 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { break; case STACK_NEARCALL: sp -= 5; - frame = push_save_stack_frame(3, frame); + frame = push_xsys35_stack_frame(3, frame); frame->buf[2] = SAVE_NEARJMP; frame->buf[1] = 1; frame->buf[0] = LittleEndian_getDW(sp, 0); // addr @@ -422,7 +422,7 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { int base = frame->buf[frame->len - 4]; // Merge into existing SAVE_VARIABLE frame. if (page == fpage && index == base - 1) { - frame = realloc(frame, sizeof(struct save_stack_frame) + sizeof(int) * (frame->len + 1)); + frame = realloc(frame, sizeof(struct xsys35_stack_frame) + sizeof(int) * (frame->len + 1)); frame->len++; frame->buf[frame->len - 1] = SAVE_VARIABLE; frame->buf[frame->len - 2] = count + 1; @@ -432,7 +432,7 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { break; } } - frame = push_save_stack_frame(5, frame); + frame = push_xsys35_stack_frame(5, frame); frame->buf[4] = SAVE_VARIABLE; frame->buf[3] = 3; frame->buf[2] = page; @@ -442,7 +442,7 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { break; case STACK_TEXTCOLOR: sp -= 3; - frame = push_save_stack_frame(5, frame); + frame = push_xsys35_stack_frame(5, frame); frame->buf[4] = SAVE_TXXSTATE; frame->buf[3] = 3; frame->buf[2] = TxxTEXTCOLOR; @@ -451,7 +451,7 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { break; case STACK_TEXTSIZE: sp -= 6; - frame = push_save_stack_frame(5, frame); + frame = push_xsys35_stack_frame(5, frame); frame->buf[4] = SAVE_TXXSTATE; frame->buf[3] = 3; frame->buf[2] = TxxTEXTSIZE; @@ -460,7 +460,7 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { break; case STACK_TEXTLOC: sp -= 9; - frame = push_save_stack_frame(5, frame); + frame = push_xsys35_stack_frame(5, frame); frame->buf[4] = SAVE_TXXSTATE; frame->buf[3] = 3; frame->buf[2] = TxxTEXTLOC; @@ -472,15 +472,15 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { } } int total_len = 0; - for (struct save_stack_frame *f = frame; f; f = f->next) + for (struct xsys35_stack_frame *f = frame; f; f = f->next) total_len += f->len; int *buf = malloc(total_len * sizeof(int)); int *p = buf; - for (struct save_stack_frame *f = frame; f;) { + for (struct xsys35_stack_frame *f = frame; f;) { memcpy(p, f->buf, f->len * sizeof(int)); p += f->len; - struct save_stack_frame *next = f->next; + struct xsys35_stack_frame *next = f->next; free(f); f = next; } @@ -510,36 +510,22 @@ uint8_t *sl_saveStack(enum save_format format, int *size_out) { return (uint8_t *)buf; } -void sl_loadStack(enum save_format format, uint8_t *unaligned_data, int size) { - if (format != SAVEFMT_XSYS35) { - if (size > stack_size) { - while (size > stack_size) - stack_size *= 2; - free(stack_buf); - stack_buf = malloc(stack_size); - if (!stack_buf) - NOMEMERR(); - } - memcpy(stack_buf, unaligned_data, size); - stack_top = stack_buf + size; - return; - } - +static void load_stack_xsys35(uint8_t *unaligned_data, int size) { // Deserialize from the stack format used in old versions of xsystem35. int *data = malloc(size); memcpy(data, unaligned_data, size); - struct save_stack_frame *frame = NULL; + struct xsys35_stack_frame *frame = NULL; for (int *p = data + size / sizeof(int); p > data;) { int len = p[-2] + 2; p -= len; - frame = push_save_stack_frame(len, frame); + frame = push_xsys35_stack_frame(len, frame); memcpy(frame->buf, p, len * sizeof(int)); } free(data); stack_top = stack_buf; - for (struct save_stack_frame *f = frame; f;) { + for (struct xsys35_stack_frame *f = frame; f;) { switch (f->buf[f->len - 1]) { case SAVE_NEARJMP: if (f->len != 3) @@ -601,12 +587,111 @@ void sl_loadStack(enum save_format format, uint8_t *unaligned_data, int size) { default: SYSERROR("broken stack data"); } - struct save_stack_frame *next = f->next; + struct xsys35_stack_frame *next = f->next; free(f); f = next; } } +struct asdv1_stack_frame { + struct asdv1_stack_frame *next; + int len; + uint8_t buf[]; +}; + +struct asdv1_stack_frame *push_asdv1_stack_frame(int size, struct asdv1_stack_frame *next) { + struct asdv1_stack_frame *f = + malloc(sizeof(struct asdv1_stack_frame) + size); + f->next = next; + f->len = size; + return f; +} + +static void load_stack_sys35(uint8_t *data, int size) { + struct asdv1_stack_frame *frame = NULL; + for (uint8_t *p = data + size; p > data;) { + int frame_size = 0; + switch (p[-1]) { + case STACK_FARCALL: + frame_size = 13; + break; + case 0xee: + frame_size = 5; + break; + case 0xdd: + frame_size = LittleEndian_getW(p, -3) * 2 + 5; + break; + } + if (!frame_size) + break; // System3.5 save files has some junk data at the stack bottom. + p -= frame_size; + frame = push_asdv1_stack_frame(frame_size, frame); + memcpy(frame->buf, p, frame_size); + } + + stack_top = stack_buf; + for (struct asdv1_stack_frame *f = frame; f;) { + switch (f->buf[f->len - 1]) { + case STACK_FARCALL: + stack_reserve(2 + 4 + 1); + if (LittleEndian_getDW(f->buf, 0) != 0) + WARNING("unexpected stack structure"); + stack_push_word(LittleEndian_getDW(f->buf, 4)); + stack_push_dword(LittleEndian_getDW(f->buf, 8)); + stack_push_byte(STACK_FARCALL); + break; + case STACK_NEARCALL: + stack_reserve(4 + 1); + stack_push_dword(LittleEndian_getDW(f->buf, 0)); + stack_push_byte(STACK_NEARCALL); + break; + case STACK_VARIABLE: + { + int count = LittleEndian_getW(f->buf, f->len - 3); + int base = LittleEndian_getW(f->buf, f->len - 5); + stack_reserve((2 + 2 + 2 + 1) * count); + for (int i = 0; i < count; i++) { + stack_push_word(base + 1); + stack_push_word(0); // page + stack_push_word(LittleEndian_getW(f->buf, i * 2)); + stack_push_byte(STACK_VARIABLE); + } + } + break; + } + struct asdv1_stack_frame *next = f->next; + free(f); + f = next; + } +} + +static void load_stack_sys39(uint8_t *data, int size) { + if (size > stack_size) { + while (size > stack_size) + stack_size *= 2; + free(stack_buf); + stack_buf = malloc(stack_size); + if (!stack_buf) + NOMEMERR(); + } + memcpy(stack_buf, data, size); + stack_top = stack_buf + size; +} + +void sl_loadStack(enum save_format format, uint8_t *unaligned_data, int size) { + switch (format) { + case SAVEFMT_XSYS35: + load_stack_xsys35(unaligned_data, size); + break; + case SAVEFMT_SYS35: + load_stack_sys35(unaligned_data, size); + break; + default: + load_stack_sys39(unaligned_data, size); + break; + } +} + void sl_getStackInfo(struct stack_info *info) { memset(info, 0, sizeof(struct stack_info));