/*************************************************************************** state.c Save state management functions. Copyright Nicola Salmoria and the MAME Team. Visit http://mamedev.org for licensing and usage restrictions. **************************************************************************** Save state file format: 0.. 7 'MAMESAVE" 8 Format version (this is format 1) 9 Flags a..13 Game name padded with \0 14..17 Signature 18..end Save game data ***************************************************************************/ #include "driver.h" #include "astring.h" #include "deprecat.h" #include /*************************************************************************** DEBUGGING ***************************************************************************/ #define VERBOSE 0 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) /*************************************************************************** CONSTANTS ***************************************************************************/ #define SAVE_VERSION 1 #define TAG_STACK_SIZE 4 /* Available flags */ enum { SS_MSB_FIRST = 0x02 }; /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ typedef struct _ss_entry ss_entry; struct _ss_entry { ss_entry * next; /* pointer to next entry */ void * data; /* pointer to the memory to save/restore */ astring * name; /* full name */ UINT8 typesize; /* size of the raw data type */ UINT32 typecount; /* number of items */ int tag; /* saving tag */ UINT32 offset; /* offset within the final structure */ }; typedef struct _ss_func ss_func; struct _ss_func { ss_func * next; /* pointer to next entry */ union { state_presave_func presave; /* presave callback */ state_postload_func postload; /* postload callback */ } func; /* function pointers */ void * param; /* function parameter */ int tag; /* saving tag */ }; /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ static int ss_illegal_regs; static ss_entry *ss_registry; static ss_func *ss_prefunc_reg; static ss_func *ss_postfunc_reg; static int ss_tag_stack[TAG_STACK_SIZE]; static int ss_tag_stack_index; static int ss_current_tag; static UINT8 ss_registration_allowed; static UINT8 *ss_dump_array; static mame_file *ss_dump_file; static UINT32 ss_dump_size; #ifdef MESS static const char ss_magic_num[8] = { 'M', 'E', 'S', 'S', 'S', 'A', 'V', 'E' }; #else static const char ss_magic_num[8] = { 'M', 'A', 'M', 'E', 'S', 'A', 'V', 'E' }; #endif /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ static void ss_c2(UINT8 *, UINT32); static void ss_c4(UINT8 *, UINT32); static void ss_c8(UINT8 *, UINT32); static void (*const ss_conv[])(UINT8 *, UINT32) = { 0, 0, ss_c2, 0, ss_c4, 0, 0, 0, ss_c8 }; /*************************************************************************** INITIALIZATION ***************************************************************************/ /*------------------------------------------------- state_init - initialize the system and reset all registrations -------------------------------------------------*/ void state_init(running_machine *machine) { ss_illegal_regs = 0; ss_current_tag = 0; ss_tag_stack_index = 0; ss_registration_allowed = FALSE; } /*************************************************************************** TAGGING ***************************************************************************/ /*------------------------------------------------- state_save_push_tag - push the current tag onto the stack and set a new tag -------------------------------------------------*/ void state_save_push_tag(int tag) { if (ss_tag_stack_index == TAG_STACK_SIZE - 1) fatalerror("state_save tag stack overflow"); ss_tag_stack[ss_tag_stack_index++] = ss_current_tag; ss_current_tag = tag; } /*------------------------------------------------- state_save_pop_tag - pop the tag from the top of the stack -------------------------------------------------*/ void state_save_pop_tag(void) { if (ss_tag_stack_index == 0) fatalerror("state_save tag stack underflow"); ss_current_tag = ss_tag_stack[--ss_tag_stack_index]; } /*------------------------------------------------- state_save_get_reg_count - count the number of registered entries on the current tag -------------------------------------------------*/ int state_save_get_reg_count(void) { ss_entry *entry; int count = 0; /* iterate over entries with matching tags */ for (entry = ss_registry; entry; entry = entry->next) if (entry->tag == ss_current_tag) count++; return count; } /*************************************************************************** REGISTRATION HANDLING ***************************************************************************/ /*------------------------------------------------- state_save_allow_registration - allow/disallow registrations to happen -------------------------------------------------*/ void state_save_allow_registration(int allowed) { /* allow/deny registration */ ss_registration_allowed = allowed; } /*------------------------------------------------- state_save_registration_allowed - query whether or not registrations are allowed -------------------------------------------------*/ int state_save_registration_allowed(void) { return ss_registration_allowed; } /*------------------------------------------------- state_save_register_memory - register an array of data in memory -------------------------------------------------*/ void state_save_register_memory(const char *module, UINT32 instance, const char *name, void *val, UINT32 valsize, UINT32 valcount) { ss_entry **entry, *next; astring *totalname; assert(valsize == 1 || valsize == 2 || valsize == 4 || valsize == 8); /* check for invalid timing */ if (!ss_registration_allowed) { logerror("Attempt to register save state entry after state registration is closed! module %s name %s\n",module,name); if (Machine->gamedrv->flags & GAME_SUPPORTS_SAVE) fatalerror("Attempt to register save state entry after state registration is closed! module %s name %s\n",module,name); ss_illegal_regs++; return; } /* create the full name */ totalname = astring_alloc(); astring_printf(totalname, "%X/%s/%X/%s", ss_current_tag, module, instance, name); /* look for duplicates and an entry to insert in front of */ for (entry = &ss_registry; *entry; entry = &(*entry)->next) { /* stop if the next guy's string is greater than ours */ int cmpval = astring_cmp((*entry)->name, totalname); if (cmpval > 0) break; /* error if we are equal */ if ((*entry)->tag == ss_current_tag && cmpval == 0) fatalerror("Duplicate save state registration entry (%d, %s)", ss_current_tag, astring_c(totalname)); } /* didn't find one; allocate a new one */ next = *entry; *entry = malloc_or_die(sizeof(**entry)); memset(*entry, 0, sizeof(**entry)); /* fill in the rest */ (*entry)->next = next; (*entry)->data = val; (*entry)->name = totalname; (*entry)->typesize = valsize; (*entry)->typecount = valcount; (*entry)->tag = ss_current_tag; restrack_register_object(OBJTYPE_STATEREG, *entry, 0, __FILE__, __LINE__); } /*------------------------------------------------- state_save_register_bitmap - register a bitmap to be saved -------------------------------------------------*/ void state_save_register_bitmap(const char *module, UINT32 instance, const char *name, bitmap_t *val) { state_save_register_memory(module, instance, name, val->base, val->bpp / 8, val->rowpixels * val->height); } /*************************************************************************** CALLBACK FUNCTION REGISTRATION ***************************************************************************/ /*------------------------------------------------- state_save_register_presave - register a pre-save function callback -------------------------------------------------*/ void state_save_register_presave(running_machine *machine, state_presave_func func, void *param) { ss_func **cur; /* check for invalid timing */ if (!ss_registration_allowed) fatalerror("Attempt to register callback function after state registration is closed!"); /* scan for duplicates and push through to the end */ for (cur = &ss_prefunc_reg; *cur; cur = &(*cur)->next) if ((*cur)->func.presave == func && (*cur)->param == param && (*cur)->tag == ss_current_tag) fatalerror("Duplicate save state function (%d, %p, %p)", ss_current_tag, param, func); /* allocate a new entry */ *cur = malloc_or_die(sizeof(ss_func)); /* fill it in */ (*cur)->next = NULL; (*cur)->func.presave = func; (*cur)->param = param; (*cur)->tag = ss_current_tag; restrack_register_object(OBJTYPE_STATEREG, *cur, 1, __FILE__, __LINE__); } /*------------------------------------------------- state_save_register_postload - register a post-load function callback -------------------------------------------------*/ void state_save_register_postload(running_machine *machine, state_postload_func func, void *param) { ss_func **cur; /* check for invalid timing */ if (!ss_registration_allowed) fatalerror("Attempt to register callback function after state registration is closed!"); /* scan for duplicates and push through to the end */ for (cur = &ss_postfunc_reg; *cur; cur = &(*cur)->next) if ((*cur)->func.postload == func && (*cur)->param == param && (*cur)->tag == ss_current_tag) fatalerror("Duplicate save state function (%d, %p, %p)", ss_current_tag, param, func); /* allocate a new entry */ *cur = malloc_or_die(sizeof(ss_func)); /* fill it in */ (*cur)->next = NULL; (*cur)->func.postload = func; (*cur)->param = param; (*cur)->tag = ss_current_tag; restrack_register_object(OBJTYPE_STATEREG, *cur, 2, __FILE__, __LINE__); } /*************************************************************************** REGISTRATION FREEING ***************************************************************************/ /*------------------------------------------------- func_free - free registered functions attached to the current resource tag -------------------------------------------------*/ static void func_free(ss_func **root, void *ptr) { ss_func **func; /* iterate over the function list */ for (func = root; *func; ) { /* if this entry matches, free it */ if (*func == ptr) { ss_func *func_to_free = *func; /* de-link us from the list and free our memory */ *func = (*func)->next; free(func_to_free); break; } /* otherwise, advance */ else func = &(*func)->next; } } /*------------------------------------------------- state_save_free - free all registrations that have been tagged with the current resource tag -------------------------------------------------*/ void state_destructor(void *ptr, size_t size) { /* size of 0 means an entry */ if (size == 0) { ss_entry **entry; /* iterate over entries */ for (entry = &ss_registry; *entry; ) { /* if this entry matches, free it */ if (*entry == ptr) { ss_entry *entry_to_free = *entry; /* de-link us from the list and free our memory */ *entry = (*entry)->next; astring_free(entry_to_free->name); free(entry_to_free); break; } /* if not a match, move on */ else entry = &(*entry)->next; } } /* size of 1 means a pre function */ else if (size == 1) func_free(&ss_prefunc_reg, ptr); /* size of 2 means a post function */ else if (size == 2) func_free(&ss_postfunc_reg, ptr); /* if we're clear of all registrations, reset the invalid counter */ if (ss_registry == NULL && ss_prefunc_reg == NULL && ss_postfunc_reg == NULL) ss_illegal_regs = 0; } /*************************************************************************** ENDIAN CONVERSION ***************************************************************************/ /*------------------------------------------------- ss_c2 - byte swap an array of 16-bit data -------------------------------------------------*/ static void ss_c2(UINT8 *data, UINT32 size) { UINT16 *convert = (UINT16 *)data; unsigned i; for (i = 0; i < size; i++) convert[i] = FLIPENDIAN_INT16(convert[i]); } /*------------------------------------------------- ss_c4 - byte swap an array of 32-bit data -------------------------------------------------*/ static void ss_c4(UINT8 *data, UINT32 size) { UINT32 *convert = (UINT32 *)data; unsigned i; for (i = 0; i < size; i++) convert[i] = FLIPENDIAN_INT32(convert[i]); } /*------------------------------------------------- ss_c8 - byte swap an array of 64-bit data -------------------------------------------------*/ static void ss_c8(UINT8 *data, UINT32 size) { UINT64 *convert = (UINT64 *)data; unsigned i; for (i = 0; i < size; i++) convert[i] = FLIPENDIAN_INT64(convert[i]); } /*************************************************************************** PROCESSING HELPERS ***************************************************************************/ /*------------------------------------------------- compute_size_and_offsets - compute the total size and offsets of each individual item -------------------------------------------------*/ static int compute_size_and_offsets(void) { ss_entry *entry; int total_size; /* start with the header size */ total_size = 0x18; /* iterate over entries */ for (entry = ss_registry; entry; entry = entry->next) { /* note the offset and accumulate a total size */ entry->offset = total_size; total_size += entry->typesize * entry->typecount; } /* return the total size */ return total_size; } /*------------------------------------------------- get_signature - compute the signature, which is a CRC over the structure of the data -------------------------------------------------*/ static UINT32 get_signature(void) { ss_entry *entry; UINT32 crc = 0; /* iterate over entries */ for (entry = ss_registry; entry; entry = entry->next) { UINT32 temp[2]; /* add the entry name to the CRC */ crc = crc32(crc, (UINT8 *)astring_c(entry->name), astring_len(entry->name)); /* add the type and size to the CRC */ temp[0] = LITTLE_ENDIANIZE_INT32(entry->typecount); temp[1] = LITTLE_ENDIANIZE_INT32(entry->typesize); crc = crc32(crc, (UINT8 *)&temp[0], 8); } return crc; } /*************************************************************************** STATE FILE VALIDATION ***************************************************************************/ /*------------------------------------------------- validate_header - validate the data in the header -------------------------------------------------*/ static int validate_header(const UINT8 *header, const char *gamename, UINT32 signature, void (CLIB_DECL *errormsg)(const char *fmt, ...), const char *error_prefix) { /* check magic number */ if (memcmp(header, ss_magic_num, 8)) { if (errormsg) errormsg("%sThis is not a " APPNAME " save file", error_prefix); return -1; } /* check save state version */ if (header[8] != SAVE_VERSION) { if (errormsg) errormsg("%sWrong version in save file (%d, 1 expected)", error_prefix, header[8]); return -1; } /* check gamename, if we were asked to */ if (gamename && strcmp(gamename, (const char *)&header[10])) { if (errormsg) errormsg("%s'%s' is not a valid savestate file for game '%s'.", error_prefix, gamename); return -1; } /* check signature, if we were asked to */ if (signature) { UINT32 rawsig = *(UINT32 *)&header[0x14]; UINT32 filesig = LITTLE_ENDIANIZE_INT32(rawsig); if (signature != filesig) { if (errormsg) errormsg("%sIncompatible save file (signature %08x, expected %08x)", error_prefix, filesig, signature); return -1; } } return 0; } /*------------------------------------------------- state_save_check_file - check if a file is a valid save state -------------------------------------------------*/ int state_save_check_file(mame_file *file, const char *gamename, int validate_signature, void (CLIB_DECL *errormsg)(const char *fmt, ...)) { UINT32 signature = 0; UINT8 header[0x18]; /* if we want to validate the signature, compute it */ if (validate_signature) signature = get_signature(); /* seek to the beginning and read the header */ mame_fseek(file, 0, SEEK_SET); if (mame_fread(file, header, sizeof(header)) != sizeof(header)) { if (errormsg) errormsg("Could not read " APPNAME " save file header"); return -1; } /* let the generic header check work out the rest */ return validate_header(header, gamename, signature, errormsg, ""); } /*************************************************************************** SAVE STATE PROCESSING ***************************************************************************/ /*------------------------------------------------- state_save_save_begin - begin the process of saving -------------------------------------------------*/ int state_save_save_begin(mame_file *file) { /* if we have illegal registrations, return an error */ if (ss_illegal_regs > 0) return 1; LOG(("Beginning save\n")); ss_dump_file = file; /* compute the total dump size and the offsets of each element */ ss_dump_size = compute_size_and_offsets(); LOG((" total size %u\n", ss_dump_size)); /* allocate memory for the array */ ss_dump_array = malloc_or_die(ss_dump_size); return 0; } /*------------------------------------------------- state_save_save_continue - save within the current tag -------------------------------------------------*/ void state_save_save_continue(void) { ss_entry *entry; ss_func *func; int count = 0; LOG(("Saving tag %d\n", ss_current_tag)); /* call the pre-save functions */ LOG((" calling pre-save functions\n")); /* iterate over the list of functions */ for (func = ss_prefunc_reg; func; func = func->next) if (func->tag == ss_current_tag) { count++; (*func->func.presave)(Machine, func->param); } LOG((" %d functions called\n", count)); /* then copy in all the data */ LOG((" copying data\n")); /* iterate over entries with matching tags */ for (entry = ss_registry; entry; entry = entry->next) if (entry->tag == ss_current_tag) { memcpy(ss_dump_array + entry->offset, entry->data, entry->typesize * entry->typecount); LOG((" %s: %x..%x\n", astring_c(entry->name), entry->offset, entry->offset + entry->typesize * entry->typecount - 1)); } } /*------------------------------------------------- state_save_save_finish - finish saving the file by writing the header and closing -------------------------------------------------*/ void state_save_save_finish(void) { UINT32 signature; UINT8 flags = 0; LOG(("Finishing save\n")); /* compute the flags */ #ifndef LSB_FIRST flags |= SS_MSB_FIRST; #endif /* build up the header */ memcpy(ss_dump_array, ss_magic_num, 8); ss_dump_array[8] = SAVE_VERSION; ss_dump_array[9] = flags; memset(ss_dump_array+0xa, 0, 10); strcpy((char *)ss_dump_array+0xa, Machine->gamedrv->name); /* copy in the signature */ signature = get_signature(); *(UINT32 *)&ss_dump_array[0x14] = LITTLE_ENDIANIZE_INT32(signature); /* write the file */ mame_fwrite(ss_dump_file, ss_dump_array, ss_dump_size); /* free memory and reset the global states */ free(ss_dump_array); ss_dump_array = NULL; ss_dump_size = 0; ss_dump_file = NULL; } /*************************************************************************** LOAD STATE PROCESSING ***************************************************************************/ /*------------------------------------------------- state_save_load_begin - begin the process of loading the state -------------------------------------------------*/ int state_save_load_begin(mame_file *file) { LOG(("Beginning load\n")); /* read the file into memory */ ss_dump_size = mame_fsize(file); ss_dump_array = malloc_or_die(ss_dump_size); ss_dump_file = file; mame_fread(ss_dump_file, ss_dump_array, ss_dump_size); /* verify the header and report an error if it doesn't match */ if (validate_header(ss_dump_array, NULL, get_signature(), popmessage, "Error: ")) { free(ss_dump_array); ss_dump_array = NULL; ss_dump_size = 0; ss_dump_file = NULL; return 1; } /* compute the total size and offset of all the entries */ compute_size_and_offsets(); return 0; } /*------------------------------------------------- state_save_load_continue - load all state in the current tag -------------------------------------------------*/ void state_save_load_continue(void) { ss_entry *entry; ss_func *func; int need_convert; int count = 0; /* first determine whether or not we need to convert the endianness of the data */ #ifdef LSB_FIRST need_convert = (ss_dump_array[9] & SS_MSB_FIRST) != 0; #else need_convert = (ss_dump_array[9] & SS_MSB_FIRST) == 0; #endif LOG(("Loading tag %d\n", ss_current_tag)); LOG((" copying data\n")); /* iterate over entries with matching tags */ for (entry = ss_registry; entry; entry = entry->next) if (entry->tag == ss_current_tag) { memcpy(entry->data, ss_dump_array + entry->offset, entry->typesize * entry->typecount); if (need_convert && ss_conv[entry->typesize]) (*ss_conv[entry->typesize])(entry->data, entry->typecount); LOG((" %s: %x..%x\n", astring_c(entry->name), entry->offset, entry->offset + entry->typesize * entry->typecount - 1)); } /* call the post-load functions */ LOG((" calling post-load functions\n")); for (func = ss_postfunc_reg; func; func = func->next) if (func->tag == ss_current_tag) { count++; (*func->func.postload)(Machine, func->param); } LOG((" %d functions called\n", count)); } /*------------------------------------------------- state_save_load_finish - complete the process of loading the state -------------------------------------------------*/ void state_save_load_finish(void) { LOG(("Finishing load\n")); /* free memory and reset the global states */ free(ss_dump_array); ss_dump_array = NULL; ss_dump_size = 0; ss_dump_file = NULL; } /*************************************************************************** DEBUGGING ***************************************************************************/ /*------------------------------------------------- state_save_get_indexed_item - return an item with the given index -------------------------------------------------*/ const char *state_save_get_indexed_item(int index, void **base, UINT32 *valsize, UINT32 *valcount) { ss_entry *ss; for (ss = ss_registry; ss != NULL; ss = ss->next) if (index-- == 0) { if (base != NULL) *base = ss->data; if (valsize != NULL) *valsize = ss->typesize; if (valcount != NULL) *valcount = ss->typecount; return astring_c(ss->name); } return NULL; } /*------------------------------------------------- state_save_combine_module_and_tag - creates a name from a given module and tag that can be used as the first agrument to the state_save_register_item family of functions -------------------------------------------------*/ void state_save_combine_module_and_tag(char *dest, const char *module, const char *tag) { astring *module_lower; astring *tag_lower; astring *combined; /* validate arguments */ assert(dest != NULL); assert(module != NULL); assert(tag != NULL); assert(strlen(module) > 0); assert(strlen(tag) > 0); /* allocate objects */ module_lower = astring_alloc(); tag_lower = astring_alloc(); combined = astring_alloc(); /* convert both arguments to lower case for case insenstive comparisson */ astring_tolower(astring_cpyc(module_lower, module)); astring_tolower(astring_cpyc(tag_lower, tag)); /* if the tag contains the module name, just use the tag as the combined name */ if (astring_find(tag_lower, 0, module_lower) >= 0) astring_cpyc(combined, tag); /* otherwise combine the module and the tag */ else astring_assemble_3(combined, module, ".", tag); /* copy the result to the destination array */ strcpy(dest, astring_c(combined)); /* free the objects */ astring_free(module_lower); astring_free(tag_lower); astring_free(combined); } /*------------------------------------------------- state_save_dump_registry - dump the registry to the logfile -------------------------------------------------*/ void state_save_dump_registry(void) { ss_entry *entry; for (entry = ss_registry; entry; entry=entry->next) LOG(("%s: %d x %d\n", astring_c(entry->name), entry->typesize, entry->typecount)); }