mirror of
https://github.com/holub/mame
synced 2025-05-05 22:04:43 +03:00

SHA-1: 1f90ceab07
* tags are now strings (nw)
fix start project for custom builds in Visual Studio (nw)
469 lines
14 KiB
C++
469 lines
14 KiB
C++
// 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 == nullptr)
|
|
return nullptr;
|
|
|
|
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 != nullptr; 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 != nullptr; 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 != nullptr)
|
|
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 = nullptr;
|
|
for (state_entry *entry = m_entry_list.first(); entry != nullptr; 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;
|
|
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 != nullptr)
|
|
(*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 != nullptr; 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, nullptr, "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 != nullptr; 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 != nullptr; 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 != nullptr; 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 != nullptr; 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 != nullptr; 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 != nullptr)
|
|
(*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 != nullptr)
|
|
(*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 != nullptr && strncmp(gamename, (const char *)&header[0x0a], 0x1c - 0x0a))
|
|
{
|
|
if (errormsg != nullptr)
|
|
(*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 != nullptr)
|
|
(*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(nullptr),
|
|
m_func(std::move(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(nullptr),
|
|
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;
|
|
}
|
|
}
|