summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/save.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/save.c')
-rw-r--r--trunk/src/emu/save.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/trunk/src/emu/save.c b/trunk/src/emu/save.c
new file mode 100644
index 00000000000..32bc56f1d3a
--- /dev/null
+++ b/trunk/src/emu/save.c
@@ -0,0 +1,484 @@
+/***************************************************************************
+
+ save.c
+
+ Save state management functions.
+
+****************************************************************************
+
+ Copyright Aaron Giles
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name 'MAME' nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+****************************************************************************
+
+ Save state file format:
+
+ 00..07 'MAMESAVE'
+ 08 Format version (this is format 2)
+ 09 Flags
+ 0A..1B Game name padded with \0
+ 1C..1F Signature
+ 20..end Save game data (compressed)
+
+ Data is always written as native-endian.
+ Data is converted from the endiannness it was written upon load.
+
+***************************************************************************/
+
+#include "emu.h"
+
+#include <zlib.h>
+
+
+//**************************************************************************
+// DEBUGGING
+//**************************************************************************
+
+#define VERBOSE 0
+
+#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+const int SAVE_VERSION = 2;
+const int HEADER_SIZE = 32;
+
+// Available flags
+enum
+{
+ SS_MSB_FIRST = 0x02
+};
+
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+const char save_manager::s_magic_num[8] = { STATE_MAGIC_NUM };
+
+//**************************************************************************
+// INITIALIZATION
+//**************************************************************************
+
+//-------------------------------------------------
+// save_manager - constructor
+//-------------------------------------------------
+
+save_manager::save_manager(running_machine &machine)
+ : m_machine(machine),
+ m_reg_allowed(true),
+ m_illegal_regs(0),
+ m_entry_list(machine.respool()),
+ m_presave_list(machine.respool()),
+ m_postload_list(machine.respool())
+{
+}
+
+
+//-------------------------------------------------
+// allow_registration - allow/disallow
+// registrations to happen
+//-------------------------------------------------
+
+void save_manager::allow_registration(bool allowed)
+{
+ // allow/deny registration
+ m_reg_allowed = allowed;
+ if (!allowed)
+ dump_registry();
+}
+
+
+//-------------------------------------------------
+// indexed_item - return an item with the given
+// index
+//-------------------------------------------------
+
+const char *save_manager::indexed_item(int index, void *&base, UINT32 &valsize, UINT32 &valcount) const
+{
+ state_entry *entry = m_entry_list.find(index);
+ if (entry == NULL)
+ return NULL;
+
+ base = entry->m_data;
+ valsize = entry->m_typesize;
+ valcount = entry->m_typecount;
+
+ return entry->m_name;
+}
+
+
+//-------------------------------------------------
+// register_presave - register a pre-save
+// function callback
+//-------------------------------------------------
+
+void save_manager::register_presave(save_prepost_delegate func)
+{
+ // check for invalid timing
+ if (!m_reg_allowed)
+ fatalerror("Attempt to register callback function after state registration is closed!");
+
+ // scan for duplicates and push through to the end
+ for (state_callback *cb = m_presave_list.first(); cb != NULL; cb = cb->next())
+ if (cb->m_func == func)
+ fatalerror("Duplicate save state function (%s/%s)", cb->m_func.name(), func.name());
+
+ // allocate a new entry
+ m_presave_list.append(*auto_alloc(machine(), state_callback(func)));
+}
+
+
+//-------------------------------------------------
+// state_save_register_postload -
+// register a post-load function callback
+//-------------------------------------------------
+
+void save_manager::register_postload(save_prepost_delegate func)
+{
+ // check for invalid timing
+ if (!m_reg_allowed)
+ fatalerror("Attempt to register callback function after state registration is closed!");
+
+ // scan for duplicates and push through to the end
+ for (state_callback *cb = m_postload_list.first(); cb != NULL; cb = cb->next())
+ if (cb->m_func == func)
+ fatalerror("Duplicate save state function (%s/%s)", cb->m_func.name(), func.name());
+
+ // allocate a new entry
+ m_postload_list.append(*auto_alloc(machine(), state_callback(func)));
+}
+
+
+//-------------------------------------------------
+// save_memory - register an array of data in
+// memory
+//-------------------------------------------------
+
+void save_manager::save_memory(const char *module, const char *tag, UINT32 index, const char *name, void *val, UINT32 valsize, UINT32 valcount)
+{
+ assert(valsize == 1 || valsize == 2 || valsize == 4 || valsize == 8);
+
+ // check for invalid timing
+ if (!m_reg_allowed)
+ {
+ logerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name);
+ if (machine().system().flags & GAME_SUPPORTS_SAVE)
+ fatalerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name);
+ m_illegal_regs++;
+ return;
+ }
+
+ // create the full name
+ astring totalname;
+ if (tag != NULL)
+ totalname.printf("%s/%s/%X/%s", module, tag, index, name);
+ else
+ totalname.printf("%s/%X/%s", module, index, name);
+
+ // look for duplicates and an entry to insert in front of
+ state_entry *insert_after = NULL;
+ for (state_entry *entry = m_entry_list.first(); entry != NULL; entry = entry->next())
+ {
+ // stop when we find an entry whose name is after ours
+ if (entry->m_name > totalname)
+ break;
+ insert_after = entry;
+
+ // error if we are equal
+ if (entry->m_name == totalname)
+ fatalerror("Duplicate save state registration entry (%s)", totalname.cstr());
+ }
+
+ // insert us into the list
+ m_entry_list.insert_after(*auto_alloc(machine(), state_entry(val, totalname, valsize, valcount)), insert_after);
+}
+
+
+//-------------------------------------------------
+// check_file - check if a file is a valid save
+// state
+//-------------------------------------------------
+
+save_error save_manager::check_file(running_machine &machine, emu_file &file, const char *gamename, void (CLIB_DECL *errormsg)(const char *fmt, ...))
+{
+ // if we want to validate the signature, compute it
+ UINT32 sig = 0;
+ sig = machine.save().signature();
+
+ // seek to the beginning and read the header
+ file.compress(FCOMPRESS_NONE);
+ file.seek(0, SEEK_SET);
+ UINT8 header[HEADER_SIZE];
+ if (file.read(header, sizeof(header)) != sizeof(header))
+ {
+ if (errormsg != NULL)
+ (*errormsg)("Could not read " APPNAME " save file header");
+ return STATERR_READ_ERROR;
+ }
+
+ // let the generic header check work out the rest
+ return validate_header(header, gamename, sig, errormsg, "");
+}
+
+
+//-------------------------------------------------
+// read_file - read the data from a file
+//-------------------------------------------------
+
+save_error save_manager::read_file(emu_file &file)
+{
+ // if we have illegal registrations, return an error
+ if (m_illegal_regs > 0)
+ return STATERR_ILLEGAL_REGISTRATIONS;
+
+ // read the header and turn on compression for the rest of the file
+ file.compress(FCOMPRESS_NONE);
+ file.seek(0, SEEK_SET);
+ UINT8 header[HEADER_SIZE];
+ if (file.read(header, sizeof(header)) != sizeof(header))
+ return STATERR_READ_ERROR;
+ file.compress(FCOMPRESS_MEDIUM);
+
+ // verify the header and report an error if it doesn't match
+ UINT32 sig = signature();
+ if (validate_header(header, machine().system().name, sig, popmessage, "Error: ") != STATERR_NONE)
+ return STATERR_INVALID_HEADER;
+
+ // determine whether or not to flip the data when done
+ bool flip = NATIVE_ENDIAN_VALUE_LE_BE((header[9] & SS_MSB_FIRST) != 0, (header[9] & SS_MSB_FIRST) == 0);
+
+ // read all the data, flipping if necessary
+ for (state_entry *entry = m_entry_list.first(); entry != NULL; entry = entry->next())
+ {
+ UINT32 totalsize = entry->m_typesize * entry->m_typecount;
+ if (file.read(entry->m_data, totalsize) != totalsize)
+ return STATERR_READ_ERROR;
+
+ // handle flipping
+ if (flip)
+ entry->flip_data();
+ }
+
+ // call the post-load functions
+ for (state_callback *func = m_postload_list.first(); func != NULL; func = func->next())
+ func->m_func();
+
+ return STATERR_NONE;
+}
+
+
+//-------------------------------------------------
+// write_file - writes the data to a file
+//-------------------------------------------------
+
+save_error save_manager::write_file(emu_file &file)
+{
+ // if we have illegal registrations, return an error
+ if (m_illegal_regs > 0)
+ return STATERR_ILLEGAL_REGISTRATIONS;
+
+ // generate the header
+ UINT8 header[HEADER_SIZE];
+ memcpy(&header[0], s_magic_num, 8);
+ header[8] = SAVE_VERSION;
+ header[9] = NATIVE_ENDIAN_VALUE_LE_BE(0, SS_MSB_FIRST);
+ strncpy((char *)&header[0x0a], machine().system().name, 0x1c - 0x0a);
+ UINT32 sig = signature();
+ *(UINT32 *)&header[0x1c] = LITTLE_ENDIANIZE_INT32(sig);
+
+ // write the header and turn on compression for the rest of the file
+ file.compress(FCOMPRESS_NONE);
+ file.seek(0, SEEK_SET);
+ if (file.write(header, sizeof(header)) != sizeof(header))
+ return STATERR_WRITE_ERROR;
+ file.compress(FCOMPRESS_MEDIUM);
+
+ // call the pre-save functions
+ for (state_callback *func = m_presave_list.first(); func != NULL; func = func->next())
+ func->m_func();
+
+ // then write all the data
+ for (state_entry *entry = m_entry_list.first(); entry != NULL; entry = entry->next())
+ {
+ UINT32 totalsize = entry->m_typesize * entry->m_typecount;
+ if (file.write(entry->m_data, totalsize) != totalsize)
+ return STATERR_WRITE_ERROR;
+ }
+ return STATERR_NONE;
+}
+
+
+//-------------------------------------------------
+// signature - compute the signature, which
+// is a CRC over the structure of the data
+//-------------------------------------------------
+
+UINT32 save_manager::signature() const
+{
+ // iterate over entries
+ UINT32 crc = 0;
+ for (state_entry *entry = m_entry_list.first(); entry != NULL; entry = entry->next())
+ {
+ // add the entry name to the CRC
+ crc = crc32(crc, (UINT8 *)entry->m_name.cstr(), entry->m_name.len());
+
+ // add the type and size to the CRC
+ UINT32 temp[2];
+ temp[0] = LITTLE_ENDIANIZE_INT32(entry->m_typecount);
+ temp[1] = LITTLE_ENDIANIZE_INT32(entry->m_typesize);
+ crc = crc32(crc, (UINT8 *)&temp[0], sizeof(temp));
+ }
+ return crc;
+}
+
+
+//-------------------------------------------------
+// dump_registry - dump the registry to the
+// logfile
+//-------------------------------------------------
+
+void save_manager::dump_registry() const
+{
+ for (state_entry *entry = m_entry_list.first(); entry != NULL; entry = entry->next())
+ LOG(("%s: %d x %d\n", entry->m_name.cstr(), entry->m_typesize, entry->m_typecount));
+}
+
+
+//-------------------------------------------------
+// validate_header - validate the data in the
+// header
+//-------------------------------------------------
+
+save_error save_manager::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, s_magic_num, 8))
+ {
+ if (errormsg != NULL)
+ (*errormsg)("%sThis is not a " APPNAME " save file", error_prefix);
+ return STATERR_INVALID_HEADER;
+ }
+
+ // check save state version
+ if (header[8] != SAVE_VERSION)
+ {
+ if (errormsg != NULL)
+ (*errormsg)("%sWrong version in save file (version %d, expected %d)", error_prefix, header[8], SAVE_VERSION);
+ return STATERR_INVALID_HEADER;
+ }
+
+ // check gamename, if we were asked to
+ if (gamename != NULL && strncmp(gamename, (const char *)&header[0x0a], 0x1c - 0x0a))
+ {
+ if (errormsg != NULL)
+ (*errormsg)("%s'File is not a valid savestate file for game '%s'.", error_prefix, gamename);
+ return STATERR_INVALID_HEADER;
+ }
+
+ // check signature, if we were asked to
+ if (signature != 0)
+ {
+ UINT32 rawsig = *(UINT32 *)&header[0x1c];
+ if (signature != LITTLE_ENDIANIZE_INT32(rawsig))
+ {
+ if (errormsg != NULL)
+ (*errormsg)("%sIncompatible save file (signature %08x, expected %08x)", error_prefix, LITTLE_ENDIANIZE_INT32(rawsig), signature);
+ return STATERR_INVALID_HEADER;
+ }
+ }
+ return STATERR_NONE;
+}
+
+
+//-------------------------------------------------
+// state_callback - constructor
+//-------------------------------------------------
+
+save_manager::state_callback::state_callback(save_prepost_delegate callback)
+ : m_next(NULL),
+ m_func(callback)
+{
+}
+
+
+//-------------------------------------------------
+// state_entry - constructor
+//-------------------------------------------------
+
+save_manager::state_entry::state_entry(void *data, const char *name, UINT8 size, UINT32 count)
+ : m_next(NULL),
+ m_data(data),
+ m_name(name),
+ m_typesize(size),
+ m_typecount(count),
+ m_offset(0)
+{
+}
+
+
+//-------------------------------------------------
+// flip_data - reverse the endianness of a
+// block of data
+//-------------------------------------------------
+
+void save_manager::state_entry::flip_data()
+{
+ UINT16 *data16;
+ UINT32 *data32;
+ UINT64 *data64;
+ int count;
+
+ switch (m_typesize)
+ {
+ case 2:
+ data16 = (UINT16 *)m_data;
+ for (count = 0; count < m_typecount; count++)
+ data16[count] = FLIPENDIAN_INT16(data16[count]);
+ break;
+
+ case 4:
+ data32 = (UINT32 *)m_data;
+ for (count = 0; count < m_typecount; count++)
+ data32[count] = FLIPENDIAN_INT32(data32[count]);
+ break;
+
+ case 8:
+ data64 = (UINT64 *)m_data;
+ for (count = 0; count < m_typecount; count++)
+ data64[count] = FLIPENDIAN_INT64(data64[count]);
+ break;
+ }
+}