mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 19:52:05 +01:00
Add VFS and support for multiple filesystems
This commit is contained in:
parent
f9a7df6de8
commit
c558744fcd
@ -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
|
||||
|
@ -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": {
|
||||
|
@ -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 |
@ -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'
|
||||
};
|
||||
|
@ -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" };
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
235
src/common/filemisc.cpp
Normal 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 ∓
|
||||
}
|
||||
|
||||
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
75
src/common/filemisc.hpp
Normal 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);
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
1
src/vendor/ff.c
vendored
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user