mirror of
https://github.com/GerbilSoft/rom-properties.git
synced 2025-06-18 19:45:41 -04:00
[libromdata] DpfReader: Initial reader class for GameCube DPF and Wii RPF files.
These are sparse disc images created by the official SDKs. Note that unlike the other sparse disc image formats we support, DPF and RPF both have byte granularity instead of block granularity. Therefore, we're not using SparseDiscReader as a base class. RomDataFactory: Use IDiscReader instead of SparseDiscReader as the base class in order to be able to use DpfReader. Added DPF and RPF MIME types and file extensions for the GameCube class. Also added the MIME types to the xdg/ directory. FIXME: memset() in read() is causing an Internal Compiler Error when compiling with gcc-13.2.0. Using a (slower) `for` loop instead. TODO: - RPF images don't start at virt=0, phys=0. Need to add a fake block for phys=0 and adjust all the virtual offsets. - Pre-adjust all phys offsets by adding the data start offset? Doing this might just waste cycles if we don't read that much data from the disc.
This commit is contained in:
parent
f5db0d87c1
commit
02bf99a970
@ -108,6 +108,7 @@ SET(${PROJECT_NAME}_SRCS
|
||||
disc/CIAReader.cpp
|
||||
disc/CisoGcnReader.cpp
|
||||
disc/CisoPspReader.cpp
|
||||
disc/DpfReader.cpp
|
||||
disc/GcnFst.cpp
|
||||
disc/GcnPartition.cpp
|
||||
disc/GcnPartition_p.cpp
|
||||
@ -304,6 +305,7 @@ SET(${PROJECT_NAME}_H
|
||||
disc/CIAReader.hpp
|
||||
disc/CisoGcnReader.hpp
|
||||
disc/CisoPspReader.hpp
|
||||
disc/DpfReader.hpp
|
||||
disc/GcnFst.hpp
|
||||
disc/GcnPartition.hpp
|
||||
disc/GcnPartition_p.hpp
|
||||
@ -323,6 +325,7 @@ SET(${PROJECT_NAME}_H
|
||||
|
||||
disc/ciso_gcn.h
|
||||
disc/ciso_psp_structs.h
|
||||
disc/dpf_structs.h
|
||||
disc/gcz_structs.h
|
||||
disc/libwbfs.h
|
||||
disc/nasos_gcn.h
|
||||
|
@ -228,6 +228,7 @@ const char *const GameCubePrivate::exts[] = {
|
||||
".tgc",
|
||||
".dec", // .iso.dec
|
||||
".gcz",
|
||||
".dpf", ".rpf",
|
||||
|
||||
// Partially supported. (Header only!)
|
||||
".wia",
|
||||
@ -255,6 +256,8 @@ const char *const GameCubePrivate::mimeTypes[] = {
|
||||
"application/x-compressed-iso", // KDE detects CISO as this
|
||||
"application/x-nasos-image",
|
||||
"application/x-gcz-image",
|
||||
"application/x-dpf-image",
|
||||
"application/x-rpf-image",
|
||||
"application/x-rvz-image",
|
||||
|
||||
nullptr
|
||||
|
@ -113,6 +113,7 @@ using std::vector;
|
||||
// Sparse disc image formats
|
||||
#include "disc/CisoGcnReader.hpp"
|
||||
#include "disc/CisoPspReader.hpp"
|
||||
#include "disc/DpfReader.hpp"
|
||||
#include "disc/GczReader.hpp"
|
||||
#include "disc/NASOSReader.hpp"
|
||||
#include "disc/WbfsReader.hpp"
|
||||
@ -200,43 +201,44 @@ public:
|
||||
public:
|
||||
/** Sparse disc image check arrays **/
|
||||
|
||||
typedef int (*pfnSparseIsDiscSupported)(const uint8_t *pHeader, size_t szHeader);
|
||||
typedef SparseDiscReader* (*pfnNewSparseDiscReader_t)(const IRpFilePtr &file);
|
||||
typedef int (*pfnIsDiscSupported)(const uint8_t *pHeader, size_t szHeader);
|
||||
typedef IDiscReader* (*pfnNewIDiscReader_t)(const IRpFilePtr &file);
|
||||
|
||||
struct SparseDiscReaderFns {
|
||||
pfnSparseIsDiscSupported isDiscSupported;
|
||||
pfnNewSparseDiscReader_t newSparseDiscReader;
|
||||
struct IDiscReaderFns {
|
||||
pfnIsDiscSupported isDiscSupported;
|
||||
pfnNewIDiscReader_t newIDiscReader;
|
||||
|
||||
// Magic numbers to check.
|
||||
// Up to 4 magic numbers can be specified for multiple formats.
|
||||
// 0 == end of magic list
|
||||
// NOTE: Must be in Big-Endian format!
|
||||
uint32_t magic[4];
|
||||
};
|
||||
|
||||
/**
|
||||
* Templated function to construct a new SparseDiscReader subclass.
|
||||
* Templated function to construct a new IDiscReader subclass.
|
||||
* @param klass Class name.
|
||||
*/
|
||||
template<typename klass>
|
||||
static SparseDiscReader *SparseDiscReader_ctor(const IRpFilePtr &file)
|
||||
static IDiscReader *IDiscReader_ctor(const IRpFilePtr &file)
|
||||
{
|
||||
return new klass(file);
|
||||
}
|
||||
|
||||
#define GetSparseDiscReaderFns(discType, magic) \
|
||||
#define GetIDiscReaderFns(discType, magic) \
|
||||
{discType::isDiscSupported_static, \
|
||||
RomDataFactoryPrivate::SparseDiscReader_ctor<discType>, \
|
||||
RomDataFactoryPrivate::IDiscReader_ctor<discType>, \
|
||||
magic}
|
||||
|
||||
static const SparseDiscReaderFns sparseDiscReaderFns[];
|
||||
static const IDiscReaderFns iDiscReaderFns[];
|
||||
|
||||
/**
|
||||
* Attempt to open a SparseDiscReader for this file.
|
||||
* Attempt to open a IDiscReader for this file.
|
||||
* @param file [in] IRpFilePtr
|
||||
* @param magic0 [in] First 32-bit value from the file (original format from the file)
|
||||
* @return SparseDiscReader*, or nullptr if not applicable.
|
||||
* @return IDiscReader*, or nullptr if not applicable.
|
||||
*/
|
||||
static SparseDiscReader *openSparseDiscReader(const IRpFilePtr &file, uint32_t magic0);
|
||||
static IDiscReader *openIDiscReader(const IRpFilePtr &file, uint32_t magic0);
|
||||
|
||||
public:
|
||||
#ifdef ROMDATAFACTORY_USE_FILE_EXTENSIONS
|
||||
@ -448,17 +450,18 @@ const RomDataFactoryPrivate::RomDataFns *const RomDataFactoryPrivate::romDataFns
|
||||
nullptr
|
||||
};
|
||||
|
||||
// Sparse Disc Reader functions
|
||||
// IDiscReader functions
|
||||
#define P99_PROTECT(...) __VA_ARGS__ /* Reference: https://stackoverflow.com/a/5504336 */
|
||||
const RomDataFactoryPrivate::SparseDiscReaderFns RomDataFactoryPrivate::sparseDiscReaderFns[] = {
|
||||
GetSparseDiscReaderFns(CisoGcnReader, P99_PROTECT({'CISO'})),
|
||||
const RomDataFactoryPrivate::IDiscReaderFns RomDataFactoryPrivate::iDiscReaderFns[] = {
|
||||
GetIDiscReaderFns(CisoGcnReader, P99_PROTECT({'CISO'})),
|
||||
// NOTE: MSVC doesn't like putting #ifdef within the P99_PROTECT macro.
|
||||
// TODO: Disable ZISO and JISO if LZ4 and LZO aren't available?
|
||||
GetSparseDiscReaderFns(CisoPspReader, P99_PROTECT({'CISO', 'ZISO', 0x44415800, 'JISO'})),
|
||||
GetSparseDiscReaderFns(GczReader, P99_PROTECT({0xB10BC001})),
|
||||
GetSparseDiscReaderFns(NASOSReader, P99_PROTECT({'GCML', 'GCMM', 'WII5', 'WII9'})),
|
||||
//GetSparseDiscReaderFns(WbfsReader, P99_PROTECT({'WBFS'})), // Handled separately
|
||||
GetSparseDiscReaderFns(WuxReader, P99_PROTECT({'WUX0'})), // NOTE: Not checking second magic here.
|
||||
GetIDiscReaderFns(CisoPspReader, P99_PROTECT({'CISO', 'ZISO', 0x44415800, 'JISO'})),
|
||||
GetIDiscReaderFns(DpfReader, P99_PROTECT({0x863EFC23, 0x6A2BF9E0})),
|
||||
GetIDiscReaderFns(GczReader, P99_PROTECT({0xB10BC001})),
|
||||
GetIDiscReaderFns(NASOSReader, P99_PROTECT({'GCML', 'GCMM', 'WII5', 'WII9'})),
|
||||
//GetIDiscReaderFns(WbfsReader, P99_PROTECT({'WBFS'})), // Handled separately
|
||||
GetIDiscReaderFns(WuxReader, P99_PROTECT({'WUX0'})), // NOTE: Not checking second magic here.
|
||||
|
||||
{nullptr, nullptr, {0}}
|
||||
};
|
||||
@ -526,12 +529,12 @@ RomDataPtr RomDataFactoryPrivate::openDreamcastVMSandVMI(const IRpFilePtr &file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to open a SparseDiscReader for this file.
|
||||
* Attempt to open an IDiscReader for this file.
|
||||
* @param file [in] IRpFilePtr
|
||||
* @param magic0 [in] First 32-bit value from the file (original format from the file)
|
||||
* @return SparseDiscReader*, or nullptr if not applicable.
|
||||
* @return IDiscReader*, or nullptr if not applicable.
|
||||
*/
|
||||
SparseDiscReader *RomDataFactoryPrivate::openSparseDiscReader(const IRpFilePtr &file, uint32_t magic0)
|
||||
IDiscReader *RomDataFactoryPrivate::openIDiscReader(const IRpFilePtr &file, uint32_t magic0)
|
||||
{
|
||||
if (magic0 == 0)
|
||||
return nullptr;
|
||||
@ -588,9 +591,12 @@ SparseDiscReader *RomDataFactoryPrivate::openSparseDiscReader(const IRpFilePtr &
|
||||
return new WbfsReader(file);
|
||||
}
|
||||
|
||||
const RomDataFactoryPrivate::SparseDiscReaderFns *sdfns =
|
||||
RomDataFactoryPrivate::sparseDiscReaderFns;
|
||||
for (; sdfns->newSparseDiscReader != nullptr; sdfns++) {
|
||||
// NOTE: This was originally for SparseDiscReader subclasses.
|
||||
// DpfReader does not derive from SparseDiscReader, so it was
|
||||
// changed to IDiscReader subclasses.
|
||||
const RomDataFactoryPrivate::IDiscReaderFns *sdfns =
|
||||
RomDataFactoryPrivate::iDiscReaderFns;
|
||||
for (; sdfns->newIDiscReader != nullptr; sdfns++) {
|
||||
// Check all four magic numbers.
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(sdfns->magic); i++) {
|
||||
if (sdfns->magic[i] == 0) {
|
||||
@ -598,16 +604,16 @@ SparseDiscReader *RomDataFactoryPrivate::openSparseDiscReader(const IRpFilePtr &
|
||||
break;
|
||||
} else if (sdfns->magic[i] == magic0) {
|
||||
// Found a matching magic.
|
||||
SparseDiscReader *const sd = sdfns->newSparseDiscReader(file);
|
||||
IDiscReader *const sd = sdfns->newIDiscReader(file);
|
||||
if (sd->isOpen()) {
|
||||
// SparseDiscReader obtained.
|
||||
// IDiscReader obtained.
|
||||
return sd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No SparseDiscReader is available for this file.
|
||||
// No IDiscReader is available for this file.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -808,7 +814,7 @@ RomDataPtr RomDataFactory::create(const IRpFilePtr &file, unsigned int attrs)
|
||||
// If a sparse disc image format is detected, this will be
|
||||
// a SparseDiscReader. Otherwise, it'll be the same as `file`.
|
||||
bool isSparseDiscReader = false;
|
||||
IRpFilePtr reader(RomDataFactoryPrivate::openSparseDiscReader(file, header.u32[0]));
|
||||
IRpFilePtr reader(RomDataFactoryPrivate::openIDiscReader(file, header.u32[0]));
|
||||
if (reader) {
|
||||
// SparseDiscReader obtained. Re-read the header.
|
||||
reader->rewind();
|
||||
|
390
src/libromdata/disc/DpfReader.cpp
Normal file
390
src/libromdata/disc/DpfReader.cpp
Normal file
@ -0,0 +1,390 @@
|
||||
/***************************************************************************
|
||||
* ROM Properties Page shell extension. (libromdata) *
|
||||
* DpfReader.cpp: GameCube/Wii DPF/RPF sparse disc image reader. *
|
||||
* *
|
||||
* Copyright (c) 2016-2024 by David Korth. *
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later *
|
||||
***************************************************************************/
|
||||
|
||||
// NOTE: This class does NOT derive from DpfReader because
|
||||
// DPF/RPF uses byte-granularity, not block-granularity.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "config.librpbase.h"
|
||||
|
||||
#include "DpfReader.hpp"
|
||||
#include "dpf_structs.h"
|
||||
|
||||
// Other rom-properties libraries
|
||||
using namespace LibRpBase;
|
||||
using namespace LibRpFile;
|
||||
|
||||
// C++ STL classes
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace LibRomData {
|
||||
|
||||
class DpfReaderPrivate
|
||||
{
|
||||
public:
|
||||
explicit DpfReaderPrivate();
|
||||
|
||||
private:
|
||||
RP_DISABLE_COPY(DpfReaderPrivate)
|
||||
|
||||
public:
|
||||
// DPF/RPF header
|
||||
DpfHeader dpfHeader;
|
||||
|
||||
// RPF entries (DPF entries are converted to RPF on load)
|
||||
rp::uvector<RpfEntry> entries;
|
||||
|
||||
public:
|
||||
// Disc size
|
||||
off64_t disc_size;
|
||||
|
||||
// Current position
|
||||
off64_t pos;
|
||||
};
|
||||
|
||||
/** DpfReaderPrivate **/
|
||||
|
||||
DpfReaderPrivate::DpfReaderPrivate()
|
||||
: disc_size(0)
|
||||
, pos(0)
|
||||
{
|
||||
// Clear the DPF header struct.
|
||||
memset(&dpfHeader, 0, sizeof(dpfHeader));
|
||||
}
|
||||
|
||||
/** DpfReader **/
|
||||
|
||||
DpfReader::DpfReader(const IRpFilePtr &file)
|
||||
: super(file)
|
||||
, d_ptr(new DpfReaderPrivate())
|
||||
{
|
||||
if (!m_file) {
|
||||
// File could not be ref()'d.
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the DPF header.
|
||||
RP_D(DpfReader);
|
||||
m_file->rewind();
|
||||
size_t sz = m_file->read(&d->dpfHeader, sizeof(d->dpfHeader));
|
||||
if (sz != sizeof(d->dpfHeader)) {
|
||||
// Error reading the DPF header.
|
||||
m_file.reset();
|
||||
m_lastError = EIO;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the DPF magic.
|
||||
if (d->dpfHeader.magic != cpu_to_le32(DPF_MAGIC) &&
|
||||
d->dpfHeader.magic != cpu_to_le32(RPF_MAGIC))
|
||||
{
|
||||
// Invalid magic.
|
||||
m_file.reset();
|
||||
m_lastError = EIO;
|
||||
return;
|
||||
}
|
||||
|
||||
#if SYS_BYTEORDER != SYS_LIL_ENDIAN
|
||||
// Byteswap the header.
|
||||
d->dpfHeader.magic = le32_to_cpu(d->dpfHeader.magic);
|
||||
d->dpfHeader.version = le32_to_cpu(d->dpfHeader.version);
|
||||
d->dpfHeader.header_size = le32_to_cpu(d->dpfHeader.header_size);
|
||||
d->dpfHeader.unknown_0C = le32_to_cpu(d->dpfHeader.unknown_0C);
|
||||
d->dpfHeader.entry_table_offset = le32_to_cpu(d->dpfHeader.entry_table_offset);
|
||||
d->dpfHeader.entry_count = le32_to_cpu(d->dpfHeader.entry_count);
|
||||
d->dpfHeader.data_offset = le32_to_cpu(d->dpfHeader.data_offset);
|
||||
#endif /* SYS_BYTEORDER != SYS_LIL_ENDIAN */
|
||||
|
||||
// Allow up to 65,535 entries.
|
||||
assert(d->dpfHeader.entry_count > 0);
|
||||
assert(d->dpfHeader.entry_count <= 65535);
|
||||
if (d->dpfHeader.entry_count == 0) {
|
||||
// No entries...
|
||||
m_file.reset();
|
||||
m_lastError = EIO;
|
||||
return;
|
||||
} else if (d->dpfHeader.entry_count > 65535) {
|
||||
// Too many entries.
|
||||
m_file.reset();
|
||||
m_lastError = ENOMEM;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the entry table.
|
||||
d->entries.resize(d->dpfHeader.entry_count);
|
||||
if (d->dpfHeader.magic == RPF_MAGIC) {
|
||||
// RPF: Load it directly.
|
||||
const size_t load_size = d->dpfHeader.entry_count * sizeof(RpfEntry);
|
||||
size_t size = m_file->seekAndRead(d->dpfHeader.entry_table_offset, d->entries.data(), load_size);
|
||||
if (load_size != size) {
|
||||
// Load error.
|
||||
d->entries.clear();
|
||||
m_file.reset();
|
||||
m_lastError = EIO;
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: If the first entry has virt=0 and phys!=0,
|
||||
// anything before that non-zero physical address should be
|
||||
// the *real* virt=0. Mostly affects RPFs.
|
||||
} else /*if (d->dpfHeader.magic == DPF_MAGIC)*/ {
|
||||
// DPF: Load into a buffer, then convert from DPF to RPF.
|
||||
unique_ptr<DpfEntry[]> dpf_entry_buf(new DpfEntry[d->dpfHeader.entry_count]);
|
||||
const size_t load_size = d->dpfHeader.entry_count * sizeof(DpfEntry);
|
||||
size_t size = m_file->seekAndRead(d->dpfHeader.entry_table_offset, dpf_entry_buf.get(), load_size);
|
||||
if (load_size != size) {
|
||||
// Load error.
|
||||
d->entries.clear();
|
||||
m_file.reset();
|
||||
m_lastError = EIO;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Use pointer arithmetic?
|
||||
for (unsigned int i = 0; i < d->dpfHeader.entry_count; i++) {
|
||||
d->entries[i].virt_offset = static_cast<uint64_t>(dpf_entry_buf[i].virt_offset);
|
||||
d->entries[i].phys_offset = static_cast<uint64_t>(dpf_entry_buf[i].phys_offset);
|
||||
d->entries[i].size = dpf_entry_buf[i].size;
|
||||
d->entries[i].unknown_14 = dpf_entry_buf[i].unknown_0C;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure entries are sorted by virtual address.
|
||||
// TODO: Remove zero-length entries?
|
||||
std::sort(d->entries.begin(), d->entries.end(),
|
||||
[](const RpfEntry &a, const RpfEntry &b) {
|
||||
return (a.virt_offset < b.virt_offset);
|
||||
});
|
||||
|
||||
// Disc size is the highest virtual address, plus size.
|
||||
const auto &last_entry = *(d->entries.crbegin());
|
||||
d->disc_size = last_entry.virt_offset + last_entry.size;
|
||||
|
||||
// Reset the disc position.
|
||||
d->pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a disc image supported by this class?
|
||||
* @param pHeader Disc image header.
|
||||
* @param szHeader Size of header.
|
||||
* @return Class-specific disc format ID (>= 0) if supported; -1 if not.
|
||||
*/
|
||||
int DpfReader::isDiscSupported_static(const uint8_t *pHeader, size_t szHeader)
|
||||
{
|
||||
if (szHeader < sizeof(DpfHeader)) {
|
||||
// Not enough data to check.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check the DPF/RPF magic.
|
||||
const DpfHeader *const dpfHeader = reinterpret_cast<const DpfHeader*>(pHeader);
|
||||
if (dpfHeader->magic != cpu_to_le32(DPF_MAGIC) &&
|
||||
dpfHeader->magic != cpu_to_le32(RPF_MAGIC))
|
||||
{
|
||||
// Invalid magic.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allow up to 65,535 entries.
|
||||
const uint32_t entry_count = le32_to_cpu(dpfHeader->entry_count);
|
||||
assert(entry_count > 0);
|
||||
assert(entry_count <= 65535);
|
||||
if (entry_count == 0 || entry_count > 65535) {
|
||||
// No entries, or too many entries.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// This is a valid DPF or RPF image.
|
||||
// TODO: More checks.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a disc image supported by this object?
|
||||
* @param pHeader Disc image header.
|
||||
* @param szHeader Size of header.
|
||||
* @return Class-specific system ID (>= 0) if supported; -1 if not.
|
||||
*/
|
||||
int DpfReader::isDiscSupported(const uint8_t *pHeader, size_t szHeader) const
|
||||
{
|
||||
return isDiscSupported_static(pHeader, szHeader);
|
||||
}
|
||||
|
||||
/** IDiscReader functions **/
|
||||
|
||||
/**
|
||||
* Read data from the disc image.
|
||||
* @param ptr Output data buffer.
|
||||
* @param size Amount of data to read, in bytes.
|
||||
* @return Number of bytes read.
|
||||
*/
|
||||
size_t DpfReader::read(void *ptr, size_t size)
|
||||
{
|
||||
RP_D(DpfReader);
|
||||
//printf("read: d->disc_size == %ld, d->pos == %08lX, ptr == %p, size == %zX\n", d->disc_size, (size_t)d->pos, ptr, size);
|
||||
assert(m_file != nullptr);
|
||||
assert(d->disc_size > 0);
|
||||
assert(d->pos >= 0);
|
||||
if (!m_file || d->disc_size <= 0 || d->pos < 0) {
|
||||
// Disc image wasn't initialized properly.
|
||||
m_lastError = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t *ptr8 = static_cast<uint8_t*>(ptr);
|
||||
size_t ret = 0;
|
||||
|
||||
// Are we already at the end of the disc?
|
||||
if (d->pos >= d->disc_size) {
|
||||
// End of the disc.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure d->pos + size <= d->disc_size.
|
||||
// If it isn't, we'll do a short read.
|
||||
if (d->pos + static_cast<off64_t>(size) >= d->disc_size) {
|
||||
size = static_cast<size_t>(d->disc_size - d->pos);
|
||||
}
|
||||
|
||||
while (size > 0) {
|
||||
uint64_t virt_start = 0;
|
||||
off64_t phys_offset = -1;
|
||||
size_t virt_size = 0;
|
||||
|
||||
// Find the sparse entry for the current position.
|
||||
// TODO: Cache it for d->pos?
|
||||
for (const auto &p : d->entries) {
|
||||
if (p.size == 0)
|
||||
continue;
|
||||
if (d->pos < static_cast<int64_t>(p.virt_offset))
|
||||
continue;
|
||||
if (d->pos >= static_cast<int64_t>(p.virt_offset + p.size)) {
|
||||
// No matching sparse entry found. This is a zero section.
|
||||
// Set fake virt_start and virt_end, with phys_offset = -1.
|
||||
virt_start = d->pos;
|
||||
virt_size = static_cast<size_t>(p.size);
|
||||
phys_offset = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Found the sparse entry for the current position.
|
||||
virt_start = p.virt_offset;
|
||||
virt_size = static_cast<size_t>(p.size);
|
||||
phys_offset = p.phys_offset;
|
||||
|
||||
// Seek to the current physical position.
|
||||
m_file->seek(phys_offset + (d->pos - virt_start) + d->dpfHeader.data_offset);
|
||||
}
|
||||
if (virt_size == 0) {
|
||||
// Section not found...
|
||||
break;
|
||||
}
|
||||
|
||||
//printf("*** d->pos == %ld; m_file->tell()_== %08lX, size = %zX\n", d->pos, (size_t)m_file->tell(), size);
|
||||
// Read up to virt_size bytes.
|
||||
if (phys_offset < 0) {
|
||||
// Zero section
|
||||
const size_t to_zero = std::min(size, virt_size);
|
||||
// FIXME: memset() here is causing an ICE in gcc-13.2.0.
|
||||
//memset(ptr8, 0, to_zero);
|
||||
//ptr8 += to_zero;
|
||||
for (size_t i = to_zero; i > 0; i--) {
|
||||
*ptr8++ = 0;
|
||||
}
|
||||
size -= to_zero;
|
||||
d->pos += to_zero;
|
||||
ret += to_zero;
|
||||
} else {
|
||||
// Data section
|
||||
size_t to_read = std::min(size, virt_size - static_cast<size_t>(d->pos - virt_start));
|
||||
size_t has_read = m_file->read(ptr8, to_read);
|
||||
size -= has_read;
|
||||
ptr8 += has_read;
|
||||
d->pos += has_read;
|
||||
ret += has_read;
|
||||
if (has_read != to_read) {
|
||||
// Short read?
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finished reading the data.
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the disc image position.
|
||||
* @param pos disc image position.
|
||||
* @return 0 on success; -1 on error.
|
||||
*/
|
||||
int DpfReader::seek(off64_t pos)
|
||||
{
|
||||
RP_D(DpfReader);
|
||||
assert(m_file != nullptr);
|
||||
assert(d->disc_size > 0);
|
||||
assert(d->pos >= 0);
|
||||
if (!m_file || d->disc_size <= 0 || d->pos < 0) {
|
||||
// Disc image wasn't initialized properly.
|
||||
m_lastError = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle out-of-range cases.
|
||||
if (pos < 0) {
|
||||
// Negative is invalid.
|
||||
m_lastError = EINVAL;
|
||||
return -1;
|
||||
} else if (pos >= d->disc_size) {
|
||||
d->pos = d->disc_size;
|
||||
} else {
|
||||
d->pos = pos;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disc image position.
|
||||
* @return Disc image position on success; -1 on error.
|
||||
*/
|
||||
off64_t DpfReader::tell(void)
|
||||
{
|
||||
RP_D(const DpfReader);
|
||||
assert(m_file != nullptr);
|
||||
assert(d->disc_size > 0);
|
||||
assert(d->pos >= 0);
|
||||
if (!m_file || d->disc_size <= 0 || d->pos < 0) {
|
||||
// Disc image wasn't initialized properly.
|
||||
m_lastError = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return d->pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disc image size.
|
||||
* @return Disc image size, or -1 on error.
|
||||
*/
|
||||
off64_t DpfReader::size(void)
|
||||
{
|
||||
RP_D(const DpfReader);
|
||||
assert(m_file != nullptr);
|
||||
assert(d->disc_size > 0);
|
||||
assert(d->pos >= 0);
|
||||
if (!m_file || d->disc_size <= 0 || d->pos < 0) {
|
||||
// Disc image wasn't initialized properly.
|
||||
m_lastError = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return d->disc_size;
|
||||
}
|
||||
|
||||
}
|
90
src/libromdata/disc/DpfReader.hpp
Normal file
90
src/libromdata/disc/DpfReader.hpp
Normal file
@ -0,0 +1,90 @@
|
||||
/***************************************************************************
|
||||
* ROM Properties Page shell extension. (libromdata) *
|
||||
* DpfReader.hpp: GameCube/Wii DPF/RPF sparse disc image reader. *
|
||||
* *
|
||||
* Copyright (c) 2016-2024 by David Korth. *
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later *
|
||||
***************************************************************************/
|
||||
|
||||
// NOTE: This class does NOT derive from SparseDiscReader because
|
||||
// DPF/RPF uses byte-granularity, not block-granularity.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "librpbase/disc/IDiscReader.hpp"
|
||||
|
||||
namespace LibRomData {
|
||||
|
||||
class DpfReaderPrivate;
|
||||
class DpfReader : public LibRpBase::IDiscReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct a DpfReader with the specified file.
|
||||
* The file is ref()'d, so the original file can be
|
||||
* unref()'d by the caller afterwards.
|
||||
* @param file File to read from.
|
||||
*/
|
||||
explicit DpfReader(const LibRpFile::IRpFilePtr &file);
|
||||
|
||||
private:
|
||||
typedef IDiscReader super;
|
||||
RP_DISABLE_COPY(DpfReader)
|
||||
private:
|
||||
friend class DpfReaderPrivate;
|
||||
DpfReaderPrivate *const d_ptr;
|
||||
|
||||
public:
|
||||
/** Disc image detection functions **/
|
||||
|
||||
/**
|
||||
* Is a disc image supported by this class?
|
||||
* @param pHeader Disc image header.
|
||||
* @param szHeader Size of header.
|
||||
* @return Class-specific disc format ID (>= 0) if supported; -1 if not.
|
||||
*/
|
||||
ATTR_ACCESS_SIZE(read_only, 1, 2)
|
||||
static int isDiscSupported_static(const uint8_t *pHeader, size_t szHeader);
|
||||
|
||||
/**
|
||||
* Is a disc image supported by this object?
|
||||
* @param pHeader Disc image header.
|
||||
* @param szHeader Size of header.
|
||||
* @return Class-specific disc format ID (>= 0) if supported; -1 if not.
|
||||
*/
|
||||
ATTR_ACCESS_SIZE(read_only, 2, 3)
|
||||
int isDiscSupported(const uint8_t *pHeader, size_t szHeader) const final;
|
||||
|
||||
public:
|
||||
/** IDiscReader functions **/
|
||||
|
||||
/**
|
||||
* Read data from the disc image.
|
||||
* @param ptr Output data buffer.
|
||||
* @param size Amount of data to read, in bytes.
|
||||
* @return Number of bytes read.
|
||||
*/
|
||||
ATTR_ACCESS_SIZE(write_only, 2, 3)
|
||||
size_t read(void *ptr, size_t size) final;
|
||||
|
||||
/**
|
||||
* Set the disc image position.
|
||||
* @param pos disc image position.
|
||||
* @return 0 on success; -1 on error.
|
||||
*/
|
||||
int seek(off64_t pos) final;
|
||||
|
||||
/**
|
||||
* Get the disc image position.
|
||||
* @return Disc image position on success; -1 on error.
|
||||
*/
|
||||
off64_t tell(void) final;
|
||||
|
||||
/**
|
||||
* Get the disc image size.
|
||||
* @return Disc image size, or -1 on error.
|
||||
*/
|
||||
off64_t size(void) final;
|
||||
};
|
||||
|
||||
}
|
68
src/libromdata/disc/dpf_structs.h
Normal file
68
src/libromdata/disc/dpf_structs.h
Normal file
@ -0,0 +1,68 @@
|
||||
/***************************************************************************
|
||||
* ROM Properties Page shell extension. (libromdata) *
|
||||
* dpf_structs.h: GameCube/Wii DPF/RPF structs. *
|
||||
* *
|
||||
* Copyright (c) 2016-2024 by David Korth. *
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later *
|
||||
***************************************************************************/
|
||||
|
||||
// DPF/RPF is a sparse format used by the official GameCube and Wii SDKs.
|
||||
// These structs were identified using reverse-engineering of existing DPF and RPF files.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/**
|
||||
* DPF/RPF header.
|
||||
*
|
||||
* All fields are in little-endian.
|
||||
*/
|
||||
#define DPF_MAGIC 0x23FC3E86
|
||||
#define RPF_MAGIC 0xE0F92B6A
|
||||
typedef struct _DpfHeader {
|
||||
uint32_t magic; // [0x000] DPF/RPF magic number
|
||||
uint32_t version; // [0x004] Version, usually 0
|
||||
uint32_t header_size; // [0x008] Size of this header (usually 32, 0x20)
|
||||
uint32_t unknown_0C; // [0x00C] Unknown, usually 0
|
||||
uint32_t entry_table_offset; // [0x010] Offset to the entry table (usually 32, 0x20)
|
||||
uint32_t entry_count; // [0x014] Number of sparse table entries
|
||||
uint32_t data_offset; // [0x018] Offset to the beginning of the actual data
|
||||
uint32_t unknown_1C; // [0x01C] Unknown (unless data_offset is actually 64-bit?)
|
||||
} DpfHeader;
|
||||
ASSERT_STRUCT(DpfHeader, 32);
|
||||
|
||||
/**
|
||||
* DPF entry.
|
||||
*
|
||||
* All fields are in little-endian.
|
||||
*/
|
||||
typedef struct _DpfEntry {
|
||||
uint32_t virt_offset; // [0x000] Virtual offset inside the logical disc image
|
||||
uint32_t phys_offset; // [0x004] Physical offset inside the sparse file
|
||||
uint32_t size; // [0x008] Size of this block, in bytes
|
||||
uint32_t unknown_0C; // [0x00C] Unknown (usually 0; may be 1 for zero-length blocks?)
|
||||
} DpfEntry;
|
||||
ASSERT_STRUCT(DpfEntry, 16);
|
||||
|
||||
/**
|
||||
* RPF entry.
|
||||
*
|
||||
* All fields are in little-endian.
|
||||
*/
|
||||
typedef struct _RpfEntry {
|
||||
uint64_t virt_offset; // [0x000] Virtual offset inside the logical disc image
|
||||
uint64_t phys_offset; // [0x008] Physical offset inside the sparse file
|
||||
uint32_t size; // [0x010] Size of this block, in bytes
|
||||
uint32_t unknown_14; // [0x014] Unknown (usually 0; may be 1 for zero-length blocks?)
|
||||
} RpfEntry;
|
||||
ASSERT_STRUCT(RpfEntry, 24);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
@ -43,6 +43,8 @@ application/x-wia # GameCube
|
||||
application/x-cso # GameCube
|
||||
application/x-nasos-image # GameCube
|
||||
application/x-gcz-image # GameCube
|
||||
application/x-dpf-image # GameCube
|
||||
application/x-rpf-image # GameCube
|
||||
application/x-rvz-image # GameCube
|
||||
application/x-gamecube-bnr # GameCubeBNR
|
||||
application/x-gamecube-save # GameCubeSave
|
||||
|
@ -292,6 +292,24 @@
|
||||
<match value="0xb10bc001" type="big32" offset="0"/>
|
||||
</magic>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-dpf-image">
|
||||
<comment>Sparse GameCube disc image</comment>
|
||||
<sub-class-of type="application/x-gamecube-rom"/>
|
||||
<generic-icon name="application-x-cd-image"/>
|
||||
<glob pattern="*.dpf"/>
|
||||
<magic priority="50">
|
||||
<match value="0x23fc3e86" type="little32" offset="0"/>
|
||||
</magic>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-rpf-image">
|
||||
<comment>Sparse Wii disc image</comment>
|
||||
<sub-class-of type="application/x-wii-rom"/>
|
||||
<generic-icon name="application-x-cd-image"/>
|
||||
<glob pattern="*.rpf"/>
|
||||
<magic priority="50">
|
||||
<match value="0xe0f92b6a" type="little32" offset="0"/>
|
||||
</magic>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-wia">
|
||||
<comment>Compressed GameCube/Wii disc image</comment>
|
||||
<sub-class-of type="application/x-wii-rom"/>
|
||||
|
Loading…
Reference in New Issue
Block a user