Decoupled RPK logic from TI-99 cartridge code. (#7880)

Decoupled support for RPK (RomPacK cartridge images) from TI-99 code, enabling the logic to be leveraged by other drivers.
This commit is contained in:
npwoods 2021-09-05 13:48:30 -04:00 committed by GitHub
parent fbd121cf43
commit 5e02ff231e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 719 additions and 408 deletions

View File

@ -69,6 +69,8 @@ project "formats"
MAME_DIR .. "src/lib/formats/dfi_dsk.cpp",
MAME_DIR .. "src/lib/formats/dfi_dsk.h",
MAME_DIR .. "src/lib/formats/fdi_dsk.cpp",
MAME_DIR .. "src/lib/formats/rpk.cpp",
MAME_DIR .. "src/lib/formats/rpk.h",
MAME_DIR .. "src/lib/formats/fsmgr.h",
MAME_DIR .. "src/lib/formats/fsmgr.cpp",

View File

@ -16,10 +16,7 @@
#include "cartridges.h"
#include "corestr.h"
#include "ioprocs.h"
#include "unzip.h"
#include "xmlfile.h"
#include "formats/rpk.h"
#define LOG_WARN (1U<<1) // Warnings
#define LOG_CONFIG (1U<<2) // Configuration
@ -63,21 +60,21 @@ enum
PCB_PAGED7
};
static const pcb_type pcbdefs[] =
static char const *const pcbdefs[] =
{
{ PCB_STANDARD, "standard" },
{ PCB_PAGED12K, "paged12k" },
{ PCB_PAGED16K, "paged" },
{ PCB_MINIMEM, "minimem" },
{ PCB_SUPER, "super" },
{ PCB_MBX, "mbx" },
{ PCB_PAGED379I, "paged379i" },
{ PCB_PAGED378, "paged378" },
{ PCB_PAGED377, "paged377" },
{ PCB_PAGEDCRU, "pagedcru" },
{ PCB_GROMEMU, "gromemu" },
{ PCB_PAGED7, "paged7" },
{ 0, nullptr}
"standard", // PCB_STANDARD
"paged12k", // PCB_PAGED12K
"paged", // PCB_PAGED16K
"minimem", // PCB_MINIMEM
"super", // PCB_SUPER
"mbx", // PCB_MBX
"paged379i", // PCB_PAGED379I
"paged378", // PCB_PAGED378
"paged377", // PCB_PAGED377
"pagedcru", // PCB_PAGEDCRU
"gromemu", // PCB_GROMEMU
"paged7", // PCB_PAGED7
nullptr
};
static const pcb_type sw_pcbdefs[] =
@ -99,8 +96,7 @@ ti99_cartridge_device::ti99_cartridge_device(const machine_config &mconfig, cons
m_pcbtype(0),
m_slot(0),
m_pcb(nullptr),
m_connector(nullptr),
m_rpk(nullptr)
m_connector(nullptr)
{
}
@ -257,23 +253,19 @@ image_init_result ti99_cartridge_device::call_load()
i++;
} while (sw_pcbdefs[i].id != 0);
LOGMASKED(LOG_CONFIG, "Cartridge type is %s (%d)\n", pcb, m_pcbtype);
m_rpk = nullptr;
m_rpk.reset();
}
else
{
auto reader = std::make_unique<rpk_reader>(pcbdefs);
try
std::error_condition err = rpk_open(machine().options(), util::core_file_read(image_core_file()), machine().system().name, m_rpk);
if (err)
{
m_rpk = reader->open(machine().options(), image_core_file(), machine().system().name);
m_pcbtype = m_rpk->get_type();
}
catch (rpk_exception& err)
{
LOGMASKED(LOG_WARN, "Failed to load cartridge '%s': %s\n", basename(), err.to_string().c_str());
m_rpk = nullptr;
LOGMASKED(LOG_WARN, "Failed to load cartridge '%s': %s\n", basename(), err.message().c_str());
m_rpk.reset();
m_err = image_error::INVALIDIMAGE;
return image_init_result::FAIL;
}
m_pcbtype = m_rpk->get_type();
}
switch (m_pcbtype)
@ -342,7 +334,7 @@ void ti99_cartridge_device::call_unload()
if (m_rpk != nullptr)
{
m_rpk->close(); // will write NVRAM contents
delete m_rpk;
m_rpk.reset();
}
else
{
@ -1451,47 +1443,6 @@ void ti99_gromemu_cartridge::gromemuwrite(offs_t offset, uint8_t data)
RPK loader
RPK format support
A RPK file ("rompack") contains a collection of dump files and a layout
file that defines the kind of circuit board (PCB) used in the cartridge
and the mapping of dumps to sockets on the board.
Example:
<?xml version="1.0" encoding="utf-8"?>
<romset>
<resources>
<rom id="gromimage" file="ed-assmg.bin" />
</resources>
<configuration>
<pcb type="standard">
<socket id="grom_socket" uses="gromimage"/>
</pcb>
</configuration>
</romset>
DTD:
<!ELEMENT romset (resources, configuration)>
<!ELEMENT resources (rom|ram)+>
<!ELEMENT rom EMPTY>
<!ELEMENT ram EMPTY>
<!ELEMENT configuration (pcb)>
<!ELEMENT pcb (socket)+>
<!ELEMENT socket EMPTY>
<!ATTLIST romset version CDATA #IMPLIED>
<!ATTLIST rom id ID #REQUIRED
<!ATTLIST rom file CDATA #REQUIRED>
<!ATTLIST rom crc CDATA #IMPLIED>
<!ATTLIST rom sha1 CDATA #IMPLIED>
<!ATTLIST ram id ID #REQUIRED>
<!ATTLIST ram type (volatile|persistent) #IMPLIED>
<!ATTLIST ram store (internal|external) #IMPLIED>
<!ATTLIST ram file CDATA #IMPLIED>
<!ATTLIST ram length CDATA #REQUIRED>
<!ATTLIST pcb type CDATA #REQUIRED>
<!ATTLIST socket id ID #REQUIRED>
<!ATTLIST socket uses IDREF #REQUIRED>
****************************************************************************/
#undef LOG_OUTPUT_FUNC
@ -1535,7 +1486,7 @@ int ti99_cartridge_device::rpk::get_resource_length(const char *socket_name)
return socket->second->get_content_length();
}
void ti99_cartridge_device::rpk::add_socket(const char* id, std::unique_ptr<rpk_socket> newsock)
void ti99_cartridge_device::rpk::add_socket(const char* id, std::unique_ptr<ti99_rpk_socket> &&newsock)
{
m_sockets.emplace(id, std::move(newsock));
}
@ -1571,178 +1522,68 @@ void ti99_cartridge_device::rpk::close()
not a network socket)
***************************************************************/
ti99_cartridge_device::rpk_socket::rpk_socket(const char* id, int length, std::unique_ptr<uint8_t []> &&contents, std::string &&pathname)
ti99_cartridge_device::ti99_rpk_socket::ti99_rpk_socket(const char* id, int length, std::vector<uint8_t> &&contents, std::string &&pathname)
: m_id(id), m_length(length), m_contents(std::move(contents)), m_pathname(std::move(pathname))
{
}
ti99_cartridge_device::rpk_socket::rpk_socket(const char* id, int length, std::unique_ptr<uint8_t []> &&contents)
: rpk_socket(id, length, std::move(contents), "")
ti99_cartridge_device::ti99_rpk_socket::ti99_rpk_socket(const char* id, int length, std::vector<uint8_t> &&contents)
: ti99_rpk_socket(id, length, std::move(contents), "")
{
}
/*
Locate a file in the ZIP container
*/
int ti99_cartridge_device::rpk_reader::find_file(util::archive_file &zip, const char *filename, uint32_t crc)
{
for (int header = zip.first_file(); header >= 0; header = zip.next_file())
{
// Ignore directories
if (!zip.current_is_directory())
{
// We don't check for CRC == 0.
if (crc != 0)
{
// if the CRC and name both match, we're good
// if the CRC matches and the name doesn't, we're still good
if (zip.current_crc() == crc)
return header;
}
else
{
if (core_stricmp(zip.current_name().c_str(), filename) == 0)
{
return header;
}
}
}
}
return -1;
}
/*
Load a rom resource and put it in a pcb socket instance.
*/
std::unique_ptr<ti99_cartridge_device::rpk_socket> ti99_cartridge_device::rpk_reader::load_rom_resource(util::archive_file &zip, util::xml::data_node const* rom_resource_node, const char* socketname)
std::error_condition ti99_cartridge_device::rpk_load_rom_resource(const rpk_socket &socket, std::unique_ptr<ti99_rpk_socket> &result)
{
// find the file attribute (required)
std::string const *const file = rom_resource_node->get_attribute_string_ptr("file");
if (file == nullptr) throw rpk_exception(RPK_INVALID_LAYOUT, "<rom> must have a 'file' attribute");
LOGMASKED(LOG_RPK, "[RPK handler] Loading ROM contents for socket '%s' from file %s\n", socket.id(), socket.filename());
LOGMASKED(LOG_RPK, "[RPK handler] Loading ROM contents for socket '%s' from file %s\n", socketname, *file);
// check for crc
std::string const *const crcstr = rom_resource_node->get_attribute_string_ptr("crc");
int header;
if (crcstr==nullptr)
{
// no CRC, just find the file in the RPK
header = find_file(zip, file->c_str(), 0);
}
else
{
uint32_t crc = strtoul(crcstr->c_str(), nullptr, 16);
header = find_file(zip, file->c_str(), crc);
}
if (header < 0) throw rpk_exception(RPK_INVALID_FILE_REF, "File not found or CRC check failed");
int length = zip.current_uncompressed_length();
// Allocate storage
std::unique_ptr<uint8_t []> contents;
try { contents = make_unique_clear<uint8_t []>(length); }
catch (std::bad_alloc const &) { throw rpk_exception(RPK_OUT_OF_MEMORY); }
// and unzip file from the zip file
std::error_condition const ziperr = zip.decompress(contents.get(), length);
if (ziperr)
{
if (ziperr == util::archive_file::error::UNSUPPORTED) throw rpk_exception(RPK_ZIP_UNSUPPORTED);
else throw rpk_exception(RPK_ZIP_ERROR);
}
// check for sha1
std::string const *const sha1 = rom_resource_node->get_attribute_string_ptr("sha1");
if (sha1 != nullptr)
{
util::hash_collection actual_hashes;
actual_hashes.compute(contents.get(), length, util::hash_collection::HASH_TYPES_CRC_SHA1);
util::hash_collection expected_hashes;
expected_hashes.add_from_string(util::hash_collection::HASH_SHA1, *sha1);
if (actual_hashes != expected_hashes) throw rpk_exception(RPK_INVALID_FILE_REF, "SHA1 check failed");
}
std::vector<uint8_t> contents;
std::error_condition err = socket.read_file(contents);
if (err)
return err;
// Create a socket instance
return std::make_unique<rpk_socket>(socketname, length, std::move(contents));
result = std::make_unique<ti99_rpk_socket>(socket.id().c_str(), contents.size(), std::move(contents));
return std::error_condition();
}
/*
Load a ram resource and put it in a pcb socket instance.
*/
std::unique_ptr<ti99_cartridge_device::rpk_socket> ti99_cartridge_device::rpk_reader::load_ram_resource(emu_options &options, util::xml::data_node const* ram_resource_node, const char* socketname, const char* system_name)
std::unique_ptr<ti99_cartridge_device::ti99_rpk_socket> ti99_cartridge_device::rpk_load_ram_resource(emu_options &options, const rpk_socket &socket, const char *system_name)
{
// find the length attribute
std::string const *const length_string = ram_resource_node->get_attribute_string_ptr("length");
if (length_string == nullptr) throw rpk_exception(RPK_MISSING_RAM_LENGTH);
// parse it
unsigned int length;
char suffix = '\0';
sscanf(length_string->c_str(), "%u%c", &length, &suffix);
switch(tolower(suffix))
{
case 'k': // kilobytes
length *= 1024;
break;
case 'm':
/* megabytes */
length *= 1024*1024;
break;
case '\0':
break;
default: // failed
throw rpk_exception(RPK_INVALID_RAM_SPEC);
}
// Allocate memory for this resource
std::unique_ptr<uint8_t []> contents;
try { contents = make_unique_clear<uint8_t []>(length); }
catch (std::bad_alloc const &) { throw rpk_exception(RPK_OUT_OF_MEMORY); }
std::vector<uint8_t> contents;
contents.resize(socket.length());
std::fill(contents.begin(), contents.end(), 0);
LOGMASKED(LOG_RPK, "[RPK handler] Allocating RAM buffer (%d bytes) for socket '%s'\n", length, socketname);
LOGMASKED(LOG_RPK, "[RPK handler] Allocating RAM buffer (%d bytes) for socket '%s'\n", socket.length(), socket.id());
// That's it for pure RAM. Now check whether the RAM is "persistent", i.e. NVRAM.
// In that case we must load it from the NVRAM directory.
// The file name is given in the RPK file; the subdirectory is the system name.
std::string const *const ram_type = ram_resource_node->get_attribute_string_ptr("type");
std::string ram_pname;
if (ram_type != nullptr)
if (socket.type() == rpk_socket::socket_type::PERSISTENT_RAM)
{
if (*ram_type == "persistent")
{
// Get the file name (required if persistent)
std::string const *const ram_filename = ram_resource_node->get_attribute_string_ptr("file");
if (ram_filename==nullptr)
throw rpk_exception(RPK_INVALID_RAM_SPEC, "<ram type='persistent'> must have a 'file' attribute");
ram_pname = std::string(system_name).append(PATH_SEPARATOR).append(socket.filename());
// load, and fill rest with 00
LOGMASKED(LOG_RPK, "[RPK handler] Loading NVRAM contents from '%s'\n", ram_pname);
ram_pname = std::string(system_name).append(PATH_SEPARATOR).append(*ram_filename);
// load, and fill rest with 00
LOGMASKED(LOG_RPK, "[RPK handler] Loading NVRAM contents from '%s'\n", ram_pname.c_str());
// try to open the battery file and read it if possible
emu_file file(options.nvram_directory(), OPEN_FLAG_READ);
std::error_condition const filerr = file.open(ram_pname);
int bytes_read = 0;
if (!filerr)
bytes_read = file.read(&contents[0], contents.size());
// Load the NVRAM contents
if (!contents || (length <= 0))
throw emu_fatalerror("ti99_cartridge_device::rpk_reader::load_ram_resource: Buffer is null or length is 0");
// try to open the battery file and read it if possible
emu_file file(options.nvram_directory(), OPEN_FLAG_READ);
std::error_condition const filerr = file.open(ram_pname);
int bytes_read = 0;
if (!filerr)
bytes_read = file.read(contents.get(), length);
// fill remaining bytes (if necessary)
std::fill_n(&contents[bytes_read], length - bytes_read, 0x00);
}
// fill remaining bytes (if necessary)
std::fill_n(&contents[bytes_read], contents.size() - bytes_read, 0x00);
}
// Create a socket instance
return std::make_unique<rpk_socket>(socketname, length, std::move(contents), std::move(ram_pname));
return std::make_unique<ti99_rpk_socket>(socket.id().c_str(), socket.length(), std::move(contents), std::move(ram_pname));
}
/*-------------------------------------------------
@ -1751,123 +1592,47 @@ std::unique_ptr<ti99_cartridge_device::rpk_socket> ti99_cartridge_device::rpk_re
system_name - name of the driver (also just for NVRAM handling)
-------------------------------------------------*/
ti99_cartridge_device::rpk* ti99_cartridge_device::rpk_reader::open(emu_options &options, util::core_file &file, const char *system_name)
std::error_condition ti99_cartridge_device::rpk_open(emu_options &options, std::unique_ptr<util::random_read> &&stream, const char *system_name, std::unique_ptr<rpk> &result)
{
auto newrpk = new rpk(options, system_name);
std::unique_ptr<rpk> newrpk = std::make_unique<rpk>(options, system_name);
try
rpk_reader reader(pcbdefs, true);
// open the RPK
rpk_file::ptr file;
std::error_condition err = reader.read(std::move(stream), file);
if (err)
return err;
// specify the PCB
newrpk->m_type = file->pcb_type() + 1;
LOGMASKED(LOG_RPK, "[RPK handler] Cartridge says it has PCB type '%s'\n", pcbdefs[file->pcb_type()]);
for (const rpk_socket &socket : file->sockets())
{
// open the ZIP file
auto reader = util::core_file_read(file);
if (!reader) throw rpk_exception(RPK_OUT_OF_MEMORY);
std::unique_ptr<ti99_rpk_socket> ti99_socket;
std::error_condition ziperr;
util::archive_file::ptr zipfile;
ziperr = util::archive_file::open_zip(std::move(reader), zipfile);
if (ziperr) throw rpk_exception(RPK_NOT_ZIP_FORMAT);
// find the layout.xml file
if (find_file(*zipfile, "layout.xml", 0) < 0) throw rpk_exception(RPK_MISSING_LAYOUT);
// reserve space for the layout file contents (+1 for the termination)
std::vector<char> layout_text(zipfile->current_uncompressed_length() + 1);
// uncompress the layout text
ziperr = zipfile->decompress(&layout_text[0], zipfile->current_uncompressed_length());
if (ziperr)
switch (socket.type())
{
if (ziperr == util::archive_file::error::UNSUPPORTED) throw rpk_exception(RPK_ZIP_UNSUPPORTED);
else throw rpk_exception(RPK_ZIP_ERROR);
}
case rpk_socket::socket_type::ROM:
err = rpk_load_rom_resource(socket, ti99_socket);
if (err)
return err;
newrpk->add_socket(socket.id().c_str(), std::move(ti99_socket));
break;
layout_text[zipfile->current_uncompressed_length()] = '\0'; // Null-terminate
case rpk_socket::socket_type::RAM:
case rpk_socket::socket_type::PERSISTENT_RAM:
newrpk->add_socket(socket.id().c_str(), rpk_load_ram_resource(options, socket, system_name));
break;
// parse the layout text
util::xml::file::ptr const layout_xml = util::xml::file::string_read(&layout_text[0], nullptr);
if (!layout_xml) throw rpk_exception(RPK_XML_ERROR);
// Now we work within the XML tree
// romset is the root node
util::xml::data_node const *const romset_node = layout_xml->get_child("romset");
if (!romset_node) throw rpk_exception(RPK_INVALID_LAYOUT, "document element must be <romset>");
// resources is a child of romset
util::xml::data_node const *const resources_node = romset_node->get_child("resources");
if (!resources_node) throw rpk_exception(RPK_INVALID_LAYOUT, "<romset> must have a <resources> child");
// configuration is a child of romset; we're actually interested in ...
util::xml::data_node const *const configuration_node = romset_node->get_child("configuration");
if (!configuration_node) throw rpk_exception(RPK_INVALID_LAYOUT, "<romset> must have a <configuration> child");
// ... pcb, which is a child of configuration
util::xml::data_node const *const pcb_node = configuration_node->get_child("pcb");
if (!pcb_node) throw rpk_exception(RPK_INVALID_LAYOUT, "<configuration> must have a <pcb> child");
// We'll try to find the PCB type on the provided type list.
std::string const *const pcb_type = pcb_node->get_attribute_string_ptr("type");
if (!pcb_type) throw rpk_exception(RPK_INVALID_LAYOUT, "<pcb> must have a 'type' attribute");
LOGMASKED(LOG_RPK, "[RPK handler] Cartridge says it has PCB type '%s'\n", *pcb_type);
int i=0;
do
{
if (*pcb_type == m_types[i].name)
{
newrpk->m_type = m_types[i].id;
break;
}
i++;
} while (m_types[i].id != 0);
if (m_types[i].id==0) throw rpk_exception(RPK_UNKNOWN_PCB_TYPE);
// Find the sockets and load their respective resource
for (util::xml::data_node const *socket_node = pcb_node->get_first_child(); socket_node != nullptr; socket_node = socket_node->get_next_sibling())
{
if (strcmp(socket_node->get_name(), "socket")!=0) throw rpk_exception(RPK_INVALID_LAYOUT, "<pcb> element has only <socket> children");
std::string const *const id = socket_node->get_attribute_string_ptr("id");
if (!id) throw rpk_exception(RPK_INVALID_LAYOUT, "<socket> must have an 'id' attribute");
std::string const *const uses_name = socket_node->get_attribute_string_ptr("uses");
if (!uses_name) throw rpk_exception(RPK_INVALID_LAYOUT, "<socket> must have a 'uses' attribute");
bool found = false;
// Locate the resource node
for (util::xml::data_node const *resource_node = resources_node->get_first_child(); resource_node != nullptr; resource_node = resource_node->get_next_sibling())
{
std::string const *const resource_name = resource_node->get_attribute_string_ptr("id");
if (!resource_name) throw rpk_exception(RPK_INVALID_LAYOUT, "resource node must have an 'id' attribute");
if (*resource_name == *uses_name)
{
// found it
if (strcmp(resource_node->get_name(), "rom")==0)
{
newrpk->add_socket(id->c_str(), load_rom_resource(*zipfile, resource_node, id->c_str()));
}
else
{
if (strcmp(resource_node->get_name(), "ram")==0)
{
newrpk->add_socket(id->c_str(), load_ram_resource(options, resource_node, id->c_str(), system_name));
}
else throw rpk_exception(RPK_INVALID_LAYOUT, "resource node must be <rom> or <ram>");
}
found = true;
}
}
if (!found) throw rpk_exception(RPK_INVALID_RESOURCE_REF, uses_name->c_str());
default:
throw false;
}
}
catch (rpk_exception &)
{
newrpk->close();
// rethrow the exception
throw;
}
return newrpk;
result = std::move(newrpk);
return std::error_condition();
}
} // end namespace bus::ti99::gromport

