mirror of
https://github.com/holub/mame
synced 2025-04-16 05:24:54 +03:00
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:
parent
fbd121cf43
commit
5e02ff231e
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
474
src/lib/formats/rpk.cpp
Normal 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
143
src/lib/formats/rpk.h
Normal 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
|
Loading…
Reference in New Issue
Block a user