summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author npwoods <npwoods@mess.org>2021-09-05 13:48:30 -0400
committer GitHub <noreply@github.com>2021-09-06 03:48:30 +1000
commit5e02ff231e2472e7e75a97b6e8758a070b227f8e (patch)
tree9bf72e54c9fb9e581d0d88575bfd06eb9f29ebcc
parentfbd121cf43b7d1ff1377d5543a0393c50f9516ad (diff)
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.
-rw-r--r--scripts/src/formats.lua2
-rw-r--r--src/devices/bus/ti99/gromport/cartridges.cpp403
-rw-r--r--src/devices/bus/ti99/gromport/cartridges.h105
-rw-r--r--src/lib/formats/rpk.cpp474
-rw-r--r--src/lib/formats/rpk.h143
5 files changed, 719 insertions, 408 deletions
diff --git a/scripts/src/formats.lua b/scripts/src/formats.lua
index d2c7ceda4a5..7e80512175b 100644
--- a/scripts/src/formats.lua
+++ b/scripts/src/formats.lua
@@ -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",
diff --git a/src/devices/bus/ti99/gromport/cartridges.cpp b/src/devices/bus/ti99/gromport/cartridges.cpp
index 2f5a558d29b..78c0231c9a7 100644
--- a/src/devices/bus/ti99/gromport/cartridges.cpp
+++ b/src/devices/bus/ti99/gromport/cartridges.cpp
@@ -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[] =
-{
- { 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}
+static char const *const pcbdefs[] =
+{
+ "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), "")
-{
-}
-
-/*
- 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)
+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), "")
{
- 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(*ram_filename);
- // load, and fill rest with 00
- LOGMASKED(LOG_RPK, "[RPK handler] Loading NVRAM contents from '%s'\n", ram_pname.c_str());
-
- // 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);
+ 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);
- // fill remaining bytes (if necessary)
- std::fill_n(&contents[bytes_read], length - bytes_read, 0x00);
- }
+ // 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());
+
+ // 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);
-
- try
- {
- // open the ZIP file
- auto reader = util::core_file_read(file);
- if (!reader) throw rpk_exception(RPK_OUT_OF_MEMORY);
-
- 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);
+ std::unique_ptr<rpk> newrpk = std::make_unique<rpk>(options, system_name);
- // reserve space for the layout file contents (+1 for the termination)
- std::vector<char> layout_text(zipfile->current_uncompressed_length() + 1);
+ rpk_reader reader(pcbdefs, true);
- // uncompress the layout text
- ziperr = zipfile->decompress(&layout_text[0], zipfile->current_uncompressed_length());
- if (ziperr)
- {
- if (ziperr == util::archive_file::error::UNSUPPORTED) throw rpk_exception(RPK_ZIP_UNSUPPORTED);
- else throw rpk_exception(RPK_ZIP_ERROR);
- }
+ // open the RPK
+ rpk_file::ptr file;
+ std::error_condition err = reader.read(std::move(stream), file);
+ if (err)
+ return err;
- layout_text[zipfile->current_uncompressed_length()] = '\0'; // Null-terminate
+ // 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()]);
- // 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);
+ for (const rpk_socket &socket : file->sockets())
+ {
+ std::unique_ptr<ti99_rpk_socket> ti99_socket;
- int i=0;
- do
+ switch (socket.type())
{
- if (*pcb_type == m_types[i].name)
- {
- newrpk->m_type = m_types[i].id;
- break;
- }
- i++;
- } while (m_types[i].id != 0);
+ 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;
- if (m_types[i].id==0) throw rpk_exception(RPK_UNKNOWN_PCB_TYPE);
+ 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;
- // 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
diff --git a/src/devices/bus/ti99/gromport/cartridges.h b/src/devices/bus/ti99/gromport/cartridges.h
index df94321a5f9..ccf8773c79b 100644
--- a/src/devices/bus/ti99/gromport/cartridges.h
+++ b/src/devices/bus/ti99/gromport/cartridges.h
@@ -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;
};
/****************************************************************************/
diff --git a/src/lib/formats/rpk.cpp b/src/lib/formats/rpk.cpp
new file mode 100644
index 00000000000..a5b146d74aa
--- /dev/null
+++ b/src/lib/formats/rpk.cpp
@@ -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;
+}
diff --git a/src/lib/formats/rpk.h b/src/lib/formats/rpk.h
new file mode 100644
index 00000000000..55b6f0a4839
--- /dev/null
+++ b/src/lib/formats/rpk.h
@@ -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