Files
ppsspp/Core/FileSystems/ISOFileSystem.cpp
T
libretroadmin d56b453891 Replace zlib, zstd, libchdr, libzip and libpng with libretro-common
Deflate, zlib and gzip streams go through rinflate and rdeflate, zstd
through rzstd, CHD images through rchd, zip archives through
rzip_archive and PNG through rpng. The bundled libraries, their build
glue and the system-library options are gone; adler32 and uncompress
keep zlib's ABI for the prebuilt FFmpeg archives that still call them.
libretro-common is one CMake library that the core, the headless tool
and the unit tests all link.

The reads take shorter paths on the way. CHD decodes straight from the
loader's reads and a range of blocks at a time, and a file read at any
alignment decodes into place; CSO keeps one inflate stream instead of
one per block; a plain ISO serves a file read with one read of its exact
bytes; a stored zip member is read in place and a deflated one through
restart points rather than a copy of the whole image in memory; zip
members decode into the caller's buffer; sequential reads through the
frontend's VFS skip the seek, which also keeps its buffering.

libretro/test reads one image as ISO, CSO, CHD and zip through the block
devices, boots each, and checks the codecs against Python-made vectors;
a CI job runs it.
2026-10-08 14:14:38 +02:00

848 lines
26 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <ctime>
#include "Common/CommonTypes.h"
#include "Common/File/FileUtil.h" // for localtime_r on Windows
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/StringUtils.h"
#include "Core/FileSystems/ISOFileSystem.h"
#include "Core/HLE/sceKernel.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
const int sectorSize = 2048;
bool parseLBN(const std::string &filename, u32 *sectorStart, u32 *readSize) {
// The format of this is: "/sce_lbn" "0x"? HEX* ANY* "_size" "0x"? HEX* ANY*
// That means that "/sce_lbn/_size1/" is perfectly valid.
// Most commonly, it looks like /sce_lbn0x10_size0x100 or /sce_lbn10_size100 (always hex.)
// If it doesn't starts with /sce_lbn or doesn't have _size, look for a file instead.
if (!startsWith(filename, "/sce_lbn"))
return false;
const size_t size_pos = filename.find("_size");
if (size_pos == filename.npos)
return false;
// TODO: Return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT when >= 32 long but passes above checks.
if (filename.size() >= 32)
return false;
const char *filename_c = filename.c_str();
size_t pos = strlen("/sce_lbn");
if (sscanf(filename_c + pos, "%x", sectorStart) != 1)
*sectorStart = 0;
pos = size_pos + strlen("_size");
if (sscanf(filename_c + pos, "%x", readSize) != 1)
*readSize = 0;
return true;
}
#pragma pack(push)
#pragma pack(1)
struct u32_le_be_pair {
u8 valueLE[4];
u8 valueBE[4];
operator u32() const {
return valueLE[0] + (valueLE[1] << 8) + (valueLE[2] << 16) + (valueLE[3] << 24);
}
};
struct u16_le_be_pair {
u8 valueLE[2];
u8 valueBE[2];
operator u16() const {
return valueLE[0] + (valueLE[1] << 8);
}
};
struct DirectoryEntry {
u8 size;
u8 sectorsInExtendedRecord;
u32_le_be_pair firstDataSector; // LBA
u32_le_be_pair dataLength; // Size
u8 years;
u8 month;
u8 day;
u8 hour;
u8 minute;
u8 second;
u8 offsetFromGMT;
u8 flags; // 2 = directory
u8 fileUnitSize;
u8 interleaveGap;
u16_le_be_pair volSeqNumber;
u8 identifierLength; //identifier comes right after
u8 firstIdChar;
};
struct DirectorySector {
DirectoryEntry entry;
char space[2048-sizeof(DirectoryEntry)];
};
struct VolDescriptor {
char type;
char cd001[6];
char version;
char sysid[32];
char volid[32];
char zeros[8];
u32_le_be_pair numSectors;
char morezeros[32];
u16_le_be_pair volSetSize;
u16_le_be_pair volSeqNum;
u16_le_be_pair sectorSize;
u32_le_be_pair pathTableLength;
u16_le_be_pair firstLETableSector;
u16_le_be_pair secondLETableSector;
u16_le_be_pair firstBETableSector;
u16_le_be_pair secondBETableSector;
DirectoryEntry root;
char volumeSetIdentifier[128];
char publisherIdentifier[128];
char dataPreparerIdentifier[128];
char applicationIdentifier[128];
char copyrightFileIdentifier[37];
char abstractFileIdentifier[37];
char bibliographicalFileIdentifier[37];
char volCreationDateTime[17];
char mreModDateTime[17];
char volExpireDateTime[17];
char volEffectiveDateTime[17];
char one;
char zero;
char reserved[512];
char zeroos[653];
};
// From http://howardhinnant.github.io/date_algorithms.html - same one sceRtc.cpp uses. Beats
// timegm(), which isn't portable, and mktime(), which would drag the host's timezone in.
static s64 DaysFromCivil(s64 y, u32 m, u32 d) {
y -= m <= 2;
const s64 era = (y >= 0 ? y : y - 399) / 400;
const u32 yoe = (u32)(y - era * 400); // [0, 399]
const u32 doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365]
const u32 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
return era * 146097 + (s64)doe - 719468;
}
// The 7-byte date in a directory record, as Unix UTC seconds. Returns 0 if there isn't a real
// date in there - plenty of ISOs leave it zeroed, and "1900-00-00" isn't worth propagating.
static s64 UnixTimeFromDirectoryEntry(const DirectoryEntry &dir) {
if (dir.month < 1 || dir.month > 12 || dir.day < 1 || dir.day > 31) {
return 0;
}
// years is an offset from 1900, and offsetFromGMT is a signed count of 15-minute steps.
const s64 days = DaysFromCivil(1900 + (s64)dir.years, dir.month, dir.day);
return days * 86400 + dir.hour * 3600 + dir.minute * 60 + dir.second - (s64)(s8)dir.offsetFromGMT * 15 * 60;
}
// ISO9660 has one timestamp per file, so it goes into all three of the PSP's. Local time, to
// match what the other file systems report.
static void FillFileTimes(PSPFileInfo *info, s64 unixTime) {
if (unixTime == 0) {
return;
}
const time_t t = (time_t)unixTime;
localtime_r(&t, &info->mtime);
info->atime = info->ctime = info->mtime;
}
// Removes version numbers from filenames.
static std::string_view CleanISOFileName(std::string_view name) {
auto pos = name.find(';');
if (pos != name.npos) {
name = name.substr(0, pos);
}
return name;
}
#pragma pack(pop)
ISOFileSystem::ISOFileSystem(IHandleAllocator *_hAlloc, std::shared_ptr<BlockDevice> _blockDevice) {
blockDevice = _blockDevice;
hAlloc = _hAlloc;
VolDescriptor desc;
if (!blockDevice->ReadBlock(16, (u8*)&desc)) {
// TODO: This is kinda wacky.
blockDevice->NotifyReadError();
errorString_ = "ISO: error reading volume descriptor";
return;
}
if (memcmp(desc.cd001, "CD001", 5)) {
errorString_ = "ISO: missing CD001 signature";
return;
}
entireISO.name.clear();
entireISO.isDirectory = false;
entireISO.startingPosition = 0;
entireISO.size = _blockDevice->GetNumBlocks();
entireISO.flags = 0;
entireISO.parent = NULL;
treeroot = new TreeEntry();
treeroot->isDirectory = true;
treeroot->startingPosition = 0;
treeroot->size = 0;
treeroot->flags = 0;
treeroot->parent = NULL;
treeroot->valid = false;
treeroot->startsector = desc.root.firstDataSector;
treeroot->dirsize = desc.root.dataLength;
}
ISOFileSystem::~ISOFileSystem() {
delete treeroot;
}
std::string ISOFileSystem::TreeEntry::BuildPath() {
if (parent) {
return parent->BuildPath() + "/" + name;
} else {
return name;
}
}
void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
for (u32 secnum = root->startsector, endsector = root->startsector + (root->dirsize + 2047) / 2048; secnum < endsector; ++secnum) {
u8 theSector[2048];
if (!blockDevice->ReadBlock(secnum, theSector)) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Error reading block for directory '%s' in sector %d - skipping", root->name.c_str(), secnum);
root->valid = true; // Prevents re-reading
return;
}
lastReadBlock_ = secnum; // Hm, this could affect timing... but lazy loading is probably more realistic.
for (int offset = 0; offset < 2048; ) {
DirectoryEntry &dir = *(DirectoryEntry *)&theSector[offset];
u8 sz = theSector[offset];
// Nothing left in this sector. There might be more in the next one.
if (sz == 0)
break;
const int IDENTIFIER_OFFSET = 33;
if (offset + IDENTIFIER_OFFSET + dir.identifierLength > 2048) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Directory entry crosses sectors, corrupt iso?");
return;
}
// dir.size (the record length actually consumed) must cover at least its
// own header and identifier, or a crafted sector could set it to 1 and
// make the loop reinterpret the same overlapping bytes as many separate
// entries, allocating far more TreeEntry objects than the sector's real
// size warrants.
if (dir.size < IDENTIFIER_OFFSET + dir.identifierLength) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Directory entry size too small, corrupt iso?");
return;
}
offset += dir.size;
bool isFile = (dir.flags & 2) ? false : true;
bool relative;
TreeEntry *entry = new TreeEntry();
if (dir.identifierLength == 1 && (dir.firstIdChar == '\x00' || dir.firstIdChar == '.')) {
entry->name = ".";
relative = true;
} else if (dir.identifierLength == 1 && dir.firstIdChar == '\x01') {
entry->name = "..";
relative = true;
} else {
entry->name = std::string(CleanISOFileName(std::string_view((const char *)&dir.firstIdChar, dir.identifierLength)));
relative = false;
}
entry->size = dir.dataLength;
entry->startingPosition = dir.firstDataSector * 2048;
entry->isDirectory = !isFile;
entry->flags = dir.flags;
entry->parent = root;
entry->startsector = dir.firstDataSector;
entry->dirsize = dir.dataLength;
entry->valid = isFile; // Can pre-mark as valid if file, as we don't recurse into those.
entry->recordTime = UnixTimeFromDirectoryEntry(dir);
VERBOSE_LOG(Log::FileSystem, "%s: %s %08x %08x %d", entry->isDirectory ? "D" : "F", entry->name.c_str(), (u32)dir.firstDataSector, entry->startingPosition, entry->startingPosition);
// Round down to avoid any false reports.
if (isFile && dir.firstDataSector + (dir.dataLength / 2048) > blockDevice->GetNumBlocks()) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "File '%s' starts or ends outside ISO. firstDataSector: %d len: %d", entry->BuildPath().c_str(), (int)dir.firstDataSector, (int)dir.dataLength);
}
// The directory record is untrusted, and callers size host buffers from entry->size, so
// don't let it claim more data than the image actually contains. We clamp rather than
// drop the entry - truncated ISOs are common and used to work with just the warning
// above, and dropping EBOOT.BIN would turn that into an unbootable game. For a sane
// file this is a no-op, since the extent always fits in its sectors.
// Measured in bytes, not whole sectors: an image whose length isn't a multiple of the
// sector size still contains its final partial sector, and a file is allowed to end
// there. Counting blocks discards that tail, which clamped real files short - a dump
// with EBOOT.BIN running to the last byte of the image lost the end of it, and the ELF
// section headers that live there went with it.
if (isFile) {
const u64 imageBytes = blockDevice->GetUncompressedSize();
const u64 firstByte = (u64)dir.firstDataSector * (u64)sectorSize;
const s64 availableBytes = firstByte >= imageBytes ? 0 : (s64)(imageBytes - firstByte);
if (entry->size > availableBytes) {
entry->size = availableBytes;
}
}
if (entry->isDirectory && !relative) {
if (entry->startsector == root->startsector) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "WARNING: Appear to have a recursive file system, breaking recursion. Probably corrupt ISO.");
}
}
root->children.push_back(entry);
}
}
root->valid = true;
}
const ISOFileSystem::TreeEntry *ISOFileSystem::GetFromPath(std::string_view path, bool catchError) {
const size_t pathLength = path.length();
if (pathLength == 0) {
// Ah, the device! "umd0:"
return &entireISO;
}
size_t pathIndex = 0;
// Skip "./"
if (pathLength > pathIndex + 1 && path[pathIndex] == '.' && path[pathIndex + 1] == '/')
pathIndex += 2;
// Skip "/"
if (pathLength > pathIndex && path[pathIndex] == '/')
++pathIndex;
if (pathLength <= pathIndex)
return treeroot;
if (!treeroot) {
// The constructor gave up - no ISO9660 volume descriptor, or it wouldn't read.
return nullptr;
}
TreeEntry *entry = treeroot;
while (true) {
if (!entry->valid) {
ReadDirectory(entry);
}
TreeEntry *nextEntry = nullptr;
std::string name = "";
if (pathLength > pathIndex) {
size_t nextSlashIndex = path.find_first_of('/', pathIndex);
if (nextSlashIndex == std::string::npos)
nextSlashIndex = pathLength;
const std::string_view firstPathComponent = path.substr(pathIndex, nextSlashIndex - pathIndex);
for (size_t i = 0; i < entry->children.size(); i++) {
const std::string_view n = entry->children[i]->name;
if (firstPathComponent == n) {
//yay we got it
nextEntry = entry->children[i];
name = n;
break;
}
}
}
if (nextEntry) {
entry = nextEntry;
if (!entry->valid) {
ReadDirectory(entry);
}
pathIndex += name.length();
if (pathIndex < pathLength && path[pathIndex] == '/')
++pathIndex;
if (pathLength <= pathIndex)
return entry;
} else {
if (catchError) {
ERROR_LOG(Log::FileSystem, "File '%.*s' not found", STR_VIEW(path));
}
return 0;
}
}
}
int ISOFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {
OpenFileEntry entry;
entry.isRawSector = false;
entry.isBlockSectorMode = false;
if (access & FILEACCESS_WRITE) {
ERROR_LOG(Log::FileSystem, "Can't open file '%s' with write access on an ISO partition", filename.c_str());
return SCE_KERNEL_ERROR_ERRNO_INVALID_FLAG;
}
if (filename.compare(0, 8, "/sce_lbn") == 0) {
// Raw sector read.
u32 sectorStart = 0xFFFFFFFF, readSize = 0xFFFFFFFF;
parseLBN(filename, &sectorStart, &readSize);
if (sectorStart > blockDevice->GetNumBlocks()) {
WARN_LOG(Log::FileSystem, "Unable to open raw sector, out of range: '%s', sector %08x, max %08x", filename.c_str(), sectorStart, blockDevice->GetNumBlocks());
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
}
else if (sectorStart == blockDevice->GetNumBlocks())
{
ERROR_LOG(Log::FileSystem, "Should not be able to open the block after the last on disc! %08x", sectorStart);
}
DEBUG_LOG(Log::FileSystem, "Got a raw sector open: '%s', sector %08x, size %08x", filename.c_str(), sectorStart, readSize);
u32 newHandle = hAlloc->GetNewHandle();
entry.seekPos = 0;
entry.file = 0;
entry.isRawSector = true;
entry.sectorStart = sectorStart;
entry.openSize = readSize;
// when open as "umd1:/sce_lbn0x0_size0x6B49D200", that mean open umd1 as a block device.
// the param in sceIoLseek and sceIoRead is lba mode. we must mark it.
if (strncmp(devicename, "umd0:", 5) == 0 || strncmp(devicename, "umd1:", 5) == 0)
entry.isBlockSectorMode = true;
entries[newHandle] = entry;
return newHandle;
}
// May return entireISO for "umd0:".
entry.file = GetFromPath(filename, false);
if (!entry.file) {
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
}
if (entry.file == &entireISO)
entry.isBlockSectorMode = true;
entry.seekPos = 0;
u32 newHandle = hAlloc->GetNewHandle();
entries[newHandle] = entry;
return newHandle;
}
void ISOFileSystem::CloseFile(u32 handle) {
EntryMap::iterator iter = entries.find(handle);
if (iter != entries.end()) {
//CloseHandle((*iter).second.hFile);
hAlloc->FreeHandle(handle);
entries.erase(iter);
} else {
//This shouldn't happen...
ERROR_LOG(Log::FileSystem, "Hey, what are you doing? Closing non-open files?");
}
}
bool ISOFileSystem::OwnsHandle(u32 handle) {
EntryMap::iterator iter = entries.find(handle);
return (iter != entries.end());
}
int ISOFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
EntryMap::iterator iter = entries.find(handle);
if (iter == entries.end()) {
ERROR_LOG(Log::FileSystem, "Ioctl on a bad file handle");
return SCE_KERNEL_ERROR_BADF;
}
OpenFileEntry &e = iter->second;
switch (cmd) {
// Get ISO9660 volume descriptor (from open ISO9660 file.)
case 0x01020001:
if (e.isBlockSectorMode) {
ERROR_LOG(Log::FileSystem, "Unsupported read volume descriptor command on a umd block device");
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
}
if (!Memory::IsValidRange(outdataPtr, 0x800) || outlen < 0x800) {
WARN_LOG_REPORT(Log::FileSystem, "sceIoIoctl: Invalid out pointer %08x while reading ISO9660 volume descriptor", outdataPtr);
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
INFO_LOG(Log::sceIo, "sceIoIoctl: reading ISO9660 volume descriptor read");
blockDevice->ReadBlock(16, Memory::GetPointerWriteUnchecked(outdataPtr));
return 0;
// Get ISO9660 path table (from open ISO9660 file.)
case 0x01020002:
if (e.isBlockSectorMode) {
ERROR_LOG(Log::FileSystem, "Unsupported read path table command on a umd block device");
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
}
VolDescriptor desc;
if (!blockDevice->ReadBlock(16, (u8 *)&desc)) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Failed to read volume descriptor for the path table");
return SCE_KERNEL_ERROR_ERRNO_IO_ERROR;
}
if (outlen < (u32)desc.pathTableLength) {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
} else {
int block = (u16)desc.firstLETableSector;
u32 size = Memory::ClampValidSizeAt(outdataPtr, (u32)desc.pathTableLength);
u8 *out = Memory::GetPointerWriteRangeOrException(outdataPtr, size);
blockDevice->ReadBytes((u64)block * blockDevice->GetBlockSize(), size, out);
return 0;
}
}
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
}
PSPDevType ISOFileSystem::DevType(u32 handle) {
EntryMap::iterator iter = entries.find(handle);
if (iter == entries.end())
return PSPDevType::FILE;
PSPDevType type = iter->second.isBlockSectorMode ? PSPDevType::BLOCK : PSPDevType::FILE;
if (iter->second.isRawSector)
type |= PSPDevType::EMU_LBN;
return type;
}
FileSystemFlags ISOFileSystem::Flags() const {
// TODO: Here may be a good place to force things, in case users recompress games
// as PBP or CSO when they were originally the other type.
return blockDevice->IsDisc() ? FileSystemFlags::UMD : FileSystemFlags::CARD;
}
size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size)
{
int ignored;
return ReadFile(handle, pointer, size, ignored);
}
size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {
EntryMap::iterator iter = entries.find(handle);
if (iter != entries.end()) {
OpenFileEntry &e = iter->second;
if (size < 0) {
ERROR_LOG(Log::FileSystem, "Invalid read for %lld bytes from umd %s", size, e.file ? e.file->name.c_str() : "device");
return 0;
}
if (e.isBlockSectorMode) {
// Whole sectors! Shortcut to this simple code.
blockDevice->ReadBlocks(e.seekPos, (int)size, pointer);
if (abs((int)lastReadBlock_ - (int)e.seekPos) > 100) {
// This is an estimate, sometimes it takes 1+ seconds, but it definitely takes time.
usec = 100000;
}
e.seekPos += (int)size;
lastReadBlock_ = e.seekPos;
return (int)size;
}
u64 positionOnIso;
s64 fileSize;
if (e.isRawSector) {
positionOnIso = e.sectorStart * 2048ULL + e.seekPos;
fileSize = (s64)e.openSize;
} else if (e.file == nullptr) {
ERROR_LOG(Log::FileSystem, "File no longer exists (loaded savestate with different ISO?)");
return 0;
} else {
positionOnIso = e.file->startingPosition + e.seekPos;
fileSize = e.file->size;
}
if ((s64)e.seekPos > fileSize) {
WARN_LOG(Log::FileSystem, "Read starting outside of file, at %lld / %lld", (s64)e.seekPos, fileSize);
return 0;
}
if ((s64)e.seekPos + size > fileSize) {
// Clamp to the remaining size, but read what we can.
const s64 newSize = fileSize - (s64)e.seekPos;
// Reading beyond the file is really quite normal behavior (if return value handled correctly), so
// not doing WARN here.
if (newSize == 0) {
DEBUG_LOG(Log::FileSystem, "Attempted read at end of file, 0-size read simulated");
} else {
DEBUG_LOG(Log::FileSystem, "Reading beyond end of file from seekPos %d, clamping size %lld to %lld", e.seekPos, size, newSize);
}
size = newSize;
}
// Okay, we have size and position, let's rock.
blockDevice->ReadBytes(positionOnIso, (size_t)size, pointer);
// The sector after the last one touched, for the seek-time estimate.
const u32 secNum = (u32)(size > 0 ? (positionOnIso + size + 2047) / 2048 : positionOnIso / 2048);
const size_t totalBytes = (size_t)size;
if (abs((int)lastReadBlock_ - (int)secNum) > 100) {
// This is an estimate, sometimes it takes 1+ seconds, but it definitely takes time.
usec = 100000;
}
lastReadBlock_ = secNum;
e.seekPos += (unsigned int)totalBytes;
return (size_t)totalBytes;
} else {
//This shouldn't happen...
ERROR_LOG(Log::FileSystem, "Hey, what are you doing? Reading non-open files?");
return 0;
}
}
size_t ISOFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {
ERROR_LOG(Log::FileSystem, "Hey, what are you doing? You can't write to an ISO!");
return 0;
}
size_t ISOFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {
ERROR_LOG(Log::FileSystem, "Hey, what are you doing? You can't write to an ISO!");
return 0;
}
size_t ISOFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
EntryMap::iterator iter = entries.find(handle);
if (iter != entries.end()) {
OpenFileEntry &e = iter->second;
switch (type)
{
case FILEMOVE_BEGIN:
e.seekPos = position;
break;
case FILEMOVE_CURRENT:
e.seekPos += position;
break;
case FILEMOVE_END:
if (e.isRawSector)
e.seekPos = e.openSize + position;
else
e.seekPos = (unsigned int)(e.file->size + position);
break;
}
return (size_t)e.seekPos;
} else {
//This shouldn't happen...
ERROR_LOG(Log::FileSystem, "Hey, what are you doing? Seeking in non-open files?");
return 0;
}
}
PSPFileInfo ISOFileSystem::GetFileInfo(std::string filename) {
if (filename.compare(0,8,"/sce_lbn") == 0) {
u32 sectorStart = 0xFFFFFFFF, readSize = 0xFFFFFFFF;
parseLBN(filename, &sectorStart, &readSize);
PSPFileInfo fileInfo;
fileInfo.name = filename;
fileInfo.exists = true;
fileInfo.type = FILETYPE_NORMAL;
fileInfo.size = readSize;
fileInfo.access = 0444;
fileInfo.startSector = sectorStart;
fileInfo.isOnSectorSystem = true;
fileInfo.numSectors = (readSize + sectorSize - 1) / sectorSize;
return fileInfo;
}
const TreeEntry *entry = GetFromPath(filename, false);
PSPFileInfo x;
if (entry) {
x.name = entry->name;
// Strangely, it seems to be executable even for files.
x.access = 0555;
x.size = entry->size;
x.exists = true;
x.type = entry->isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
x.isOnSectorSystem = true;
x.startSector = entry->startingPosition / 2048;
FillFileTimes(&x, entry->recordTime);
}
return x;
}
PSPFileInfo ISOFileSystem::GetFileInfoByHandle(u32 handle) {
auto iter = entries.find(handle);
PSPFileInfo x;
if (iter != entries.end()) {
const TreeEntry *entry = iter->second.file;
x.name = entry->name;
// Strangely, it seems to be executable even for files.
x.access = 0555;
x.size = entry->size;
x.exists = true;
x.type = entry->isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
x.isOnSectorSystem = true;
x.startSector = entry->startingPosition / 2048;
FillFileTimes(&x, entry->recordTime);
}
return x;
}
std::vector<PSPFileInfo> ISOFileSystem::GetDirListing(std::string_view path, bool *exists) {
std::vector<PSPFileInfo> myVector;
const TreeEntry *entry = GetFromPath(path);
if (!entry) {
if (exists)
*exists = false;
return myVector;
}
if (entry == &entireISO) {
entry = GetFromPath("/");
}
const std::string dot(".");
const std::string dotdot("..");
for (size_t i = 0; i < entry->children.size(); i++) {
const TreeEntry *e = entry->children[i];
// do not include the relative entries in the list
if (e->name == dot || e->name == dotdot)
continue;
PSPFileInfo x;
x.name = e->name;
// Strangely, it seems to be executable even for files.
x.access = 0555;
x.exists = true;
x.size = e->size;
x.type = e->isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
x.isOnSectorSystem = true;
x.startSector = e->startingPosition/2048;
FillFileTimes(&x, e->recordTime);
x.sectorSize = sectorSize;
x.numSectors = (u32)((e->size + sectorSize - 1) / sectorSize);
myVector.push_back(x);
}
if (exists)
*exists = true;
return myVector;
}
std::string ISOFileSystem::EntryFullPath(const TreeEntry *e) {
if (e == &entireISO)
return "";
size_t fullLen = 0;
const TreeEntry *cur = e;
while (cur != NULL && cur != treeroot) {
// For the "/".
fullLen += 1 + cur->name.size();
cur = cur->parent;
}
std::string path;
path.resize(fullLen);
cur = e;
while (cur != NULL && cur != treeroot) {
path.replace(fullLen - cur->name.size(), cur->name.size(), cur->name);
path.replace(fullLen - cur->name.size() - 1, 1, "/");
fullLen -= 1 + cur->name.size();
cur = cur->parent;
}
return path;
}
ISOFileSystem::TreeEntry::~TreeEntry() {
for (size_t i = 0; i < children.size(); ++i)
delete children[i];
children.clear();
}
void ISOFileSystem::DoState(PointerWrap &p) {
auto s = p.Section("ISOFileSystem", 1, 2);
if (!s)
return;
int n = (int) entries.size();
Do(p, n);
if (p.mode == p.MODE_READ) {
entries.clear();
for (int i = 0; i < n; ++i) {
u32 fd = 0;
OpenFileEntry of;
Do(p, fd);
Do(p, of.seekPos);
Do(p, of.isRawSector);
Do(p, of.isBlockSectorMode);
Do(p, of.sectorStart);
Do(p, of.openSize);
bool hasFile = false;
Do(p, hasFile);
if (hasFile) {
std::string path;
Do(p, path);
of.file = GetFromPath(path);
if (!of.file) {
// Loaded against a different image. Like a file that's gone from a directory,
// drop the handle rather than keep one that points at nothing.
ERROR_LOG(Log::FileSystem, "Failed to reopen file while loading state: %s", path.c_str());
continue;
}
} else {
of.file = NULL;
}
entries[fd] = of;
}
} else {
for (EntryMap::iterator it = entries.begin(), end = entries.end(); it != end; ++it) {
OpenFileEntry &of = it->second;
Do(p, it->first);
Do(p, of.seekPos);
Do(p, of.isRawSector);
Do(p, of.isBlockSectorMode);
Do(p, of.sectorStart);
Do(p, of.openSize);
bool hasFile = of.file != NULL;
Do(p, hasFile);
if (hasFile) {
std::string path = EntryFullPath(of.file);
Do(p, path);
}
}
}
if (s >= 2) {
Do(p, lastReadBlock_);
} else {
lastReadBlock_ = 0;
}
}