Add VFS and support for multiple filesystems

This commit is contained in:
spicyjpeg 2024-05-26 23:36:44 +02:00
parent f9a7df6de8
commit c558744fcd
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
30 changed files with 859 additions and 613 deletions

View File

@ -89,6 +89,7 @@ addExecutable(
src/common/file.cpp
src/common/file9660.cpp
src/common/filefat.cpp
src/common/filemisc.cpp
src/common/filezip.cpp
src/common/gpu.cpp
src/common/gpufont.cpp

View File

@ -7,7 +7,7 @@
"App": {
"startupWorker": {
"initIDE": "Initializing IDE devices...\nDo not turn off the 573 or unplug drives.",
"initFAT": "Attempting to mount FAT filesystem...\nDo not turn off the 573 or unplug drives.",
"initFileIO": "Detecting and mounting filesystems...\nDo not turn off the 573 or unplug drives.",
"loadResources": "Loading resource pack...\nDo not turn off the 573 or unplug drives."
},
"cartDetectWorker": {
@ -49,7 +49,7 @@
"cartDumpWorker": {
"save": "Saving cartridge dump...\nDo not turn off the 573 or unplug drives.",
"success": "A dump of the cartridge and all its identifiers has been saved as %s in the root of the drive. The dump can be decoded and viewed using the decodeDump.py script provided with this tool.",
"error": "An error occurred while saving the dump. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary and formatted with a single FAT16, FAT32 or exFAT partition.\n\nFile: %s\nPress the Test button to view debug logs."
"error": "An error occurred while saving the dump. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary (if a CD-ROM drive is also present) and formatted with a single FAT16, FAT32 or exFAT partition.\n\nFile: %s\nPress the Test button to view debug logs."
},
"executableWorker": {
"init": "Validating executable file...\nDo not turn off the 573 or unplug drives.",
@ -82,7 +82,7 @@
"dumpPCMCIA1": "Dumping PCMCIA card in slot 1...\nDo not turn off the 573 or unplug drives.",
"dumpPCMCIA2": "Dumping PCMCIA card in slot 2...\nDo not turn off the 573 or unplug drives.",
"success": "All dumps have been saved to the %s directory in the root of the drive.",
"initError": "An error occurred while creating the dump directory. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary and formatted with a single FAT16, FAT32 or exFAT partition.\n\nPath: %s\nPress the Test button to view debug logs.",
"initError": "An error occurred while creating the dump directory. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary (if a CD-ROM drive is also present) and formatted with a single FAT16, FAT32 or exFAT partition.\n\nPath: %s\nPress the Test button to view debug logs.",
"fileError": "An error occurred while saving one of the dumps. Ensure the drive has at least 32 MB of free space (256 MB if both PCMCIA cards are inserted) and the filesystem is not damaged.\n\nFile: %s\nPress the Test button to view debug logs."
},
"romEraseWorker": {
@ -142,7 +142,7 @@
},
"hddDump": {
"name": "Dump cartridge to hard drive",
"prompt": "Save the contents of the cartridge's EEPROM to a file on the IDE hard drive or CF card connected as secondary drive (if any)."
"prompt": "Save the contents of the cartridge's EEPROM to a file on the IDE hard drive or CF card (if connected)."
},
"hexdump": {
"name": "View cartridge hexdump",
@ -150,7 +150,7 @@
},
"hddRestore": {
"name": "Restore cartridge from dump",
"prompt": "Wipe all data and restore the cartridge's contents from a previously saved dump on the IDE hard drive or CF card connected as secondary drive (if any).",
"prompt": "Wipe all data and restore the cartridge's contents from a previously saved dump on the CD-ROM, IDE hard drive or CF card (if connected).",
"filePrompt": "Note that the dump must have the same chip type, and should have the same DS2401 ID (if any), as the cartridge.",
"confirm": "The contents of the cartridge's EEPROM will be altered and the unlocking key updated to match the selected dump.\n\nDo you wish to proceed?"
},
@ -261,14 +261,22 @@
"yes": "Yes, continue"
},
"FilePickerScreen": {
"title": "{CART_ICON} Select file from hard drive",
"FileBrowserScreen": {
"title": "{CART_ICON} Select file",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
"parentDir": "[Parent directory]",
"noFilesError": "No files or directories have been found in the root of the hard drive's filesystem.",
"rootError": "An error occurred while enumerating files in the root directory. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary and formatted with a single FAT16, FAT32 or exFAT partition.\n\nPress the Test button to view debug logs.",
"subdirError": "An error occurred while enumerating files in the selected subdirectory. The filesystem may be corrupted or otherwise inaccessible.\n\nPath: %s\nPress the Test button to view debug logs."
"parentDir": "[Parent directory]",
"subdirError": "An error occurred while enumerating files in the selected subdirectory. The filesystem may be corrupted or otherwise inaccessible.\n\nPath: %s\nPress the Test button to view debug logs."
},
"FilePickerScreen": {
"title": "{CART_ICON} Select IDE drive",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
"noDeviceError": "No drives have been found and successfully initialized on the IDE bus. Make sure the drives are appropriately configured as primary or secondary and are receiving power.\n\nPress the Test button to view debug logs.",
"atapiError": "Failed to initialize the CD-ROM drive or access the filesystem on it. Your drive might be incompatible with the ATAPI driver used by this tool.\n\nPress the Test button to view debug logs.",
"ideError": "Failed to initialize the drive or access the filesystem on it. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary (if a CD-ROM drive is also present) and formatted with a single FAT16, FAT32 or exFAT partition.\n\nPress the Test button to view debug logs.",
"noFilesError": "No files or directories have been found in the root of the selected drive's filesystem."
},
"HexdumpScreen": {
@ -280,7 +288,7 @@
"title": "{CART_ICON} IDE device information",
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back.",
"ide": {
"device": {
"header": {
"primary": "Primary IDE drive:\n",
"secondary": "Secondary IDE drive:\n"
@ -294,9 +302,20 @@
"error": " No drive present or initialization failed\n"
},
"fat": {
"header": "FAT filesystem:\n",
"info": " Type:\t\t%s\n Volume label:\t%s\n Serial number:\t%04X-%04X\n Formatted:\t\t%llu MB\n Free space:\t%llu MB\n",
"error": " No filesystem mounted\n"
"header": {
"primary": "FAT filesystem on primary drive:\n",
"secondary": "FAT filesystem on secondary drive:\n"
},
"info": " Type:\t\t%s\n Volume label:\t%s\n Serial number:\t%04X-%04X\n Formatted:\t\t%llu MB\n Free space:\t%llu MB\n"
},
"iso9660": {
"header": {
"primary": "ISO9660 filesystem on primary drive:\n",
"secondary": "ISO9660 filesystem on secondary drive:\n"
},
"info": " Volume label:\t%s\n Formatted:\t\t%llu MB\n"
}
},
@ -321,11 +340,11 @@
},
"ideInfo": {
"name": "View IDE device and filesystem information",
"prompt": "Display information about the connected IDE/ATAPI devices and mounted FAT filesystem (if any)."
"prompt": "Display information about the connected IDE/ATAPI devices and mounted FAT and ISO9660 filesystems (if any)."
},
"runExecutable": {
"name": "Run executable from hard drive",
"prompt": "Load and launch a System 573 executable file from the IDE hard drive or CF card connected as secondary drive (if any).",
"name": "Run executable from CD-ROM or hard drive",
"prompt": "Load and launch a System 573 executable from the CD-ROM, IDE hard drive or CF card (if connected).",
"filePrompt": "Note that PlayStation executables built without proper System 573 support will not run unless the watchdog is manually disabled."
},
"setRTCTime": {
@ -405,7 +424,7 @@
},
"dump": {
"name": "Dump flash, cards, RTC and BIOS ROM",
"prompt": "Dump the contents of the internal ROMs and any inserted flash cards to the IDE hard drive or CF card connected as secondary drive (if any).",
"prompt": "Dump the contents of the internal ROMs and any inserted flash cards to the IDE hard drive or CF card (if connected).",
"confirm": "The contents of the internal flash memory, RTC RAM, BIOS ROM and any inserted PCMCIA flash cards will be dumped and the dumps saved to a new directory in the root of the hard drive or CF card currently configured as secondary on the IDE bus.\n\nDo you wish to proceed?"
},
"restore": {
@ -414,19 +433,19 @@
"rtc": {
"name": "Restore RTC RAM from dump",
"prompt": "Erase RTC memory and restore its contents from a file on the IDE hard drive or CF card connected as secondary drive (if any)."
"prompt": "Erase RTC memory and restore its contents from a file on the CD-ROM, IDE hard drive or CF card (if connected)."
},
"flash": {
"name": "Restore internal flash from dump",
"prompt": "Erase the system's flash memory and restore its contents from a file on the IDE hard drive or CF card connected as secondary drive (if any)."
"prompt": "Erase the system's flash memory and restore its contents from a file on the CD-ROM, IDE hard drive or CF card (if connected)."
},
"pcmcia1": {
"name": "Restore PCMCIA card in slot 1 from dump",
"prompt": "Erase the flash card and restore its contents from a file on the IDE hard drive or CF card connected as secondary drive (if any)."
"prompt": "Erase the flash card and restore its contents from a file on the CD-ROM, IDE hard drive or CF card (if connected)."
},
"pcmcia2": {
"name": "Restore PCMCIA card in slot 2 from dump",
"prompt": "Erase the flash card and restore its contents from a file on the IDE hard drive or CF card connected as secondary drive (if any)."
"prompt": "Erase the flash card and restore its contents from a file on the CD-ROM, IDE hard drive or CF card (if connected)."
}
},
"erase": {

View File

@ -101,6 +101,7 @@
"~": { "x": 84, "y": 45, "width": 6, "height": 9 },
"\u007f": { "x": 90, "y": 45, "width": 6, "height": 9 },
"\u0080": { "x": 0, "y": 54, "width": 6, "height": 9 },
"\u0081": { "x": 6, "y": 54, "width": 6, "height": 9 },
"\u0082": { "x": 12, "y": 54, "width": 4, "height": 9 },
@ -111,14 +112,18 @@
"\u0087": { "x": 42, "y": 54, "width": 6, "height": 9 },
"\u0090": { "x": 0, "y": 63, "width": 7, "height": 9, "icon": true },
"\u0091": { "x": 7, "y": 63, "width": 7, "height": 9, "icon": true },
"\u0092": { "x": 14, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0093": { "x": 23, "y": 63, "width": 8, "height": 10, "icon": true },
"\u0094": { "x": 31, "y": 63, "width": 11, "height": 10, "icon": true },
"\u0095": { "x": 42, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0096": { "x": 51, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0097": { "x": 60, "y": 63, "width": 9, "height": 10, "icon": true },
"\u0098": { "x": 69, "y": 63, "width": 12, "height": 10, "icon": true },
"\u0099": { "x": 81, "y": 63, "width": 14, "height": 10, "icon": true }
"\u0091": { "x": 12, "y": 63, "width": 7, "height": 9, "icon": true },
"\u0092": { "x": 24, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0093": { "x": 36, "y": 63, "width": 8, "height": 10, "icon": true },
"\u0094": { "x": 48, "y": 63, "width": 11, "height": 10, "icon": true },
"\u0095": { "x": 60, "y": 63, "width": 12, "height": 10, "icon": true },
"\u0096": { "x": 72, "y": 63, "width": 14, "height": 9, "icon": true },
"\u00a0": { "x": 0, "y": 73, "width": 10, "height": 10, "icon": true },
"\u00a1": { "x": 12, "y": 73, "width": 10, "height": 10, "icon": true },
"\u00a2": { "x": 24, "y": 73, "width": 10, "height": 10, "icon": true },
"\u00a3": { "x": 36, "y": 73, "width": 10, "height": 9, "icon": true },
"\u00a4": { "x": 48, "y": 73, "width": 10, "height": 9, "icon": true },
"\u00a5": { "x": 60, "y": 73, "width": 10, "height": 10, "icon": true }
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -11,7 +11,7 @@
#define VERSION_STRING VERSION "-debug"
#endif
#define EXTERNAL_DATA_DIR "cartdata"
#define EXTERNAL_DATA_DIR "hdd:cartdata"
enum Character : char {
CH_UP_ARROW = '\x80',
@ -22,14 +22,19 @@ enum Character : char {
CH_DOWN_ARROW_ALT = '\x85',
CH_LEFT_ARROW_ALT = '\x86',
CH_RIGHT_ARROW_ALT = '\x87',
CH_LEFT_BUTTON = '\x90',
CH_RIGHT_BUTTON = '\x91',
CH_START_BUTTON = '\x92',
CH_CLOSED_LOCK = '\x93',
CH_OPEN_LOCK = '\x94',
CH_DIR_ICON = '\x95',
CH_PARENT_DIR_ICON = '\x96',
CH_FILE_ICON = '\x97',
CH_CHIP_ICON = '\x98',
CH_CART_ICON = '\x99'
CH_LEFT_BUTTON = '\x90',
CH_RIGHT_BUTTON = '\x91',
CH_START_BUTTON = '\x92',
CH_CLOSED_LOCK = '\x93',
CH_OPEN_LOCK = '\x94',
CH_CHIP_ICON = '\x95',
CH_CART_ICON = '\x96',
CH_CDROM_ICON = '\xa0',
CH_HDD_ICON = '\xa1',
CH_HOST_ICON = '\xa2',
CH_DIR_ICON = '\xa3',
CH_PARENT_DIR_ICON = '\xa4',
CH_FILE_ICON = '\xa5'
};

View File

@ -6,7 +6,6 @@
#include "common/file.hpp"
#include "common/gpu.hpp"
#include "common/util.hpp"
#include "ps1/pcdrv.h"
namespace file {
@ -29,54 +28,6 @@ File::~File(void) {
close();
}
size_t HostFile::read(void *output, size_t length) {
int actualLength = pcdrvRead(_fd, output, length);
if (actualLength < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualLength, this);
return 0;
}
return size_t(actualLength);
}
size_t HostFile::write(const void *input, size_t length) {
int actualLength = pcdrvWrite(_fd, input, length);
if (actualLength < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualLength, this);
return 0;
}
return size_t(actualLength);
}
uint64_t HostFile::seek(uint64_t offset) {
int actualOffset = pcdrvSeek(_fd, int(offset), PCDRV_SEEK_SET);
if (actualOffset < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualOffset, this);
return 0;
}
return uint64_t(actualOffset);
}
uint64_t HostFile::tell(void) const {
int actualOffset = pcdrvSeek(_fd, 0, PCDRV_SEEK_CUR);
if (actualOffset < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualOffset, this);
return 0;
}
return uint64_t(actualOffset);
}
void HostFile::close(void) {
pcdrvClose(_fd);
}
Directory::~Directory(void) {
close();
}
@ -115,7 +66,7 @@ size_t Provider::loadData(void *output, size_t length, const char *path) {
if (!_file)
return 0;
//assert(file->length >= length);
//assert(file->size >= length);
size_t actualLength = _file->read(output, length);
_file->close();
@ -267,54 +218,6 @@ size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) {
return length;
}
bool HostProvider::init(void) {
int error = pcdrvInit();
if (error < 0) {
LOG("PCDRV error, code=%d", error);
return false;
}
type = HOST;
return true;
}
bool HostProvider::createDirectory(const char *path) {
int fd = pcdrvCreate(path, DIRECTORY);
if (fd < 0) {
LOG("PCDRV error, code=%d", fd);
return false;
}
pcdrvClose(fd);
return true;
}
File *HostProvider::openFile(const char *path, uint32_t flags) {
PCDRVOpenMode mode = PCDRV_MODE_READ;
if ((flags & (READ | WRITE)) == (READ | WRITE))
mode = PCDRV_MODE_READ_WRITE;
else if (flags & WRITE)
mode = PCDRV_MODE_WRITE;
int fd = pcdrvOpen(path, mode);
if (fd < 0) {
LOG("PCDRV error, code=%d", fd);
return nullptr;
}
auto file = new HostFile();
file->_fd = fd;
file->size = pcdrvSeek(fd, 0, PCDRV_SEEK_END);
pcdrvSeek(fd, 0, PCDRV_SEEK_SET);
return file;
}
/* String table parser */
static const char _ERROR_STRING[]{ "missingno" };

View File

@ -23,7 +23,8 @@ enum FileSystemType {
ISO9660 = 4,
ZIP_MEMORY = 5,
ZIP_FILE = 6,
HOST = 7
HOST = 7,
VFS = 8
};
// These are functionally equivalent to the FA_* flags used by FatFs.
@ -82,20 +83,6 @@ public:
virtual void close(void) {}
};
class HostFile : public File {
friend class HostProvider;
private:
int _fd;
public:
size_t read(void *output, size_t length);
size_t write(const void *input, size_t length);
uint64_t seek(uint64_t offset);
uint64_t tell(void) const;
void close(void);
};
class Directory {
public:
virtual ~Directory(void);
@ -150,15 +137,6 @@ public:
size_t saveVRAMBMP(gpu::RectWH &rect, const char *path);
};
class HostProvider : public Provider {
public:
bool init(void);
bool createDirectory(const char *path);
File *openFile(const char *path, uint32_t flags);
};
/* String table parser */
static constexpr int TABLE_BUCKET_COUNT = 256;

View File

@ -199,6 +199,9 @@ bool ISO9660Provider::_readData(
bool ISO9660Provider::_getRecord(
ISORecordBuffer &output, const ISORecord &root, const char *path
) {
if (!type)
return false;
util::Data records;
auto numSectors =
(root.length.le + ide::ATAPI_SECTOR_SIZE - 1) / ide::ATAPI_SECTOR_SIZE;
@ -245,13 +248,8 @@ bool ISO9660Provider::_getRecord(
return false;
}
bool ISO9660Provider::init(const char *drive) {
int driveID = drive[0] - '0';
if ((driveID < 0) || (driveID >= int(util::countOf(ide::devices))))
return false;
_device = &ide::devices[driveID];
bool ISO9660Provider::init(int drive) {
_device = &ide::devices[drive];
// Locate and parse the primary volume descriptor.
ISOPrimaryVolumeDesc pvd;
@ -286,7 +284,7 @@ bool ISO9660Provider::init(const char *drive) {
type = ISO9660;
capacity = uint64_t(pvd.volumeLength.le) * ide::ATAPI_SECTOR_SIZE;
LOG("mounted ISO: %s, drive=%d", volumeLabel, driveID);
LOG("mounted ISO: %s, drive=%d:", volumeLabel, drive);
return true;
}

View File

@ -173,7 +173,7 @@ public:
inline ISO9660Provider(void)
: _device(nullptr) {}
bool init(const char *drive);
bool init(int drive);
void close(void);
bool getFileInfo(FileInfo &output, const char *path);

View File

@ -103,13 +103,13 @@ void FATDirectory::close(void) {
/* FAT filesystem provider */
bool FATProvider::init(const char *drive) {
__builtin_strncpy(_drive, drive, sizeof(_drive));
bool FATProvider::init(int drive) {
_drive[0] = drive + '0';
auto error = f_mount(&_fs, drive, 1);
auto error = f_mount(&_fs, _drive, 1);
if (error) {
LOG("%s, drive=%s", _FATFS_ERROR_NAMES[error], drive);
LOG("%s, drive=%s", _FATFS_ERROR_NAMES[error], _drive);
return false;
}
@ -118,7 +118,7 @@ bool FATProvider::init(const char *drive) {
f_getlabel(_drive, volumeLabel, &serialNumber);
LOG("mounted FAT: %s, drive=%s", volumeLabel, drive);
LOG("mounted FAT: %s, drive=%s", volumeLabel, _drive);
return true;
}

View File

@ -40,21 +40,19 @@ public:
class FATProvider : public Provider {
private:
FATFS _fs;
char _drive[8];
char _drive[4];
bool _selectDrive(void);
public:
inline FATProvider(void) {
_fs.fs_type = 0;
_drive[0] = 0;
_drive[0] = '#';
_drive[1] = ':';
_drive[2] = 0;
}
inline const char *getDriveString(void) {
return _drive;
}
bool init(const char *drive);
bool init(int drive);
void close(void);
uint64_t getFreeSpace(void);

235
src/common/filemisc.cpp Normal file
View File

@ -0,0 +1,235 @@
#include <stddef.h>
#include <stdint.h>
#include "common/file.hpp"
#include "common/filemisc.hpp"
#include "common/util.hpp"
#include "ps1/pcdrv.h"
namespace file {
/* PCDRV file class */
size_t HostFile::read(void *output, size_t length) {
int actualLength = pcdrvRead(_fd, output, length);
if (actualLength < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualLength, this);
return 0;
}
return size_t(actualLength);
}
size_t HostFile::write(const void *input, size_t length) {
int actualLength = pcdrvWrite(_fd, input, length);
if (actualLength < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualLength, this);
return 0;
}
return size_t(actualLength);
}
uint64_t HostFile::seek(uint64_t offset) {
int actualOffset = pcdrvSeek(_fd, int(offset), PCDRV_SEEK_SET);
if (actualOffset < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualOffset, this);
return 0;
}
return uint64_t(actualOffset);
}
uint64_t HostFile::tell(void) const {
int actualOffset = pcdrvSeek(_fd, 0, PCDRV_SEEK_CUR);
if (actualOffset < 0) {
LOG("PCDRV error, code=%d, file=0x%08x", actualOffset, this);
return 0;
}
return uint64_t(actualOffset);
}
void HostFile::close(void) {
pcdrvClose(_fd);
}
/* PCDRV filesystem provider */
bool HostProvider::init(void) {
int error = pcdrvInit();
if (error < 0) {
LOG("PCDRV error, code=%d", error);
return false;
}
type = HOST;
return true;
}
bool HostProvider::createDirectory(const char *path) {
int fd = pcdrvCreate(path, DIRECTORY);
if (fd < 0) {
LOG("PCDRV error, code=%d", fd);
return false;
}
pcdrvClose(fd);
return true;
}
File *HostProvider::openFile(const char *path, uint32_t flags) {
PCDRVOpenMode mode = PCDRV_MODE_READ;
if ((flags & (READ | WRITE)) == (READ | WRITE))
mode = PCDRV_MODE_READ_WRITE;
else if (flags & WRITE)
mode = PCDRV_MODE_WRITE;
int fd = pcdrvOpen(path, mode);
if (fd < 0) {
LOG("PCDRV error, code=%d", fd);
return nullptr;
}
auto file = new HostFile();
file->_fd = fd;
file->size = pcdrvSeek(fd, 0, PCDRV_SEEK_END);
pcdrvSeek(fd, 0, PCDRV_SEEK_SET);
return file;
}
/* Virtual filesystem driver */
VFSMountPoint *VFSProvider::_getMounted(const char *path) {
auto hash = util::hash(path, VFS_PREFIX_SEPARATOR);
for (auto &mp : _mountPoints) {
if (mp.prefix == hash)
return &mp;
}
return nullptr;
}
bool VFSProvider::mount(const char *prefix, Provider *provider) {
for (auto &mp : _mountPoints) {
if (mp.provider)
continue;
mp.prefix = util::hash(prefix, VFS_PREFIX_SEPARATOR);
mp.pathOffset = 0;
mp.provider = provider;
while (prefix[mp.pathOffset] != VFS_PREFIX_SEPARATOR)
mp.pathOffset++;
mp.pathOffset++;
return true;
}
return false;
}
bool VFSProvider::unmount(const char *prefix) {
auto hash = util::hash(prefix, VFS_PREFIX_SEPARATOR);
for (auto &mp : _mountPoints) {
if (mp.prefix != hash)
continue;
mp.prefix = 0;
mp.pathOffset = 0;
mp.provider = nullptr;
return true;
}
return false;
}
bool VFSProvider::getFileInfo(FileInfo &output, const char *path) {
auto mp = _getMounted(path);
if (!mp)
return false;
return mp->provider->getFileInfo(output, &path[mp->pathOffset]);
}
bool VFSProvider::getFileFragments(
FileFragmentTable &output, const char *path
) {
auto mp = _getMounted(path);
if (!mp)
return false;
return mp->provider->getFileFragments(output, &path[mp->pathOffset]);
}
Directory *VFSProvider::openDirectory(const char *path) {
auto mp = _getMounted(path);
if (!mp)
return nullptr;
return mp->provider->openDirectory(&path[mp->pathOffset]);
}
bool VFSProvider::createDirectory(const char *path) {
auto mp = _getMounted(path);
if (!mp)
return false;
return mp->provider->createDirectory(&path[mp->pathOffset]);
}
File *VFSProvider::openFile(const char *path, uint32_t flags) {
auto mp = _getMounted(path);
if (!mp)
return nullptr;
return mp->provider->openFile(&path[mp->pathOffset], flags);
}
size_t VFSProvider::loadData(util::Data &output, const char *path) {
auto mp = _getMounted(path);
if (!mp)
return 0;
return mp->provider->loadData(output, &path[mp->pathOffset]);
}
size_t VFSProvider::loadData(void *output, size_t length, const char *path) {
auto mp = _getMounted(path);
if (!mp)
return 0;
return mp->provider->loadData(output, length, &path[mp->pathOffset]);
}
size_t VFSProvider::saveData(
const void *input, size_t length, const char *path
) {
auto mp = _getMounted(path);
if (!mp)
return 0;
return mp->provider->saveData(input, length, &path[mp->pathOffset]);
}
}

75
src/common/filemisc.hpp Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "common/file.hpp"
#include "common/util.hpp"
namespace file {
/* PCDRV driver */
class HostFile : public File {
friend class HostProvider;
private:
int _fd;
public:
size_t read(void *output, size_t length);
size_t write(const void *input, size_t length);
uint64_t seek(uint64_t offset);
uint64_t tell(void) const;
void close(void);
};
class HostProvider : public Provider {
public:
bool init(void);
bool createDirectory(const char *path);
File *openFile(const char *path, uint32_t flags);
};
/* Virtual filesystem driver */
static constexpr char VFS_PREFIX_SEPARATOR = ':';
static constexpr size_t MAX_VFS_MOUNT_POINTS = 8;
struct VFSMountPoint {
public:
util::Hash prefix;
size_t pathOffset;
Provider *provider;
};
class VFSProvider : public Provider {
private:
VFSMountPoint _mountPoints[MAX_VFS_MOUNT_POINTS];
VFSMountPoint *_getMounted(const char *path);
public:
inline VFSProvider(void) {
type = VFS;
__builtin_memset(_mountPoints, 0, sizeof(_mountPoints));
}
bool mount(const char *prefix, Provider *provider);
bool unmount(const char *prefix);
bool getFileInfo(FileInfo &output, const char *path);
bool getFileFragments(FileFragmentTable &output, const char *path);
Directory *openDirectory(const char *path);
bool createDirectory(const char *path);
File *openFile(const char *path, uint32_t flags);
size_t loadData(util::Data &output, const char *path);
size_t loadData(void *output, size_t length, const char *path);
size_t saveData(const void *input, size_t length, const char *path);
};
}

View File

@ -101,10 +101,12 @@ bool ZIPProvider::init(const void *zipData, size_t length) {
void ZIPProvider::close(void) {
mz_zip_reader_end(&_zip);
#if 0
if (_file) {
_file->close();
delete _file;
}
#endif
type = NONE;
capacity = 0;

View File

@ -3,10 +3,13 @@
#include <stdio.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/file9660.hpp"
#include "common/filefat.hpp"
#include "common/filemisc.hpp"
#include "common/filezip.hpp"
#include "common/gpu.hpp"
#include "common/io.hpp"
#include "common/spu.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/cart.hpp"
@ -61,29 +64,113 @@ void WorkerStatus::finish(void) {
enableInterrupts();
}
/* Filesystem manager class */
FileIOManager::FileIOManager(void)
: _resourceFile(nullptr) {
__builtin_memset(ide, 0, sizeof(ide));
vfs.mount("resource:", &resource);
vfs.mount("host:", &host);
}
void FileIOManager::_closeResourceFile(void) {
if (!_resourceFile)
return;
_resourceFile->close();
delete _resourceFile;
_resourceFile = nullptr;
}
void FileIOManager::initIDE(void) {
char name[6]{ "ide#:" };
for (int i = 0; i < util::countOf(ide::devices); i++) {
if (ide[i])
continue;
auto &dev = ide::devices[i];
name[3] = i + '0';
// Note that calling vfs.mount() multiple times will *not* update any
// already mounted device, so if two hard drives or CD-ROMs are present
// the hdd:/cdrom: prefix will be assigned to the first one.
if (dev.flags & ide::DEVICE_ATAPI) {
auto iso = new file::ISO9660Provider();
if (!iso->init(i)) {
delete iso;
continue;
}
ide[i] = iso;
bool mapped = vfs.mount("cdrom:", iso);
if (mapped)
LOG("mapped cdrom: -> %s", name);
} else {
auto fat = new file::FATProvider();
if (!fat->init(i)) {
delete fat;
continue;
}
ide[i] = fat;
bool mapped = vfs.mount("hdd:", fat);
if (mapped)
LOG("mapped hdd: -> %s", name);
}
vfs.mount(name, ide[i]);
}
}
bool FileIOManager::loadResourceFile(const char *path) {
_closeResourceFile();
_resourceFile = vfs.openFile(path, file::READ);
if (!_resourceFile)
return false;
resource.close();
return resource.init(_resourceFile);
}
void FileIOManager::close(void) {
vfs.close();
resource.close();
host.close();
_closeResourceFile();
for (int i = 0; i < util::countOf(ide::devices); i++) {
if (!ide[i])
continue;
ide[i]->close();
delete ide[i];
ide[i] = nullptr;
}
}
/* App class */
static constexpr size_t _WORKER_STACK_SIZE = 0x20000;
App::App(ui::Context &ctx, file::ZIPProvider &resourceProvider)
App::App(ui::Context &ctx)
#ifdef ENABLE_LOG_BUFFER
: _logOverlay(_logBuffer),
#else
:
#endif
_ctx(ctx), _resourceProvider(resourceProvider), _resourceFile(nullptr),
_cartDriver(nullptr), _cartParser(nullptr), _identified(nullptr) {}
_ctx(ctx), _cartDriver(nullptr), _cartParser(nullptr), _identified(nullptr) {}
App::~App(void) {
_unloadCartData();
//_resourceProvider.close();
if (_resourceFile) {
_resourceFile->close();
delete _resourceFile;
}
//_fileProvider.close();
_fileIO.close();
}
void App::_unloadCartData(void) {
@ -148,21 +235,25 @@ static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
};
void App::_loadResources(void) {
_resourceProvider.loadTIM(_background.tile, "assets/textures/background.tim");
_resourceProvider.loadTIM(_ctx.font.image, "assets/textures/font.tim");
_resourceProvider.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics");
_resourceProvider.loadStruct(_ctx.colors, "assets/app.palette");
_resourceProvider.loadData(_stringTable, "assets/app.strings");
auto &res = _fileIO.resource;
res.loadTIM(_background.tile, "assets/textures/background.tim");
res.loadTIM(_ctx.font.image, "assets/textures/font.tim");
res.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics");
res.loadStruct(_ctx.colors, "assets/app.palette");
res.loadData(_stringTable, "assets/app.strings");
file::currentSPUOffset = spu::DUMMY_BLOCK_END;
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
_resourceProvider.loadVAG(_ctx.sounds[i], _UI_SOUND_PATHS[i]);
res.loadVAG(_ctx.sounds[i], _UI_SOUND_PATHS[i]);
}
bool App::_createDataDirectory(void) {
file::FileInfo info;
if (!_fileProvider.getFileInfo(info, EXTERNAL_DATA_DIR))
return _fileProvider.createDirectory(EXTERNAL_DATA_DIR);
if (!_fileIO.vfs.getFileInfo(info, EXTERNAL_DATA_DIR))
return _fileIO.vfs.createDirectory(EXTERNAL_DATA_DIR);
if (info.attributes & file::DIRECTORY)
return true;
@ -178,7 +269,7 @@ bool App::_getNumberedPath(char *output, size_t length, const char *path) {
return false;
snprintf(output, length, path, index);
} while (_fileProvider.getFileInfo(info, output));
} while (_fileIO.vfs.getFileInfo(info, output));
return true;
}
@ -194,7 +285,7 @@ bool App::_takeScreenshot(void) {
gpu::RectWH clip;
_ctx.gpuCtx.getVRAMClipRect(clip);
if (!_fileProvider.saveVRAMBMP(clip, path))
if (!_fileIO.vfs.saveVRAMBMP(clip, path))
return false;
LOG("%s saved", path);
@ -223,7 +314,7 @@ void App::_interruptHandler(void) {
}
}
[[noreturn]] void App::run(void) {
[[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) {
#ifdef ENABLE_LOG_BUFFER
util::logger.setLogBuffer(&_logBuffer);
#endif
@ -234,6 +325,8 @@ void App::_interruptHandler(void) {
_ctx.screenData = this;
_setupWorker(&App::_startupWorker);
_setupInterrupts();
_fileIO.resource.init(resourcePtr, resourceLength);
_loadResources();
char dateString[24];

View File

@ -3,8 +3,9 @@
#include <stddef.h>
#include "common/file.hpp"
#include "common/filefat.hpp"
#include "common/filemisc.hpp"
#include "common/filezip.hpp"
#include "common/ide.hpp"
#include "main/app/cartactions.hpp"
#include "main/app/cartunlock.hpp"
#include "main/app/main.hpp"
@ -50,6 +51,31 @@ public:
void finish(void);
};
/* Filesystem manager class */
class FileIOManager {
private:
file::File *_resourceFile;
void _closeResourceFile(void);
public:
file::Provider *ide[util::countOf(ide::devices)];
file::ZIPProvider resource;
file::HostProvider host;
file::VFSProvider vfs;
inline ~FileIOManager(void) {
close();
}
FileIOManager(void);
void initIDE(void);
bool loadResourceFile(const char *path);
void close(void);
};
/* App class */
class App {
@ -57,6 +83,7 @@ class App {
friend class MessageScreen;
friend class ConfirmScreen;
friend class FilePickerScreen;
friend class FileBrowserScreen;
friend class WarningScreen;
friend class ButtonMappingScreen;
friend class MainMenuScreen;
@ -82,6 +109,7 @@ private:
MessageScreen _messageScreen;
ConfirmScreen _confirmScreen;
FilePickerScreen _filePickerScreen;
FileBrowserScreen _fileBrowserScreen;
WarningScreen _warningScreen;
ButtonMappingScreen _buttonMappingScreen;
MainMenuScreen _mainMenuScreen;
@ -111,10 +139,8 @@ private:
ui::ScreenshotOverlay _screenshotOverlay;
ui::Context &_ctx;
file::ZIPProvider &_resourceProvider;
file::File *_resourceFile;
file::FATProvider _fileProvider;
file::StringTable _stringTable;
FileIOManager _fileIO;
cart::CartDump _cartDump;
cart::ROMHeaderDump _romHeaderDump;
@ -165,9 +191,10 @@ private:
void _interruptHandler(void);
public:
App(ui::Context &ctx, file::ZIPProvider &resourceProvider);
App(ui::Context &ctx);
~App(void);
[[noreturn]] void run(void);
[[noreturn]] void run(const void *resourcePtr, size_t resourceLength);
};
#define APP (reinterpret_cast<App *>(ctx.screenData))

View File

@ -85,7 +85,7 @@ void CartActionsScreen::hddRestore(ui::Context &ctx) {
STR("CartActionsScreen.hddRestore.filePrompt")
);
APP->_confirmScreen.setMessage(
APP->_filePickerScreen,
APP->_fileBrowserScreen,
[](ui::Context &ctx) {
APP->_setupWorker(&App::_cartRestoreWorker);
ctx.show(APP->_workerStatusScreen, false, true);

View File

@ -27,7 +27,7 @@ bool App::_cartDetectWorker(void) {
#ifdef ENABLE_DUMMY_DRIVER
if (!cart::dummyDriverDump.chipType)
_resourceProvider.loadStruct(cart::dummyDriverDump, "data/test.573");
_fileIO.resource.loadStruct(cart::dummyDriverDump, "data/test.573");
if (cart::dummyDriverDump.chipType) {
LOG("using dummy cart driver");
@ -60,7 +60,7 @@ bool App::_cartDetectWorker(void) {
_workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
if (!_cartDB.ptr) {
if (!_resourceProvider.loadData(
if (!_fileIO.resource.loadData(
_cartDB, _CARTDB_PATHS[_cartDump.chipType])
) {
LOG("%s not found", _CARTDB_PATHS[_cartDump.chipType]);
@ -99,7 +99,7 @@ _cartInitDone:
util::Data bitstream;
bool ready;
if (!_resourceProvider.loadData(bitstream, "data/fpga.bit")) {
if (!_fileIO.resource.loadData(bitstream, "data/fpga.bit")) {
LOG("bitstream unavailable");
return true;
}
@ -224,7 +224,7 @@ bool App::_cartDumpWorker(void) {
LOG("saving %s, length=%d", path, length);
if (_fileProvider.saveData(&_cartDump, length, path) != length)
if (_fileIO.vfs.saveData(&_cartDump, length, path) != length)
goto _error;
_messageScreen.setMessage(
@ -269,8 +269,8 @@ bool App::_cartWriteWorker(void) {
bool App::_cartRestoreWorker(void) {
_workerStatus.update(0, 3, WSTR("App.cartRestoreWorker.init"));
const char *path = _filePickerScreen.selectedPath;
auto _file = _fileProvider.openFile(path, file::READ);
const char *path = _fileBrowserScreen.selectedPath;
auto _file = _fileIO.vfs.openFile(path, file::READ);
cart::CartDump newDump;
size_t length;
@ -292,7 +292,7 @@ bool App::_cartRestoreWorker(void) {
if (_cartDump.chipType != newDump.chipType) {
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
MESSAGE_ERROR, _fileBrowserScreen,
WSTR("App.cartRestoreWorker.typeError"), path
);
_workerStatus.setNextScreen(_messageScreen);
@ -322,7 +322,7 @@ bool App::_cartRestoreWorker(void) {
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
MESSAGE_ERROR, _fileBrowserScreen,
WSTR("App.cartRestoreWorker.writeError"),
cart::getErrorString(error)
);
@ -338,7 +338,7 @@ _fileError:
_fileOpenError:
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
MESSAGE_ERROR, _fileBrowserScreen,
WSTR("App.cartRestoreWorker.fileError"), path
);
_workerStatus.setNextScreen(_messageScreen);

View File

@ -2,6 +2,7 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "common/file.hpp"
#include "common/ide.hpp"
#include "common/io.hpp"
#include "common/spu.hpp"
@ -13,9 +14,21 @@
/* System information screens */
static const util::Hash _IDE_HEADERS[]{
"IDEInfoScreen.ide.header.primary"_h,
"IDEInfoScreen.ide.header.secondary"_h
struct IDEInfoHeader {
public:
util::Hash device, fat, iso9660;
};
static const IDEInfoHeader _IDE_INFO_HEADERS[]{
{
.device = "IDEInfoScreen.device.header.primary"_h,
.fat = "IDEInfoScreen.fat.header.primary"_h,
.iso9660 = "IDEInfoScreen.iso9660.header.primary"_h
}, {
.device = "IDEInfoScreen.device.header.secondary"_h,
.fat = "IDEInfoScreen.fat.header.secondary"_h,
.iso9660 = "IDEInfoScreen.iso9660.header.secondary"_h
}
};
static const char *const _FAT_TYPES[]{
@ -36,57 +49,69 @@ void IDEInfoScreen::show(ui::Context &ctx, bool goBack) {
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
// IDE drives
for (int i = 0; i < 2; i++) {
auto &dev = ide::devices[i];
auto &header = _IDE_INFO_HEADERS[i];
auto &dev = ide::devices[i];
auto fs = APP->_fileIO.ide[i];
_PRINT(STRH(_IDE_HEADERS[i]));
// Device information
_PRINT(STRH(header.device));
if (dev.flags & ide::DEVICE_READY) {
_PRINT(
STR("IDEInfoScreen.ide.commonInfo"), dev.model, dev.revision,
STR("IDEInfoScreen.device.commonInfo"), dev.model, dev.revision,
dev.serialNumber
);
if (dev.flags & ide::DEVICE_ATAPI) {
_PRINT(
STR("IDEInfoScreen.ide.atapiInfo"),
STR("IDEInfoScreen.device.atapiInfo"),
(dev.flags & ide::DEVICE_HAS_PACKET16) ? 16 : 12
);
} else {
_PRINT(
STR("IDEInfoScreen.ide.ataInfo"),
STR("IDEInfoScreen.device.ataInfo"),
uint64_t(dev.capacity / (0x100000 / ide::ATA_SECTOR_SIZE)),
(dev.flags & ide::DEVICE_HAS_LBA48) ? 48 : 28
);
if (dev.flags & ide::DEVICE_HAS_TRIM)
_PRINT(STR("IDEInfoScreen.ide.hasTrim"));
_PRINT(STR("IDEInfoScreen.device.hasTrim"));
if (dev.flags & ide::DEVICE_HAS_FLUSH)
_PRINT(STR("IDEInfoScreen.ide.hasFlush"));
_PRINT(STR("IDEInfoScreen.device.hasFlush"));
}
} else {
_PRINT(STR("IDEInfoScreen.ide.error"));
_PRINT(STR("IDEInfoScreen.device.error"));
}
_PRINTLN();
// Filesystem information
if (!fs)
continue;
if (fs->type == file::ISO9660) {
_PRINT(STRH(header.iso9660));
_PRINT(
STR("IDEInfoScreen.iso9660.info"), fs->volumeLabel,
fs->capacity / 0x100000
);
} else {
_PRINT(STRH(header.fat));
_PRINT(
STR("IDEInfoScreen.fat.info"), _FAT_TYPES[fs->type],
fs->volumeLabel, fs->serialNumber >> 16,
fs->serialNumber & 0xffff, fs->capacity / 0x100000,
fs->getFreeSpace() / 0x100000
);
}
_PRINTLN();
}
// FAT file system
auto &fs = APP->_fileProvider;
_PRINT(STR("IDEInfoScreen.fat.header"));
if (fs.type)
_PRINT(
STR("IDEInfoScreen.fat.info"), _FAT_TYPES[fs.type], fs.volumeLabel,
fs.serialNumber >> 16, fs.serialNumber & 0xffff,
fs.capacity / 0x100000, fs.getFreeSpace() / 0x100000
);
else
_PRINT(STR("IDEInfoScreen.fat.error"));
//*(--ptr) = 0;
*(--ptr) = 0;
LOG("remaining=%d", end - ptr);
TextScreen::show(ctx, goBack);
@ -224,7 +249,7 @@ void AboutScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("AboutScreen.title");
_prompt = STR("AboutScreen.prompt");
APP->_resourceProvider.loadData(_text, "assets/about.txt");
APP->_fileIO.resource.loadData(_text, "assets/about.txt");
auto ptr = reinterpret_cast<char *>(_text.ptr);
_body = ptr;

View File

@ -22,41 +22,24 @@ bool App::_startupWorker(void) {
auto &dev = ide::devices[i];
_workerStatus.update(i, 4, WSTR("App.startupWorker.initIDE"));
if (dev.enumerate())
continue;
if (!(dev.flags & ide::DEVICE_ATAPI))
continue;
// Try to prevent the disc from keeping spinning unnecessarily.
ide::Packet packet;
packet.setStartStopUnit(ide::START_STOP_MODE_STOP_DISC);
if (dev.flags & ide::DEVICE_ATAPI) {
// Force the drive to stop the spindle motor immediately.
ide::Packet packet;
dev.atapiPacket(packet);
packet.setStartStopUnit(ide::START_STOP_MODE_STOP_DISC);
dev.atapiPacket(packet);
}
}
_workerStatus.update(2, 4, WSTR("App.startupWorker.initFAT"));
#if 0
// Attempt to mount the secondary drive first, then in case of failure try
// mounting the primary drive instead.
if (!_fileProvider.init("1:"))
_fileProvider.init("0:");
#else
_fileProvider.init("1:");
#endif
_workerStatus.update(2, 4, WSTR("App.startupWorker.initFileIO"));
_fileIO.initIDE();
_workerStatus.update(3, 4, WSTR("App.startupWorker.loadResources"));
_resourceFile = _fileProvider.openFile(
EXTERNAL_DATA_DIR "/resource.zip", file::READ
);
if (_resourceFile) {
_resourceProvider.close();
if (_resourceProvider.init(_resourceFile))
_loadResources();
}
if (_fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip"))
_loadResources();
_ctx.sounds[ui::SOUND_STARTUP].play();
return true;
@ -90,8 +73,8 @@ static const uint32_t _EXECUTABLE_OFFSETS[]{
bool App::_executableWorker(void) {
_workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
const char *path = _filePickerScreen.selectedPath;
auto _file = _fileProvider.openFile(path, file::READ);
const char *path = _fileBrowserScreen.selectedPath;
auto _file = _fileIO.vfs.openFile(path, file::READ);
if (!_file)
goto _fileOpenError;
@ -115,7 +98,7 @@ bool App::_executableWorker(void) {
_fileOpenError:
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
MESSAGE_ERROR, _fileBrowserScreen,
WSTR("App.executableWorker.fileError"), path
);
_workerStatus.setNextScreen(_messageScreen);
@ -154,7 +137,7 @@ _validFile:
// location and pass it the path to the executable to be loaded.
util::Data data;
if (!_resourceProvider.loadData(data, launcher.path))
if (!_fileIO.resource.loadData(data, launcher.path))
continue;
LOG("using %s", launcher.path);
@ -173,24 +156,13 @@ _validFile:
// All destructors must be invoked manually as we are not returning to
// main() before starting the new executable.
_unloadCartData();
_resourceProvider.close();
if (_resourceFile) {
_resourceFile->close();
delete _resourceFile;
}
_fileProvider.close();
_fileIO.close();
util::ExecutableLoader loader(
header, reinterpret_cast<void *>(launcherEnd)
);
char arg[128];
snprintf(
arg, sizeof(arg), "launcher.drive=%s", _fileProvider.getDriveString()
);
loader.copyArgument(arg);
snprintf(arg, sizeof(arg), "launcher.path=%s", path);
loader.copyArgument(arg);
@ -199,7 +171,7 @@ _validFile:
}
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
MESSAGE_ERROR, _fileBrowserScreen,
WSTR("App.executableWorker.addressError"), path, header.textOffset,
executableEnd - 1, stackTop
);
@ -246,14 +218,7 @@ bool App::_rebootWorker(void) {
_workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
_unloadCartData();
_resourceProvider.close();
if (_resourceFile) {
_resourceFile->close();
delete _resourceFile;
}
_fileProvider.close();
_fileIO.close();
_workerStatus.setStatus(WORKER_REBOOT);
// Fall back to a soft reboot if the watchdog fails to reset the system.

View File

@ -3,6 +3,8 @@
#include <stdio.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/filemisc.hpp"
#include "common/ide.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/app/modals.hpp"
@ -110,66 +112,26 @@ void ConfirmScreen::update(ui::Context &ctx) {
/* File picker screen */
void FilePickerScreen::_setPathToParent(void) {
auto ptr = __builtin_strrchr(_currentPath, '/');
if (ptr) {
size_t length = ptr - _currentPath;
__builtin_memcpy(selectedPath, _currentPath, length);
selectedPath[length] = 0;
} else {
selectedPath[0] = 0;
}
}
void FilePickerScreen::_setPathToChild(const char *entry) {
size_t length = __builtin_strlen(_currentPath);
if (length) {
__builtin_memcpy(selectedPath, _currentPath, length);
selectedPath[length++] = '/';
}
__builtin_strncpy(
&selectedPath[length], entry, sizeof(selectedPath) - length
);
}
void FilePickerScreen::_unloadDirectory(void) {
_numFiles = 0;
_numDirectories = 0;
_files.destroy();
_directories.destroy();
}
const char *FilePickerScreen::_getItemName(ui::Context &ctx, int index) const {
static char name[file::MAX_NAME_LENGTH]; // TODO: get rid of this ugly crap
if (_currentPath[0])
index--;
int drive = _drives[index];
auto &dev = ide::devices[drive];
auto fs = APP->_fileIO.ide[drive];
const char *path;
char icon;
if (dev.flags & ide::DEVICE_ATAPI)
name[0] = CH_CDROM_ICON;
else
name[0] = CH_HDD_ICON;
if (index < 0) {
path = STR("FilePickerScreen.parentDir");
icon = CH_PARENT_DIR_ICON;
} else if (index < _numDirectories) {
auto entries = _directories.as<file::FileInfo>();
path = entries[index].name;
icon = CH_DIR_ICON;
} else {
auto entries = _files.as<file::FileInfo>();
path = entries[index - _numDirectories].name;
icon = CH_FILE_ICON;
}
name[0] = icon;
name[1] = ' ';
__builtin_strncpy(&name[2], path, sizeof(name) - 2);
if (fs)
snprintf(
&name[2], sizeof(name) - 2, "%s: %s", dev.model, fs->volumeLabel
);
else
__builtin_strncpy(&name[2], dev.model, sizeof(name) - 2);
return name;
}
@ -188,12 +150,141 @@ void FilePickerScreen::setMessage(
va_end(ap);
}
int FilePickerScreen::loadDirectory(ui::Context &ctx, const char *path) {
int FilePickerScreen::loadRootAndShow(ui::Context &ctx) {
_listLength = 0;
for (size_t i = 0; i < util::countOf(ide::devices); i++) {
if (ide::devices[i].flags & ide::DEVICE_READY)
_drives[_listLength++] = i;
}
if (_listLength) {
ctx.show(APP->_filePickerScreen, false, true);
} else {
APP->_messageScreen.setMessage(
MESSAGE_ERROR, *_prevScreen, STR("FilePickerScreen.noDeviceError")
);
ctx.show(APP->_messageScreen, false, true);
}
return _listLength;
}
void FilePickerScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("FilePickerScreen.title");
_prompt = _promptText;
_itemPrompt = STR("FilePickerScreen.itemPrompt");
ListScreen::show(ctx, goBack);
}
void FilePickerScreen::update(ui::Context &ctx) {
ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (ctx.buttons.held(ui::BTN_LEFT) || ctx.buttons.held(ui::BTN_RIGHT)) {
ctx.show(*_prevScreen, true, true);
} else {
char name[6]{ "ide#:" };
int drive = _drives[_activeItem];
auto &dev = ide::devices[drive];
name[3] = drive + '0';
int count = APP->_fileBrowserScreen.loadDirectory(ctx, name);
if (count > 0) {
ctx.show(APP->_fileBrowserScreen, false, true);
} else {
util::Hash error;
if (!count)
error = "FilePickerScreen.noFilesError"_h;
else if (dev.flags & ide::DEVICE_ATAPI)
error = "FilePickerScreen.atapiError"_h;
else
error = "FilePickerScreen.ideError"_h;
APP->_messageScreen.setMessage(
MESSAGE_ERROR, *this, STRH(error)
);
ctx.show(APP->_messageScreen, false, true);
}
}
}
}
void FileBrowserScreen::_setPathToParent(void) {
auto ptr = __builtin_strrchr(_currentPath, '/');
if (ptr) {
size_t length = ptr - _currentPath;
__builtin_memcpy(selectedPath, _currentPath, length);
selectedPath[length] = 0;
} else {
ptr = __builtin_strchr(_currentPath, file::VFS_PREFIX_SEPARATOR);
*(++ptr) = 0;
}
}
void FileBrowserScreen::_setPathToChild(const char *entry) {
size_t length = __builtin_strlen(_currentPath);
if (length) {
__builtin_memcpy(selectedPath, _currentPath, length);
selectedPath[length++] = '/';
}
__builtin_strncpy(
&selectedPath[length], entry, sizeof(selectedPath) - length
);
}
void FileBrowserScreen::_unloadDirectory(void) {
_numFiles = 0;
_numDirectories = 0;
_files.destroy();
_directories.destroy();
}
const char *FileBrowserScreen::_getItemName(ui::Context &ctx, int index) const {
static char name[file::MAX_NAME_LENGTH]; // TODO: get rid of this ugly crap
if (!_isRoot)
index--;
const char *path;
if (index < 0) {
name[0] = CH_PARENT_DIR_ICON;
path = STR("FileBrowserScreen.parentDir");
} else if (index < _numDirectories) {
auto entries = _directories.as<file::FileInfo>();
name[0] = CH_DIR_ICON;
path = entries[index].name;
} else {
auto entries = _files.as<file::FileInfo>();
name[0] = CH_FILE_ICON;
path = entries[index - _numDirectories].name;
}
name[1] = ' ';
__builtin_strncpy(&name[2], path, sizeof(name) - 2);
return name;
}
int FileBrowserScreen::loadDirectory(ui::Context &ctx, const char *path) {
_unloadDirectory();
// Count the number of files and subfolders in the current directory, so
// that we can allocate enough space for them.
auto directory = APP->_fileProvider.openDirectory(path);
auto directory = APP->_fileIO.vfs.openDirectory(path);
if (!directory)
return -1;
@ -211,10 +302,11 @@ int FilePickerScreen::loadDirectory(ui::Context &ctx, const char *path) {
_activeItem = 0;
_listLength = _numFiles + _numDirectories;
if (path[0])
_isRoot = bool(!__builtin_strchr(path, '/'));
if (!_isRoot)
_listLength++;
LOG("path: %s", path);
LOG("files=%d, dirs=%d", _numFiles, _numDirectories);
file::FileInfo *files, *directories;
@ -225,7 +317,8 @@ int FilePickerScreen::loadDirectory(ui::Context &ctx, const char *path) {
directories = _directories.allocate<file::FileInfo>(_numDirectories);
// Iterate over all entries again to populate the newly allocated arrays.
directory = APP->_fileProvider.openDirectory(path);
directory = APP->_fileIO.vfs.openDirectory(path);
if (!directory)
return -1;
@ -243,50 +336,25 @@ int FilePickerScreen::loadDirectory(ui::Context &ctx, const char *path) {
return _numFiles + _numDirectories;
}
int FilePickerScreen::loadRootAndShow(ui::Context &ctx) {
int count = loadDirectory(ctx, "");
if (count > 0) {
ctx.show(*this, false, true);
} else {
auto error = (count < 0)
? "FilePickerScreen.rootError"_h
: "FilePickerScreen.noFilesError"_h;
APP->_messageScreen.setMessage(MESSAGE_ERROR, *_prevScreen, STRH(error));
ctx.show(APP->_messageScreen, false, true);
}
return count;
}
void FilePickerScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("FilePickerScreen.title");
_prompt = _promptText;
_itemPrompt = STR("FilePickerScreen.itemPrompt");
//loadDirectory(ctx, "");
void FileBrowserScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("FileBrowserScreen.title");
_prompt = APP->_filePickerScreen._promptText;
_itemPrompt = STR("FileBrowserScreen.itemPrompt");
ListScreen::show(ctx, goBack);
}
void FilePickerScreen::hide(ui::Context &ctx, bool goBack) {
//_unloadDirectory();
ListScreen::hide(ctx, goBack);
}
void FilePickerScreen::update(ui::Context &ctx) {
void FileBrowserScreen::update(ui::Context &ctx) {
ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (ctx.buttons.held(ui::BTN_LEFT) || ctx.buttons.held(ui::BTN_RIGHT)) {
_unloadDirectory();
ctx.show(*_prevScreen, true, true);
ctx.show(APP->_filePickerScreen, true, true);
} else {
int index = _activeItem;
if (_currentPath[0])
if (!_isRoot)
index--;
if (index < _numDirectories) {
@ -300,7 +368,7 @@ void FilePickerScreen::update(ui::Context &ctx) {
if (loadDirectory(ctx, selectedPath) < 0) {
APP->_messageScreen.setMessage(
MESSAGE_ERROR, *this,
STR("FilePickerScreen.subdirError"), selectedPath
STR("FileBrowserScreen.subdirError"), selectedPath
);
ctx.show(APP->_messageScreen, false, true);
@ -309,7 +377,7 @@ void FilePickerScreen::update(ui::Context &ctx) {
auto entries = _files.as<file::FileInfo>();
_setPathToChild(entries[index - _numDirectories].name);
_callback(ctx);
APP->_filePickerScreen._callback(ctx);
}
}
}

View File

@ -2,6 +2,7 @@
#pragma once
#include "common/file.hpp"
#include "common/ide.hpp"
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
@ -55,12 +56,34 @@ public:
/* File picker screen */
class FilePickerScreen : public ui::ListScreen {
friend class FileBrowserScreen;
private:
char _promptText[512];
ui::Screen *_prevScreen;
void (*_callback)(ui::Context &ctx);
char _currentPath[file::MAX_PATH_LENGTH];
int _drives[util::countOf(ide::devices)];
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
public:
void setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx),
const char *format, ...
);
int loadRootAndShow(ui::Context &ctx);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class FileBrowserScreen : public ui::ListScreen {
private:
char _currentPath[file::MAX_PATH_LENGTH];
bool _isRoot;
int _numFiles, _numDirectories;
util::Data _files, _directories;
@ -74,14 +97,8 @@ protected:
public:
char selectedPath[file::MAX_PATH_LENGTH];
void setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx),
const char *format, ...
);
int loadDirectory(ui::Context &ctx, const char *path);
int loadRootAndShow(ui::Context &ctx);
void show(ui::Context &ctx, bool goBack = false);
void hide(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};

View File

@ -235,7 +235,7 @@ void StorageActionsScreen::restore(ui::Context &ctx) {
STR("StorageActionsScreen.restore.filePrompt")
);
APP->_confirmScreen.setMessage(
APP->_filePickerScreen,
APP->_fileBrowserScreen,
[](ui::Context &ctx) {
APP->_setupWorker(&App::_romRestoreWorker);
ctx.show(APP->_workerStatusScreen, false, true);

View File

@ -108,7 +108,7 @@ bool App::_romDumpWorker(void) {
dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d"
))
goto _initError;
if (!_fileProvider.createDirectory(dirPath))
if (!_fileIO.vfs.createDirectory(dirPath))
goto _initError;
LOG("saving dumps to %s", dirPath);
@ -123,7 +123,7 @@ bool App::_romDumpWorker(void) {
snprintf(filePath, sizeof(filePath), entry.path, dirPath);
auto _file = _fileProvider.openFile(
auto _file = _fileIO.vfs.openFile(
filePath, file::WRITE | file::ALLOW_CREATE
);
@ -184,8 +184,8 @@ _fileError:
bool App::_romRestoreWorker(void) {
_workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init"));
const char *path = _filePickerScreen.selectedPath;
auto _file = _fileProvider.openFile(path, file::READ);
const char *path = _fileBrowserScreen.selectedPath;
auto _file = _fileIO.vfs.openFile(path, file::READ);
auto region = _storageActionsScreen.selectedRegion;
auto regionLength = _cardSizeScreen.selectedLength;

View File

@ -1,6 +1,5 @@
#include "common/args.hpp"
#include "common/file.hpp"
#include "common/gpu.hpp"
#include "common/io.hpp"
#include "common/spu.hpp"
@ -33,21 +32,16 @@ int main(int argc, const char **argv) {
return 1;
}
auto resourceProvider = new file::ZIPProvider;
resourceProvider->init(args.resourcePtr, args.resourceLength);
io::clearWatchdog();
auto gpuCtx = new gpu::Context(
GP1_MODE_NTSC, args.screenWidth, args.screenHeight, args.forceInterlace
);
auto uiCtx = new ui::Context(*gpuCtx);
auto app = new App(*uiCtx, *resourceProvider);
auto app = new App(*uiCtx);
gpu::enableDisplay(true);
spu::setMasterVolume(spu::MAX_VOLUME);
io::setMiscOutput(io::MISC_SPU_ENABLE, true);
app->run();
app->run(args.resourcePtr, args.resourceLength);
delete app;
delete uiCtx;

1
src/vendor/ff.c vendored
View File

@ -7125,7 +7125,6 @@ FRESULT f_getlbas (
cl = fp->obj.sclust; /* Origin of the chain */
if (cl != 0) {
do {
// TODO ofs
/* Get a fragment */
tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */
do {

View File

@ -189,7 +189,7 @@ def createParser() -> ArgumentParser:
)
group.add_argument(
"gameList",
type = Path,
type = FileType("rt"),
help = "Path to JSON file containing game list"
)
group.add_argument(
@ -206,7 +206,7 @@ def main():
args: Namespace = parser.parse_args()
setupLogger(args.verbose)
with args.gameList.open("rt") as _file:
with args.gameList as _file:
gameList: Sequence[Mapping[str, Any]] = json.load(_file)
gameDB: GameDB = GameDB(gameList)

View File

@ -131,7 +131,7 @@ def createParser() -> ArgumentParser:
)
group.add_argument(
"gameList",
type = Path,
type = FileType("rt"),
help = "Path to JSON file containing game list"
)
group.add_argument(
@ -148,7 +148,7 @@ def main():
args: Namespace = parser.parse_args()
setupLogger(args.verbose)
with args.gameList.open("rt") as _file:
with args.gameList as _file:
gameList: Sequence[Mapping[str, Any]] = json.load(_file)
gameDB: GameDB = GameDB(gameList)

View File

@ -213,16 +213,21 @@ _TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = {
b"DOWN_ARROW_ALT": b"\x85",
b"LEFT_ARROW_ALT": b"\x86",
b"RIGHT_ARROW_ALT": b"\x87",
b"LEFT_BUTTON": b"\x90",
b"RIGHT_BUTTON": b"\x91",
b"START_BUTTON": b"\x92",
b"CLOSED_LOCK": b"\x93",
b"OPEN_LOCK": b"\x94",
b"DIR_ICON": b"\x95",
b"PARENT_DIR_ICON": b"\x96",
b"FILE_ICON": b"\x97",
b"CHIP_ICON": b"\x98",
b"CART_ICON": b"\x99"
b"LEFT_BUTTON": b"\x90",
b"RIGHT_BUTTON": b"\x91",
b"START_BUTTON": b"\x92",
b"CLOSED_LOCK": b"\x93",
b"OPEN_LOCK": b"\x94",
b"CHIP_ICON": b"\x95",
b"CART_ICON": b"\x96",
b"CDROM_ICON": b"\xa0",
b"HDD_ICON": b"\xa1",
b"HOST_ICON": b"\xa2",
b"DIR_ICON": b"\xa3",
b"PARENT_DIR_ICON": b"\xa4",
b"FILE_ICON": b"\xa5"
}
def _convertString(string: str) -> bytes:

View File

@ -1,40 +1,22 @@
# -*- coding: utf-8 -*-
import logging, re
from collections import deque
from hashlib import md5
from io import SEEK_END, SEEK_SET
from typing import \
BinaryIO, ByteString, Iterable, Iterator, Mapping, MutableSequence, \
Sequence, TextIO
from hashlib import md5
from io import SEEK_END, SEEK_SET
from typing import \
Any, BinaryIO, ByteString, Iterable, Iterator, Sequence, TextIO
import numpy
## Value manipulation
## Value and array manipulation
def encodeSigned(value: int, bitLength: int) -> int:
return value & (1 << bitLength)
def signExtend(value: int, bitLength: int) -> int:
def decodeSigned(value: int, bitLength: int) -> int:
signMask: int = 1 << (bitLength - 1)
valueMask: int = signMask - 1
return (value & valueMask) - (value & signMask)
def blitArray(
source: numpy.ndarray, dest: numpy.ndarray, position: Sequence[int]
):
pos: map[int | None] = map(lambda x: x if x >= 0 else None, position)
neg: map[int | None] = map(lambda x: -x if x < 0 else None, position)
destView: numpy.ndarray = dest[tuple(
slice(start, None) for start in pos
)]
sourceView: numpy.ndarray = source[tuple(
slice(start, end) for start, end in zip(neg, destView.shape)
)]
destView[tuple(
slice(None, end) for end in source.shape
)] = sourceView
## String manipulation
# This encoding is similar to standard base45, but with some problematic
@ -166,6 +148,13 @@ class InterleavedFile(BinaryIO):
even.seek(0, SEEK_SET)
odd.seek(0, SEEK_SET)
def __enter__(self) -> BinaryIO:
return self
def __exit__(self, excType: Any, excValue: Any, traceback, Any) -> bool:
self.close()
return False
def close(self):
self._even.close()
self._odd.close()
@ -198,158 +187,3 @@ class InterleavedFile(BinaryIO):
self._offset += _length
return output
## Boolean algebra expression parser
class BooleanOperator:
precedence: int = 1
operands: int = 2
@staticmethod
def execute(stack: MutableSequence[bool]):
pass
class AndOperator(BooleanOperator):
precedence: int = 2
@staticmethod
def execute(stack: MutableSequence[bool]):
a: bool = stack.pop()
b: bool = stack.pop()
stack.append(a and b)
class OrOperator(BooleanOperator):
@staticmethod
def execute(stack: MutableSequence[bool]):
a: bool = stack.pop()
b: bool = stack.pop()
stack.append(a or b)
class XorOperator(BooleanOperator):
@staticmethod
def execute(stack: MutableSequence[bool]):
a: bool = stack.pop()
b: bool = stack.pop()
stack.append(a != b)
class NotOperator(BooleanOperator):
precedence: int = 3
operands: int = 1
@staticmethod
def execute(stack: MutableSequence):
stack.append(not stack.pop())
_OPERATORS: Mapping[str, type[BooleanOperator]] = {
"*": AndOperator,
"+": OrOperator,
"@": XorOperator,
"~": NotOperator
}
class BooleanFunction:
def __init__(self, expression: str):
# "Compile" the expression to its respective RPN representation using
# the shunting yard algorithm.
self.expression: list[str | type[BooleanOperator]] = []
operators: deque[str] = deque()
tokenBuffer: str = ""
for char in expression:
if char not in "*+@~()":
tokenBuffer += char
continue
# Flush the non-operator token buffer when an operator is
# encountered.
if tokenBuffer:
self.expression.append(tokenBuffer)
tokenBuffer = ""
match char:
case "(":
operators.append(char)
case ")":
if "(" not in operators:
raise RuntimeError("mismatched parentheses in expression")
while (op := operators.pop()) != "(":
self.expression.append(_OPERATORS[op])
case _:
precedence: int = _OPERATORS[char].precedence
while operators:
op: str = operators[-1]
if op == "(":
break
if _OPERATORS[op].precedence < precedence:
break
self.expression.append(_OPERATORS[op])
operators.pop()
operators.append(char)
if tokenBuffer:
self.expression.append(tokenBuffer)
tokenBuffer = ""
if "(" in operators:
raise RuntimeError("mismatched parentheses in expression")
while operators:
self.expression.append(_OPERATORS[operators.pop()])
def evaluate(self, variables: Mapping[str, bool]) -> bool:
values: dict[str, bool] = { "0": False, "1": True, **variables }
stack: deque[bool] = deque()
for token in self.expression:
if isinstance(token, str):
value: bool | None = values.get(token)
if value is None:
raise RuntimeError(f"unknown variable '{token}'")
stack.append(value)
else:
token.execute(stack)
if len(stack) != 1:
raise RuntimeError("invalid or malformed expression")
return stack[0]
## Logic lookup table conversion
def generateLUTFromExpression(expression: str, inputs: Sequence[str]) -> int:
lut: int = 0
function: BooleanFunction = BooleanFunction(expression)
variables: dict[str, bool] = {}
for index in range(1 << len(inputs)):
for bit, name in enumerate(inputs):
variables[name] = bool((index >> bit) & 1)
if function.evaluate(variables):
lut |= 1 << index # LSB-first
return lut
def generateExpressionFromLUT(lut: int, inputs: Sequence[str]) -> str:
products: list[str] = []
for index in range(1 << len(inputs)):
values: str = "*".join(
(value if (index >> bit) & 1 else f"~{value}")
for bit, value in enumerate(inputs)
)
if (lut >> index) & 1:
products.append(f"({values})")
return "+".join(products) or "0"