mame/src/emu/save.cpp
Miodrag Milanovic 4e8e3066f8 reverting:
SHA-1: 1f90ceab07

* tags are now strings (nw)
fix start project for custom builds in Visual Studio (nw)
2016-01-20 21:42:13 +01:00

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;
}
}