View File

@ -11,6 +11,7 @@
#include "gromport.h"
#include "machine/tmc0430.h"
#include "formats/rpk.h"
#include "emuopts.h"
#include "softlist_dev.h"
@ -22,46 +23,6 @@ namespace bus::ti99::gromport {
class ti99_cartridge_pcb;
enum rpk_open_error
{
RPK_OK,
RPK_NOT_ZIP_FORMAT,
RPK_CORRUPT,
RPK_OUT_OF_MEMORY,
RPK_XML_ERROR,
RPK_INVALID_FILE_REF,
RPK_ZIP_ERROR,
RPK_ZIP_UNSUPPORTED,
RPK_MISSING_RAM_LENGTH,
RPK_INVALID_RAM_SPEC,
RPK_UNKNOWN_RESOURCE_TYPE,
RPK_INVALID_RESOURCE_REF,
RPK_INVALID_LAYOUT,
RPK_MISSING_LAYOUT,
RPK_NO_PCB_OR_RESOURCES,
RPK_UNKNOWN_PCB_TYPE
};
const char *const error_text[16] =
{
"No error",
"Not a RPK (zip) file",
"Module definition corrupt",
"Out of memory",
"XML format error",
"Invalid file reference",
"Zip file error",
"Unsupported zip version",
"Missing RAM length",
"Invalid RAM specification",
"Unknown resource type",
"Invalid resource reference",
"layout.xml not valid",
"Missing layout",
"No pcb or resource found",
"Unknown pcb type"
};
class ti99_cartridge_device : public device_t, public device_image_interface
{
public:
@ -108,50 +69,16 @@ protected:
private:
/***************** RPK support ********************
Actually deprecated, and to be removed as soon as
softlists allow for homebrew cartridges
***************************************************/
class rpk_socket;
class ti99_rpk_socket;
class rpk;
class rpk_exception
{
public:
rpk_exception(rpk_open_error value): m_err(value), m_detail(nullptr) { }
rpk_exception(rpk_open_error value, const char* detail) : m_err(value), m_detail(detail) { }
std::string to_string()
{
std::string errmsg(error_text[(int)m_err]);
if (m_detail==nullptr)
return errmsg;
return errmsg.append(": ").append(m_detail);
}
private:
rpk_open_error m_err;
const char* m_detail;
};
class rpk_reader
{
public:
rpk_reader(const pcb_type *types) : m_types(types) { }
rpk *open(emu_options &options, util::core_file &file, const char *system_name);
private:
int find_file(util::archive_file &zip, const char *filename, uint32_t crc);
std::unique_ptr<rpk_socket> load_rom_resource(util::archive_file &zip, util::xml::data_node const* rom_resource_node, const char* socketname);
std::unique_ptr<rpk_socket> load_ram_resource(emu_options &options, util::xml::data_node const* ram_resource_node, const char* socketname, const char* system_name);
const pcb_type* m_types;
};
static std::error_condition rpk_open(emu_options &options, std::unique_ptr<util::random_read> &&stream, const char *system_name, std::unique_ptr<rpk> &result);
static std::error_condition rpk_load_rom_resource(const rpk_socket &socket, std::unique_ptr<ti99_rpk_socket> &result);
static std::unique_ptr<ti99_rpk_socket> rpk_load_ram_resource(emu_options &options, const rpk_socket &socket, const char *system_name);
class rpk
{
friend class rpk_reader;
friend class ti99_cartridge_device;
public:
rpk(emu_options& options, const char* sysname);
~rpk();
@ -165,29 +92,29 @@ private:
emu_options& m_options; // need this to find the path to the nvram files
int m_type;
//const char* m_system_name; // need this to find the path to the nvram files
std::unordered_map<std::string,std::unique_ptr<rpk_socket>> m_sockets;
std::unordered_map<std::string,std::unique_ptr<ti99_rpk_socket>> m_sockets;
void add_socket(const char* id, std::unique_ptr<rpk_socket> newsock);
void add_socket(const char* id, std::unique_ptr<ti99_rpk_socket> &&newsock);
};
class rpk_socket
class ti99_rpk_socket
{
public:
rpk_socket(const char *id, int length, std::unique_ptr<uint8_t []> &&contents);
rpk_socket(const char *id, int length, std::unique_ptr<uint8_t []> &&contents, std::string &&pathname);
~rpk_socket() {}
ti99_rpk_socket(const char *id, int length, std::vector<uint8_t> &&contents);
ti99_rpk_socket(const char *id, int length, std::vector<uint8_t> &&contents, std::string &&pathname);
~ti99_rpk_socket() {}
const char* id() { return m_id; }
int get_content_length() { return m_length; }
uint8_t* get_contents() { return m_contents.get(); }
uint8_t* get_contents() { return &m_contents[0]; }
bool persistent_ram() { return !m_pathname.empty(); }
const char* get_pathname() { return m_pathname.c_str(); }
void cleanup() { m_contents.reset(); }
void cleanup() { m_contents.clear(); }
private:
const char* m_id;
uint32_t m_length;
std::unique_ptr<uint8_t []> m_contents;
std::vector<uint8_t> m_contents;
const std::string m_pathname;
};
@ -201,7 +128,7 @@ private:
// RPK which is associated to this cartridge
// When we close it, the contents are saved to NVRAM if available
rpk *m_rpk;
std::unique_ptr<rpk> m_rpk;
};
/****************************************************************************/

474
src/lib/formats/rpk.cpp Normal file
View File

@ -0,0 +1,474 @@
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/***************************************************************************
rpk.cpp
RPK format support
A RPK file ("rompack") contains a collection of dump files and a layout
file that defines the kind of circuit board (PCB) used in the cartridge
and the mapping of dumps to sockets on the board.
Example:
<?xml version="1.0" encoding="utf-8"?>
<romset>
<resources>
<rom id="gromimage" file="ed-assmg.bin" />
</resources>
<configuration>
<pcb type="standard">
<socket id="grom_socket" uses="gromimage"/>
</pcb>
</configuration>
</romset>
DTD:
<!ELEMENT romset (resources, configuration)>
<!ELEMENT resources (rom|ram)+>
<!ELEMENT rom EMPTY>
<!ELEMENT ram EMPTY>
<!ELEMENT configuration (pcb)>
<!ELEMENT pcb (socket)+>
<!ELEMENT socket EMPTY>
<!ATTLIST romset version CDATA #IMPLIED>
<!ATTLIST rom id ID #REQUIRED
<!ATTLIST rom file CDATA #REQUIRED>
<!ATTLIST rom crc CDATA #IMPLIED>
<!ATTLIST rom sha1 CDATA #IMPLIED>
<!ATTLIST ram id ID #REQUIRED>
<!ATTLIST ram type (volatile|persistent) #IMPLIED>
<!ATTLIST ram store (internal|external) #IMPLIED>
<!ATTLIST ram file CDATA #IMPLIED>
<!ATTLIST ram length CDATA #REQUIRED>
<!ATTLIST pcb type CDATA #REQUIRED>
<!ATTLIST socket id ID #REQUIRED>
<!ATTLIST socket uses IDREF #REQUIRED>
***************************************************************************/
#include "rpk.h"
#include "xmlfile.h"
namespace
{
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
class rpk_category_impl : public std::error_category
{
public:
virtual char const *name() const noexcept override { return "rpk"; }
virtual std::string message(int condition) const override;
};
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
rpk_category_impl const f_rpk_category_instance;
};
/***************************************************************************
RPK READER
***************************************************************************/
//-------------------------------------------------
// ctor
//-------------------------------------------------
rpk_reader::rpk_reader(char const *const *pcb_types, bool supports_ram)
: m_pcb_types(pcb_types)
, m_supports_ram(supports_ram)
{
}
//-------------------------------------------------
// read
//-------------------------------------------------
std::error_condition rpk_reader::read(std::unique_ptr<util::random_read> &&stream, rpk_file::ptr &result) const
{
// open the RPK (as a zip file)
util::archive_file::ptr zipfile;
std::error_condition ziperr = util::archive_file::open_zip(std::move(stream), zipfile);
if (ziperr)
return ziperr;
// open the layout XML
if (zipfile->search("layout.xml", false) < 0)
return error::MISSING_LAYOUT;
// determine the uncompressed length
uint64_t uncompressed_length_uint64 = zipfile->current_uncompressed_length();
size_t uncompressed_length = (size_t)uncompressed_length_uint64;
if (uncompressed_length != uncompressed_length_uint64)
return std::errc::not_enough_memory;
// prepare a buffer for the layout XML
std::unique_ptr<char[]> layout_xml_text(new (std::nothrow) char[uncompressed_length + 1]);
if (!layout_xml_text)
return std::errc::not_enough_memory;
// and decompress it
ziperr = zipfile->decompress(&layout_xml_text[0], uncompressed_length);
if (ziperr)
return ziperr;
layout_xml_text[uncompressed_length] = 0;
// parse the layout text
util::xml::file::ptr const layout_xml = util::xml::file::string_read(&layout_xml_text[0], nullptr);
if (!layout_xml)
return error::XML_ERROR;
layout_xml_text.reset();
// now we work within the XML tree
// romset is the root node
util::xml::data_node const *const romset_node = layout_xml->get_child("romset");
if (!romset_node)
return error::INVALID_LAYOUT; // document element must be <romset>
// resources is a child of romset
util::xml::data_node const *const resources_node = romset_node->get_child("resources");
if (!resources_node)
return error::INVALID_LAYOUT; // <romset> must have a <resources> child
// configuration is a child of romset; we're actually interested in ...
util::xml::data_node const *const configuration_node = romset_node->get_child("configuration");
if (!configuration_node)
return error::INVALID_LAYOUT; // <romset> must have a <configuration> child
// ... pcb, which is a child of configuration
util::xml::data_node const *const pcb_node = configuration_node->get_child("pcb");
if (!pcb_node)
return error::INVALID_LAYOUT; // <configuration> must have a <pcb> child
// we'll try to find the PCB type on the provided type list.
std::string const *const pcb_type_string = pcb_node->get_attribute_string_ptr("type");
if (!pcb_type_string)
return error::INVALID_LAYOUT; // <pcb> must have a 'type' attribute";
int pcb_type = 0;
while (m_pcb_types[pcb_type] && strcmp(m_pcb_types[pcb_type], pcb_type_string->c_str()))
pcb_type++;
if (!m_pcb_types[pcb_type])
return error::UNKNOWN_PCB_TYPE;
// create the rpk_file object
rpk_file::ptr file = std::make_unique<rpk_file>(std::move(zipfile), pcb_type);
// find the sockets and load their respective resource
for (util::xml::data_node const *socket_node = pcb_node->get_first_child(); socket_node; socket_node = socket_node->get_next_sibling())
{
if (strcmp(socket_node->get_name(), "socket") != 0)
return error::INVALID_LAYOUT; // <pcb> element has only <socket> children
std::string const *const id = socket_node->get_attribute_string_ptr("id");
if (!id)
return error::INVALID_LAYOUT; // <socket> must have an 'id' attribute
std::string const *const uses_name = socket_node->get_attribute_string_ptr("uses");
if (!uses_name)
return error::INVALID_LAYOUT; // <socket> must have a 'uses' attribute"
// locate the resource node
util::xml::data_node const *resource_node = nullptr;
for (util::xml::data_node const *this_resource_node = resources_node->get_first_child(); this_resource_node; this_resource_node = this_resource_node->get_next_sibling())
{
std::string const *const resource_name = this_resource_node->get_attribute_string_ptr("id");
if (!resource_name)
return error::INVALID_LAYOUT; // resource node must have an 'id' attribute
if (*resource_name == *uses_name)
{
resource_node = this_resource_node;
break;
}
}
if (!resource_node)
return error::INVALID_RESOURCE_REF; // *uses_name
// process the resource
if (!strcmp(resource_node->get_name(), "rom"))
{
std::error_condition err = file->add_rom_socket(std::string(*id), *resource_node);
if (err)
return err;
}
else if (!strcmp(resource_node->get_name(), "ram"))
{
if (!m_supports_ram)
return error::UNSUPPORTED_RPK_FEATURE; // <ram> is not supported by this system
std::error_condition err = file->add_ram_socket(std::string(*id), *resource_node);
if (err)
return err;
}
else
return error::INVALID_LAYOUT; // resource node must be <rom> or <ram>
}
// and we're done!
result = std::move(file);
return std::error_condition();
}
/***************************************************************************
RPK FILE
***************************************************************************/
//-------------------------------------------------
// ctor
//-------------------------------------------------
rpk_file::rpk_file(util::archive_file::ptr &&zipfile, int pcb_type)
: m_zipfile(std::move(zipfile))
, m_pcb_type(pcb_type)
{
}
//-------------------------------------------------
// dtor
//-------------------------------------------------
rpk_file::~rpk_file()
{
}
//-------------------------------------------------
// add_rom_socket
//-------------------------------------------------
std::error_condition rpk_file::add_rom_socket(std::string &&id, const util::xml::data_node &rom_resource_node)
{
// find the file attribute (required)
std::string const *const file = rom_resource_node.get_attribute_string_ptr("file");
if (!file)
return rpk_reader::error::INVALID_LAYOUT; // <rom> must have a 'file' attribute
// check for crc (optional)
std::optional<util::hash_collection> hashes;
std::string const *const crcstr = rom_resource_node.get_attribute_string_ptr("crc");
if (crcstr)
{
if (!hashes)
hashes.emplace();
hashes->add_from_string(util::hash_collection::HASH_CRC, *crcstr);
}
// check for sha1 (optional)
std::string const *const sha1 = rom_resource_node.get_attribute_string_ptr("sha1");
if (sha1)
{
if (!hashes.has_value())
hashes.emplace();
hashes->add_from_string(util::hash_collection::HASH_SHA1, *sha1);
}
// finally add the socket
m_sockets.emplace_back(*this, std::move(id), rpk_socket::socket_type::ROM, std::string(*file), std::move(hashes));
return std::error_condition();
}
//-------------------------------------------------
// add_ram_socket
//-------------------------------------------------
std::error_condition rpk_file::add_ram_socket(std::string &&id, const util::xml::data_node &ram_resource_node)
{
// find the length attribute
std::string const *const length_string = ram_resource_node.get_attribute_string_ptr("length");
if (!length_string)
return rpk_reader::error::MISSING_RAM_LENGTH;
// parse it
unsigned int length;
char suffix;
switch (sscanf(length_string->c_str(), "%u%c", &length, &suffix))
{
case 1:
// fall through
break;
case 2:
switch (tolower(suffix))
{
case 'k':
// kilobytes
length *= 1024;
break;
case 'm':
// megabytes
length *= 1024 * 1024;
break;
default: // failed
return rpk_reader::error::INVALID_RAM_SPEC;
}
break;
default:
return rpk_reader::error::INVALID_RAM_SPEC;
}
// determine the type of RAM
rpk_socket::socket_type type;
std::string const *const ram_type = ram_resource_node.get_attribute_string_ptr("type");
if (ram_type && *ram_type == "persistent")
type = rpk_socket::socket_type::PERSISTENT_RAM;
else
type = rpk_socket::socket_type::RAM;
// persistent RAM needs a file name
std::string file;
if (type == rpk_socket::socket_type::PERSISTENT_RAM)
{
std::string const *const ram_filename = ram_resource_node.get_attribute_string_ptr("file");
if (ram_filename == nullptr)
return rpk_reader::error::INVALID_RAM_SPEC; // <ram type='persistent'> must have a 'file' attribute
file = *ram_filename;
}
// finally add the socket
m_sockets.emplace_back(*this, std::move(id), type, std::move(file), std::nullopt, length);
return std::error_condition();
}
/***************************************************************************
RPK SOCKET
***************************************************************************/
//-------------------------------------------------
// ctor
//-------------------------------------------------
rpk_socket::rpk_socket(rpk_file &rpk, std::string &&id, socket_type type, std::string &&filename, std::optional<util::hash_collection> &&hashes, std::uint32_t length)
: m_rpk(rpk)
, m_id(id)
, m_type(type)
, m_filename(filename)
, m_hashes(std::move(hashes))
, m_length(length)
{
}
//-------------------------------------------------
// dtor
//-------------------------------------------------
rpk_socket::~rpk_socket()
{
}
//-------------------------------------------------
// read_file
//-------------------------------------------------
std::error_condition rpk_socket::read_file(std::vector<std::uint8_t> &result) const
{
// find the file
if (m_rpk.zipfile().search(m_filename, false) < 0)
return rpk_reader::error::INVALID_FILE_REF;
// prepare a buffer
result.clear();
try
{
result.resize(m_rpk.zipfile().current_uncompressed_length());
}
catch (std::bad_alloc const &)
{
return std::errc::not_enough_memory;
}
// read the file
std::error_condition const ziperr = m_rpk.zipfile().decompress(&result[0], m_rpk.zipfile().current_uncompressed_length());
if (ziperr)
return ziperr;
// perform hash checks, if appropriate
if (m_hashes.has_value())
{
util::hash_collection actual_hashes;
actual_hashes.compute(&result[0], result.size(), m_hashes->hash_types().c_str());
if (actual_hashes != m_hashes)
return rpk_reader::error::INVALID_FILE_REF; // Hash check failed
}
// success!
return std::error_condition();
}
/***************************************************************************
RPK EXCEPTION HANDLING
***************************************************************************/
//-------------------------------------------------
// rpk_category - gets the RPK error category instance
//-------------------------------------------------
std::error_category const &rpk_category() noexcept
{
return f_rpk_category_instance;
}
//-------------------------------------------------
// error_message
//-------------------------------------------------
std::string rpk_category_impl::message(int condition) const
{
using namespace std::literals;
std::string result;
switch ( condition)
{
case 0:
result = "No error"s;
break;
case (int)rpk_reader::error::XML_ERROR:
result = "XML format error"s;
break;
case (int)rpk_reader::error::MISSING_RAM_LENGTH:
result = "Missing RAM length"s;
break;
case (int)rpk_reader::error::INVALID_RAM_SPEC:
result = "Invalid RAM specification"s;
break;
case (int)rpk_reader::error::INVALID_RESOURCE_REF:
result = "Invalid resource reference"s;
break;
case (int)rpk_reader::error::INVALID_LAYOUT:
result = "layout.xml not valid"s;
break;
case (int)rpk_reader::error::MISSING_LAYOUT:
result = "Missing layout"s;
break;
case (int)rpk_reader::error::UNKNOWN_PCB_TYPE:
result = "Unknown pcb type"s;
break;
case (int)rpk_reader::error::UNSUPPORTED_RPK_FEATURE:
result = "RPK feature not supported"s;
break;
default:
result = "Unknown error"s;
break;
}
return result;
}

143
src/lib/formats/rpk.h Normal file
View File

@ -0,0 +1,143 @@
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/***************************************************************************
rpk.h
RPK format support
***************************************************************************/
#ifndef MAME_FORMATS_RPK_H
#define MAME_FORMATS_RPK_H
#pragma once
#include "hash.h"
#include "unzip.h"
#include <cassert>
#include <list>
#include <optional>
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
class rpk_reader;
class rpk_file;
// ======================> rpk_socket
class rpk_socket
{
friend class rpk_reader;
public:
enum class socket_type
{
ROM,
RAM,
PERSISTENT_RAM
};
// ctor/dtor
rpk_socket(rpk_file &rpk, std::string &&id, socket_type type, std::string &&filename, std::optional<util::hash_collection> &&hashes, std::uint32_t length = ~0);
rpk_socket(const rpk_socket &) = delete;
rpk_socket(rpk_socket &&) = delete;
~rpk_socket();
// accessors
const std::string &id() const noexcept { return m_id; }
socket_type type() const noexcept { return m_type; }
const std::string &filename() const noexcept { return m_filename; }
std::uint32_t length() const noexcept { assert(m_type == socket_type::RAM || m_type == socket_type::PERSISTENT_RAM); return m_length; }
// methods
std::error_condition read_file(std::vector<std::uint8_t> &result) const;
private:
rpk_file & m_rpk;
std::string m_id;
socket_type m_type;
std::string m_filename;
std::optional<util::hash_collection> m_hashes;
std::uint32_t m_length;
};
// ======================> rpk_file
class rpk_file
{
friend class rpk_reader;
friend class rpk_socket;
public:
typedef std::unique_ptr<rpk_file> ptr;
// ctor/dtor
rpk_file(util::archive_file::ptr &&zipfile, int pcb_type);
rpk_file(const rpk_file &) = delete;
rpk_file(rpk_file &&) = delete;
~rpk_file();
// accessors
int pcb_type() const { return m_pcb_type; }
const std::list<rpk_socket> &sockets() const { return m_sockets; }
private:
util::archive_file::ptr m_zipfile;
int m_pcb_type;
std::list<rpk_socket> m_sockets;
// accesors
util::archive_file &zipfile() { return *m_zipfile; }
// methods
std::error_condition add_rom_socket(std::string &&id, const util::xml::data_node &rom_resource_node);
std::error_condition add_ram_socket(std::string &&id, const util::xml::data_node &ram_resource_node);
};
// ======================> rpk_reader
class rpk_reader
{
public:
enum class error
{
XML_ERROR = 1,
INVALID_FILE_REF,
MISSING_RAM_LENGTH,
INVALID_RAM_SPEC,
INVALID_RESOURCE_REF,
INVALID_LAYOUT,
MISSING_LAYOUT,
UNKNOWN_PCB_TYPE,
UNSUPPORTED_RPK_FEATURE
};
// ctor/dtor
rpk_reader(char const *const *pcb_types, bool supports_ram);
rpk_reader(const rpk_reader &) = delete;
rpk_reader(rpk_reader &&) = delete;
// methods
std::error_condition read(std::unique_ptr<util::random_read> &&stream, rpk_file::ptr &result) const;
private:
char const *const * m_pcb_types;
bool m_supports_ram;
};
// error category for RPK errors
std::error_category const &rpk_category() noexcept;
inline std::error_condition make_error_condition(rpk_reader::error err) noexcept { return std::error_condition(int(err), rpk_category()); }
namespace std {
template <> struct is_error_condition_enum<rpk_reader::error> : public std::true_type { };
} // namespace std
#endif // MAME_FORMATS_RPK_H