From 5e02ff231e2472e7e75a97b6e8758a070b227f8e Mon Sep 17 00:00:00 2001 From: npwoods Date: Sun, 5 Sep 2021 13:48:30 -0400 Subject: 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. --- scripts/src/formats.lua | 2 + src/devices/bus/ti99/gromport/cartridges.cpp | 403 +++++------------------ src/devices/bus/ti99/gromport/cartridges.h | 105 +----- src/lib/formats/rpk.cpp | 474 +++++++++++++++++++++++++++ src/lib/formats/rpk.h | 143 ++++++++ 5 files changed, 719 insertions(+), 408 deletions(-) create mode 100644 src/lib/formats/rpk.cpp create mode 100644 src/lib/formats/rpk.h 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(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: - - - - - - - - - - - - -DTD: - - - - - - - - - - - - - - - - - - - - ****************************************************************************/ #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 newsock) +void ti99_cartridge_device::rpk::add_socket(const char* id, std::unique_ptr &&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 &&contents, std::string &&pathname) +ti99_cartridge_device::ti99_rpk_socket::ti99_rpk_socket(const char* id, int length, std::vector &&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 &&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 &&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_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 &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, " 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 contents; - try { contents = make_unique_clear(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 contents; + std::error_condition err = socket.read_file(contents); + if (err) + return err; // Create a socket instance - return std::make_unique(socketname, length, std::move(contents)); + result = std::make_unique(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_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::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 contents; - try { contents = make_unique_clear(length); } - catch (std::bad_alloc const &) { throw rpk_exception(RPK_OUT_OF_MEMORY); } + std::vector 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, " 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(socketname, length, std::move(contents), std::move(ram_pname)); + return std::make_unique(socket.id().c_str(), socket.length(), std::move(contents), std::move(ram_pname)); } /*------------------------------------------------- @@ -1751,123 +1592,47 @@ std::unique_ptr 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 &&stream, const char *system_name, std::unique_ptr &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 newrpk = std::make_unique(options, system_name); - // reserve space for the layout file contents (+1 for the termination) - std::vector 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 "); - - // 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, " must have a 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, " must have a 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, " must have a 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, " 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_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, " element has only children"); - std::string const *const id = socket_node->get_attribute_string_ptr("id"); - if (!id) throw rpk_exception(RPK_INVALID_LAYOUT, " 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, " 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 or "); - } - 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 load_rom_resource(util::archive_file &zip, util::xml::data_node const* rom_resource_node, const char* socketname); - std::unique_ptr 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 &&stream, const char *system_name, std::unique_ptr &result); + static std::error_condition rpk_load_rom_resource(const rpk_socket &socket, std::unique_ptr &result); + static std::unique_ptr 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> m_sockets; + std::unordered_map> m_sockets; - void add_socket(const char* id, std::unique_ptr newsock); + void add_socket(const char* id, std::unique_ptr &&newsock); }; - class rpk_socket + class ti99_rpk_socket { public: - rpk_socket(const char *id, int length, std::unique_ptr &&contents); - rpk_socket(const char *id, int length, std::unique_ptr &&contents, std::string &&pathname); - ~rpk_socket() {} + ti99_rpk_socket(const char *id, int length, std::vector &&contents); + ti99_rpk_socket(const char *id, int length, std::vector &&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 m_contents; + std::vector 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 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: + + + + + + + + + + + + +DTD: + + + + + + + + + + + + + + + + + + + + +***************************************************************************/ + +#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 &&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 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 + + // 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; // must have a 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; // must have a 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; // must have a 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; // 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(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; // element has only children + + std::string const *const id = socket_node->get_attribute_string_ptr("id"); + if (!id) + return error::INVALID_LAYOUT; // 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; // 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; // 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 or + } + + // 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; // must have a 'file' attribute + + // check for crc (optional) + std::optional 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; // 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 &&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 &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 +#include +#include + + +/*************************************************************************** + 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 &&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 &result) const; + +private: + rpk_file & m_rpk; + std::string m_id; + socket_type m_type; + std::string m_filename; + std::optional 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 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 &sockets() const { return m_sockets; } + +private: + util::archive_file::ptr m_zipfile; + int m_pcb_type; + std::list 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 &&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 : public std::true_type { }; +} // namespace std + +#endif // MAME_FORMATS_RPK_H -- cgit v1.2.3