mirror of
https://github.com/GerbilSoft/rom-properties.git
synced 2025-06-18 19:45:41 -04:00
[librpbase] IsoPartition: Implement opendir(), readdir(), and closedir().
Tested using VBoxGuestAdditions.iso from VirtualBox 7.1.8. Skip subdirectories with names "\x00" and "\x01". These are Joliet "special directory identifiers". IFst: - Change `dir_idx` from int to intptr_t. - Add two explicit constructors with a `dir_idx` parameter. One takes intptr_t; the other takes void*. - Added an `extra` parameter for temporary filename storage. The ISO-9660 directory entry does not have NULL termination for the filename, so we need the temporary buffer to add the terminator. GcnFst, WiiUFst: - Update for the `dir_idx` constructor parameter. - Zero out the `extra` parameter. TODO: - Move it to a new IFst subclass, IsoFst? - Remove the file version number, e.g. ";1"? - Handle "/NT3x/". Currently, "/NT3x" works, but the trailing slash confuses the directory lookup function. - Joliet subdirectory support?
This commit is contained in:
parent
6b30dd5b70
commit
c7ccd7a39a
@ -422,14 +422,15 @@ IFst::Dir *GcnFst::opendir(const char *path)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IFst::Dir *dirp = new IFst::Dir(this);
|
||||
d->fstDirCount++;
|
||||
// TODO: Better way to get dir_idx?
|
||||
dirp->dir_idx = static_cast<int>(fst_entry - d->fstData);
|
||||
const int dir_idx = static_cast<int>(fst_entry - d->fstData);
|
||||
IFst::Dir *dirp = new IFst::Dir(this, dir_idx);
|
||||
d->fstDirCount++;
|
||||
|
||||
// Initialize the entry to this directory.
|
||||
// readdir() will automatically seek to the next entry.
|
||||
dirp->entry.ptnum = 0; // not used for GCN/Wii
|
||||
dirp->entry.extra = nullptr; // not used for GCN/Wii
|
||||
dirp->entry.ptnum = 0; // not used for GCN/Wii
|
||||
dirp->entry.idx = dirp->dir_idx;
|
||||
dirp->entry.type = DT_DIR;
|
||||
dirp->entry.name = d->entry_name(fst_entry);
|
||||
|
@ -27,6 +27,7 @@ class IsoPartitionPrivate
|
||||
{
|
||||
public:
|
||||
IsoPartitionPrivate(IsoPartition *q, off64_t partition_offset, int iso_start_offset);
|
||||
~IsoPartitionPrivate();
|
||||
|
||||
private:
|
||||
RP_DISABLE_COPY(IsoPartitionPrivate)
|
||||
@ -53,9 +54,12 @@ public:
|
||||
// -1 == unknown
|
||||
int iso_start_offset;
|
||||
|
||||
// IFst::Dir* reference counter
|
||||
int fstDirCount;
|
||||
|
||||
/**
|
||||
* Find the last slash or backslash in a path.
|
||||
* @param path Path.
|
||||
* @param path Path
|
||||
* @return Last slash or backslash, or nullptr if not found.
|
||||
*/
|
||||
static inline const char *findLastSlash(const char *path)
|
||||
@ -90,14 +94,14 @@ public:
|
||||
/**
|
||||
* Look up a directory entry from a filename.
|
||||
* @param filename Filename [UTF-8]
|
||||
* @return ISO directory entry.
|
||||
* @return ISO directory entry
|
||||
*/
|
||||
const ISO_DirEntry *lookup(const char *filename);
|
||||
|
||||
/**
|
||||
* Parse an ISO-9660 timestamp.
|
||||
* @param isofiletime File timestamp.
|
||||
* @return Unix time.
|
||||
* @param isofiletime File timestamp
|
||||
* @return Unix time
|
||||
*/
|
||||
static time_t parseTimestamp(const ISO_Dir_DateTime_t *isofiletime);
|
||||
};
|
||||
@ -110,6 +114,7 @@ IsoPartitionPrivate::IsoPartitionPrivate(IsoPartition *q,
|
||||
, partition_offset(partition_offset)
|
||||
, partition_size(0)
|
||||
, iso_start_offset(iso_start_offset)
|
||||
, fstDirCount(0)
|
||||
{
|
||||
// Clear the PVD struct.
|
||||
memset(&pvd, 0, sizeof(pvd));
|
||||
@ -152,6 +157,11 @@ IsoPartitionPrivate::IsoPartitionPrivate(IsoPartition *q,
|
||||
getDirectory("/");
|
||||
}
|
||||
|
||||
IsoPartitionPrivate::~IsoPartitionPrivate()
|
||||
{
|
||||
assert(fstDirCount == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a directory entry from a base filename and directory.
|
||||
* @param pDir [in] Directory
|
||||
@ -169,10 +179,26 @@ const ISO_DirEntry *IsoPartitionPrivate::lookup_int(const DirData_t *pDir, const
|
||||
const ISO_DirEntry *dirEntry_found = nullptr;
|
||||
const uint8_t *p = pDir->data();
|
||||
const uint8_t *const p_end = p + pDir->size();
|
||||
while (p < p_end) {
|
||||
while ((p + sizeof(ISO_DirEntry)) < p_end) {
|
||||
const ISO_DirEntry *dirEntry = reinterpret_cast<const ISO_DirEntry*>(p);
|
||||
if (dirEntry->entry_length < sizeof(*dirEntry)) {
|
||||
// End of directory.
|
||||
if (dirEntry->entry_length == 0) {
|
||||
// Directory entries cannot span multiple sectors in
|
||||
// multi-sector directories, so if needed, the rest
|
||||
// of the sector is padded with 00.
|
||||
// Find the next non-zero byte.
|
||||
for (p++; p < p_end; p++) {
|
||||
if (*p != '\0') {
|
||||
// Found a non-zero byte.
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p >= p_end) {
|
||||
// No more non-zero bytes.
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else if (dirEntry->entry_length < sizeof(*dirEntry)) {
|
||||
// Invalid directory entry?
|
||||
break;
|
||||
}
|
||||
|
||||
@ -182,6 +208,17 @@ const ISO_DirEntry *IsoPartitionPrivate::lookup_int(const DirData_t *pDir, const
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip subdirectories with names "\x00" and "\x01".
|
||||
// These are Joliet "special directory identifiers".
|
||||
// TODO: Joliet subdirectory support?
|
||||
if ((dirEntry->flags & ISO_FLAG_DIRECTORY) && dirEntry->filename_length == 1) {
|
||||
if (static_cast<uint8_t>(entry_filename[0]) <= 0x01) {
|
||||
// Skip this filename.
|
||||
p += dirEntry->entry_length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the filename.
|
||||
// 1990s and early 2000s CD-ROM games usually have
|
||||
// ";1" filenames, so check for that first.
|
||||
@ -301,6 +338,7 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
|
||||
// in which case, we'll need to assume that the
|
||||
// root directory starts at block 20.
|
||||
// TODO: Better heuristics.
|
||||
// TODO: Find the block that starts with "CD001" instead of this heuristic.
|
||||
if (rootdir->block.he < 20) {
|
||||
// Starting block is invalid.
|
||||
q->m_lastError = EIO;
|
||||
@ -371,7 +409,7 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
|
||||
/**
|
||||
* Look up a directory entry from a filename.
|
||||
* @param filename Filename [UTF-8]
|
||||
* @return ISO directory entry.
|
||||
* @return ISO directory entry
|
||||
*/
|
||||
const ISO_DirEntry *IsoPartitionPrivate::lookup(const char *filename)
|
||||
{
|
||||
@ -419,8 +457,8 @@ const ISO_DirEntry *IsoPartitionPrivate::lookup(const char *filename)
|
||||
|
||||
/**
|
||||
* Parse an ISO-9660 timestamp.
|
||||
* @param isofiletime File timestamp.
|
||||
* @return Unix time.
|
||||
* @param isofiletime File timestamp
|
||||
* @return Unix time
|
||||
*/
|
||||
time_t IsoPartitionPrivate::parseTimestamp(const ISO_Dir_DateTime_t *isofiletime)
|
||||
{
|
||||
@ -589,68 +627,181 @@ off64_t IsoPartition::partition_size_used(void) const
|
||||
|
||||
/** IsoPartition **/
|
||||
|
||||
/** GcnFst wrapper functions. **/
|
||||
/** IFst wrapper functions **/
|
||||
|
||||
// TODO
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* Open a directory.
|
||||
* @param path [in] Directory path.
|
||||
* @param path [in] Directory path
|
||||
* @return IFst::Dir*, or nullptr on error.
|
||||
*/
|
||||
IFst::Dir *IsoPartition::opendir(const char *path)
|
||||
{
|
||||
RP_D(IsoPartition);
|
||||
if (!d->fst) {
|
||||
// FST isn't loaded.
|
||||
if (d->loadFst() != 0) {
|
||||
// FST load failed.
|
||||
// TODO: Errors?
|
||||
return nullptr;
|
||||
}
|
||||
const IsoPartitionPrivate::DirData_t *const pDir = d->getDirectory(path);
|
||||
if (!pDir) {
|
||||
// Path not found.
|
||||
// TODO: Return an error code?
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return d->fst->opendir(path);
|
||||
// FIXME: Create an IsoFst class? Cannot pass `this` as IFst*.
|
||||
IFst::Dir *const dirp = new IFst::Dir(nullptr, (void*)pDir);
|
||||
d->fstDirCount++;
|
||||
|
||||
// Initialize the entry to this directory.
|
||||
// readdir() will automatically seek to the next entry.
|
||||
dirp->entry.extra = nullptr; // temporary filename storage
|
||||
dirp->entry.ptnum = 0; // not used for ISO
|
||||
dirp->entry.idx = 0;
|
||||
dirp->entry.type = DT_UNKNOWN;
|
||||
dirp->entry.name = nullptr;
|
||||
// offset and size are not valid for directories.
|
||||
dirp->entry.offset = 0;
|
||||
dirp->entry.size = 0;
|
||||
|
||||
// Return the IFst::Dir*.
|
||||
return dirp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a directory entry.
|
||||
* @param dirp FstDir pointer.
|
||||
* @param dirp FstDir pointer
|
||||
* @return IFst::DirEnt*, or nullptr if end of directory or on error.
|
||||
* (TODO: Add lastError()?)
|
||||
*/
|
||||
IFst::DirEnt *IsoPartition::readdir(IFst::Dir *dirp)
|
||||
const IFst::DirEnt *IsoPartition::readdir(IFst::Dir *dirp)
|
||||
{
|
||||
RP_D(IsoPartition);
|
||||
if (!d->fst) {
|
||||
// TODO: Errors?
|
||||
assert(dirp != nullptr);
|
||||
//assert(dirp->parent == this);
|
||||
if (!dirp /*|| dirp->parent != this*/) {
|
||||
// No directory pointer, or the dirp
|
||||
// doesn't belong to this IFst.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return d->fst->readdir(dirp);
|
||||
const IsoPartitionPrivate::DirData_t *const pDir =
|
||||
reinterpret_cast<const IsoPartitionPrivate::DirData_t*>(dirp->dir_idx);
|
||||
const uint8_t *p = pDir->data();
|
||||
const uint8_t *const p_end = p + pDir->size();
|
||||
p += dirp->entry.idx;
|
||||
|
||||
// NOTE: Using a loop in order to skip files that aren't really files.
|
||||
const ISO_DirEntry *dirEntry = nullptr;
|
||||
const char *entry_filename = nullptr;
|
||||
while (p < p_end) {
|
||||
dirEntry = reinterpret_cast<const ISO_DirEntry*>(p);
|
||||
if (dirEntry->entry_length == 0) {
|
||||
// Directory entries cannot span multiple sectors in
|
||||
// multi-sector directories, so if needed, the rest
|
||||
// of the sector is padded with 00.
|
||||
// Find the next non-zero byte.
|
||||
for (p++; p < p_end; p++) {
|
||||
if (*p != '\0') {
|
||||
// Found a non-zero byte.
|
||||
dirp->entry.idx = static_cast<int>(p - pDir->data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p >= p_end) {
|
||||
// No more non-zero bytes.
|
||||
dirp->entry.idx = static_cast<int>(pDir->size());
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else if (dirEntry->entry_length < sizeof(*dirEntry)) {
|
||||
// Invalid directory entry?
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
entry_filename = reinterpret_cast<const char*>(p) + sizeof(*dirEntry);
|
||||
if (entry_filename + dirEntry->filename_length > reinterpret_cast<const char*>(p_end)) {
|
||||
// Filename is out of bounds.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Skip subdirectories with names "\x00" and "\x01".
|
||||
// These are Joliet "special directory identifiers".
|
||||
// TODO: Joliet subdirectory support?
|
||||
if ((dirEntry->flags & ISO_FLAG_DIRECTORY) && dirEntry->filename_length == 1) {
|
||||
if (static_cast<uint8_t>(entry_filename[0]) <= 0x01) {
|
||||
// Skip this filename.
|
||||
dirp->entry.idx += dirEntry->entry_length;
|
||||
p += dirEntry->entry_length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Found a valid file.
|
||||
break;
|
||||
}
|
||||
if (!dirEntry) {
|
||||
// Could not find a valid file. (End of directory?)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const bool isDir = !!(dirEntry->flags & ISO_FLAG_DIRECTORY);
|
||||
if (isDir) {
|
||||
// Technically, offset/size are valid for directories on ISO-9660,
|
||||
// but we're going to set them to 0.
|
||||
dirp->entry.type = DT_DIR;
|
||||
dirp->entry.offset = 0;
|
||||
dirp->entry.size = 0;
|
||||
} else {
|
||||
RP_D(IsoPartition);
|
||||
const unsigned int block_size = d->pvd.logical_block_size.he;
|
||||
dirp->entry.type = DT_REG;
|
||||
dirp->entry.offset = static_cast<off64_t>(dirEntry->block.he) * block_size;
|
||||
dirp->entry.size = dirEntry->size.he;
|
||||
}
|
||||
|
||||
// NOTE: Need to copy the filename in order to have NULL-termination.
|
||||
// TODO: Remove ";1" from the filename, if present?
|
||||
char *extra = static_cast<char*>(dirp->entry.extra);
|
||||
delete[] extra;
|
||||
extra = new char[dirEntry->filename_length + 1];
|
||||
memcpy(extra, entry_filename, dirEntry->filename_length);
|
||||
extra[dirEntry->filename_length] = '\0';
|
||||
|
||||
dirp->entry.name = extra;
|
||||
dirp->entry.extra = extra;
|
||||
|
||||
// Next file entry.
|
||||
dirp->entry.idx += dirEntry->entry_length;
|
||||
|
||||
return &dirp->entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close an opened directory.
|
||||
* @param dirp FstDir pointer.
|
||||
* @param dirp FstDir pointer
|
||||
* @return 0 on success; negative POSIX error code on error.
|
||||
*/
|
||||
int IsoPartition::closedir(IFst::Dir *dirp)
|
||||
{
|
||||
RP_D(IsoPartition);
|
||||
if (!d->fst) {
|
||||
// TODO: Errors?
|
||||
return -EBADF;
|
||||
}
|
||||
assert(dirp != nullptr);
|
||||
//assert(dirp->parent == this);
|
||||
if (!dirp) {
|
||||
// No directory pointer.
|
||||
// In release builds, this is a no-op.
|
||||
return 0;
|
||||
} /*else if (dirp->parent != this) {
|
||||
// The dirp doesn't belong to this IFst.
|
||||
return -EINVAL;
|
||||
}*/
|
||||
|
||||
return d->fst->closedir(dirp);
|
||||
RP_D(IsoPartition);
|
||||
assert(d->fstDirCount > 0);
|
||||
if (dirp->entry.extra) {
|
||||
delete[] static_cast<char*>(dirp->entry.extra);
|
||||
}
|
||||
delete dirp;
|
||||
d->fstDirCount--;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Open a file. (read-only)
|
||||
* @param filename Filename.
|
||||
* @param filename Filename
|
||||
* @return IRpFile*, or nullptr on error.
|
||||
*/
|
||||
IRpFilePtr IsoPartition::open(const char *filename)
|
||||
@ -707,9 +858,11 @@ IRpFilePtr IsoPartition::open(const char *filename)
|
||||
return std::make_shared<PartitionFile>(this->shared_from_this(), file_addr, dirEntry->size.he);
|
||||
}
|
||||
|
||||
/** IsoPartition-specific functions **/
|
||||
|
||||
/**
|
||||
* Get a file's timestamp.
|
||||
* @param filename Filename.
|
||||
* @param filename Filename
|
||||
* @return Timestamp, or -1 on error.
|
||||
*/
|
||||
time_t IsoPartition::get_mtime(const char *filename)
|
||||
|
@ -90,41 +90,41 @@ public:
|
||||
public:
|
||||
/** IFst wrapper functions **/
|
||||
|
||||
// TODO
|
||||
#if 0
|
||||
/**
|
||||
* Open a directory.
|
||||
* @param path [in] Directory path.
|
||||
* @param path [in] Directory path
|
||||
* @return IFst::Dir*, or nullptr on error.
|
||||
*/
|
||||
LibRpBase::IFst::Dir *opendir(const char *path) final;
|
||||
|
||||
/**
|
||||
* Read a directory entry.
|
||||
* @param dirp IFst::Dir pointer.
|
||||
* @param dirp IFst::Dir pointer
|
||||
* @return IFst::DirEnt, or nullptr if end of directory or on error.
|
||||
* (TODO: Add lastError()?)
|
||||
*/
|
||||
LibRpBase::IFst::DirEnt *readdir(LibRpBase::IFst::Dir *dirp) final;
|
||||
const LibRpBase::IFst::DirEnt *readdir(LibRpBase::IFst::Dir *dirp) final;
|
||||
|
||||
/**
|
||||
* Close an opened directory.
|
||||
* @param dirp IFst::Dir pointer.
|
||||
* @param dirp IFst::Dir pointer
|
||||
* @return 0 on success; negative POSIX error code on error.
|
||||
*/
|
||||
int closedir(LibRpBase::IFst::Dir *dirp) final;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Open a file. (read-only)
|
||||
* @param filename Filename.
|
||||
* @param filename Filename
|
||||
* @return IRpFile*, or nullptr on error.
|
||||
*/
|
||||
LibRpFile::IRpFilePtr open(const char *filename) final;
|
||||
|
||||
public:
|
||||
/** IsoPartition-specific functions **/
|
||||
|
||||
/**
|
||||
* Get a file's timestamp.
|
||||
* @param filename Filename.
|
||||
* @param filename Filename
|
||||
* @return Timestamp, or -1 on error.
|
||||
*/
|
||||
time_t get_mtime(const char *filename);
|
||||
|
@ -467,13 +467,14 @@ IFst::Dir *WiiUFst::opendir(const char *path)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IFst::Dir *dirp = new IFst::Dir(this);
|
||||
d->fstDirCount++;
|
||||
// TODO: Better way to get dir_idx?
|
||||
dirp->dir_idx = static_cast<int>(fst_entry - d->fstEntries);
|
||||
const int dir_idx = static_cast<int>(fst_entry - d->fstEntries);
|
||||
IFst::Dir *dirp = new IFst::Dir(this, dir_idx);
|
||||
d->fstDirCount++;
|
||||
|
||||
// Initialize the entry to this directory.
|
||||
// readdir() will automatically seek to the next entry.
|
||||
dirp->entry.extra = nullptr; // not used for Wii U
|
||||
dirp->entry.ptnum = be16_to_cpu(fst_entry->storage_cluster_index);
|
||||
dirp->entry.idx = dirp->dir_idx;
|
||||
dirp->entry.type = DT_DIR;
|
||||
|
@ -57,6 +57,7 @@ public:
|
||||
const char *name; // Filename
|
||||
|
||||
// TODO: Additional placeholders?
|
||||
void *extra; // Extra data
|
||||
unsigned int ptnum; // Partition or content number
|
||||
int idx; // File index
|
||||
uint8_t type; // File type (See d_type.h)
|
||||
@ -64,11 +65,17 @@ public:
|
||||
|
||||
struct Dir {
|
||||
IFst *const parent; // IFst that owns this Dir
|
||||
int dir_idx; // Directory index in the FST
|
||||
intptr_t dir_idx; // Directory index in the FST
|
||||
DirEnt entry; // Current DirEnt
|
||||
|
||||
explicit Dir(IFst *parent)
|
||||
explicit Dir(IFst *parent, intptr_t dir_idx)
|
||||
: parent(parent)
|
||||
, dir_idx(dir_idx)
|
||||
{}
|
||||
|
||||
explicit Dir(IFst *parent, void *dir_idx)
|
||||
: parent(parent)
|
||||
, dir_idx(reinterpret_cast<intptr_t>(dir_idx))
|
||||
{}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user