diff options
| author | 2015-11-08 12:56:12 +0100 | |
|---|---|---|
| committer | 2015-11-08 12:56:12 +0100 | |
| commit | 7c19aac60e12d6f5ea301bdb34d7826a01e0b06f (patch) | |
| tree | f310d86aa2c6bfc19d115307dedde4eb0cd52dad /src/emu/save.cpp | |
| parent | a57b46ae933badd7441ce1644711dbb851e2b504 (diff) | |
Rename *.c -> *.cpp in our source (nw)
Diffstat (limited to 'src/emu/save.cpp')
| -rw-r--r-- | src/emu/save.cpp | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/emu/save.cpp b/src/emu/save.cpp new file mode 100644 index 00000000000..fe20fe2a988 --- /dev/null +++ b/src/emu/save.cpp @@ -0,0 +1,468 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles +/*************************************************************************** + + save.c + + Save state management functions. + +**************************************************************************** + + 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 "coreutil.h" + + +//************************************************************************** +// DEBUGGING +//************************************************************************** + +#define VERBOSE 0 + +#define LOG(x) do { if (VERBOSE) machine().logerror x; } while (0) + + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +const int SAVE_VERSION = 2; +const int HEADER_SIZE = 32; + +// Available flags +enum +{ + SS_MSB_FIRST = 0x02 +}; + + +//************************************************************************** +// INITIALIZATION +//************************************************************************** + +//------------------------------------------------- +// save_manager - constructor +//------------------------------------------------- + +save_manager::save_manager(running_machine &machine) + : m_machine(machine), + m_reg_allowed(true), + m_illegal_regs(0) +{ +} + + +//------------------------------------------------- +// 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.c_str(); +} + + +//------------------------------------------------- +// 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!\n"); + + // 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)\n", cb->m_func.name(), func.name()); + + // allocate a new entry + m_presave_list.append(*global_alloc(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!\n"); + + // 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)\n", cb->m_func.name(), func.name()); + + // allocate a new entry + m_postload_list.append(*global_alloc(state_callback(func))); +} + + +//------------------------------------------------- +// save_memory - register an array of data in +// memory +//------------------------------------------------- + +void save_manager::save_memory(device_t *device, 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) + { + machine().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 & MACHINE_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 + std::string totalname; + if (tag != NULL) + strprintf(totalname, "%s/%s/%X/%s", module, tag, index, name); + else + strprintf(totalname, "%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.compare(totalname)>0) + break; + insert_after = entry; + + // error if we are equal + if (entry->m_name.compare(totalname)==0) + fatalerror("Duplicate save state registration entry (%s)\n", totalname.c_str()); + } + + // insert us into the list + m_entry_list.insert_after(*global_alloc(state_entry(val, totalname.c_str(), device, module, tag ? tag : "", index, 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 %s save file header",emulator_info::get_appname()); + return STATERR_READ_ERROR; + } + + // let the generic header check work out the rest + return validate_header(header, gamename, sig, errormsg, ""); +} + +//------------------------------------------------- +// dispatch_postload - invoke all registered +// postload callbacks for updates +//------------------------------------------------- + + +void save_manager::dispatch_postload() +{ + for (state_callback *func = m_postload_list.first(); func != NULL; func = func->next()) + func->m_func(); +} + +//------------------------------------------------- +// 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, NULL, "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 + dispatch_postload(); + + return STATERR_NONE; +} + +//------------------------------------------------- +// dispatch_presave - invoke all registered +// presave callbacks for updates +//------------------------------------------------- + + +void save_manager::dispatch_presave() +{ + for (state_callback *func = m_presave_list.first(); func != NULL; func = func->next()) + func->m_func(); +} + +//------------------------------------------------- +// 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], emulator_info::get_state_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 + dispatch_presave(); + + // 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 = core_crc32(crc, (UINT8 *)entry->m_name.c_str(), entry->m_name.length()); + + // 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 = core_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.c_str(), 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, emulator_info::get_state_magic_num(), 8)) + { + if (errormsg != NULL) + (*errormsg)("%sThis is not a %s save file", error_prefix,emulator_info::get_appname()); + 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 +//------------------------------------------------- + +state_entry::state_entry(void *data, const char *name, device_t *device, const char *module, const char *tag, int index, UINT8 size, UINT32 count) + : m_next(NULL), + m_data(data), + m_name(name), + m_device(device), + m_module(module), + m_tag(tag), + m_index(index), + m_typesize(size), + m_typecount(count), + m_offset(0) +{ +} + + +//------------------------------------------------- +// flip_data - reverse the endianness of a +// block of data +//------------------------------------------------- + +void 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; + } +} |
