Split off file picker screen, fix executable launcher

This commit is contained in:
spicyjpeg 2024-03-21 20:03:59 +01:00
parent d8aa1806b6
commit ed4cc53631
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
14 changed files with 339 additions and 246 deletions

View File

@ -108,6 +108,20 @@
"name": "View cartridge hexdump", "name": "View cartridge hexdump",
"prompt": "Display the raw contents of the cartridge's EEPROM in hexadecimal format." "prompt": "Display the raw contents of the cartridge's EEPROM in hexadecimal format."
}, },
"hddRestore": {
"name": "Restore cartridge from dump",
"prompt": "Wipe all data and restore the cartridge's contents from a dump that had been previously been saved to the IDE hard drive or CF card connected as secondary drive (if any)."
},
"reflash": {
"name": "Erase and convert cartridge to another game",
"prompt": "Wipe all data and flash the cartridge with another game's identifiers. All cartridges can be converted for use with any other game that uses the same cartridge type.",
"confirm": "The contents of the cartridge's EEPROM will be replaced, the system identifier (if any) will be cleared and the unlocking key will be updated to match the new game.\n\nDo you wish to proceed?"
},
"erase": {
"name": "Erase cartridge",
"prompt": "Wipe all data including game identifiers. Erased cartridges can be flashed for use with games unsupported by this tool using a master calendar.",
"confirm": "The contents of the cartridge's EEPROM will be erased, making it unusable until reflashed and resetting the unlocking key to 00-00-00-00-00-00-00-00. The cartridge ID will not be altered.\n\nDo you wish to proceed?"
},
"resetSystemID": { "resetSystemID": {
"name": "Reset system identifier (unpair cartridge)", "name": "Reset system identifier (unpair cartridge)",
"prompt": "Delete any previously saved system identifier, allowing the cartridge to be used to reinstall the game on any system.", "prompt": "Delete any previously saved system identifier, allowing the cartridge to be used to reinstall the game on any system.",
@ -125,16 +139,6 @@
"prompt": "Edit the saved system identifier to allow the cartridge to be used on a specific system without having to reinstall the game first.", "prompt": "Edit the saved system identifier to allow the cartridge to be used on a specific system without having to reinstall the game first.",
"confirm": "The system identifier will be changed, allowing the cartridge to be used on the respective system. If already installed on the new system, the game will not have to be reinstalled.\n\nDo you wish to proceed?", "confirm": "The system identifier will be changed, allowing the cartridge to be used on the respective system. If already installed on the new system, the game will not have to be reinstalled.\n\nDo you wish to proceed?",
"error": "The system identifier entered is invalid. Make sure all digits are correct and try again." "error": "The system identifier entered is invalid. Make sure all digits are correct and try again."
},
"reflash": {
"name": "Erase and convert cartridge to another game",
"prompt": "Wipe all data and flash the cartridge with another game's identifiers. All cartridges can be converted for use with any other game that uses the same cartridge type.",
"confirm": "The contents of the cartridge's EEPROM will be replaced, the system identifier (if any) will be cleared and the unlocking key will be updated to match the new game.\n\nDo you wish to proceed?"
},
"erase": {
"name": "Erase cartridge",
"prompt": "Wipe all data including game identifiers. Erased cartridges can be flashed for use with games unsupported by this tool using a master calendar.",
"confirm": "The contents of the cartridge's EEPROM will be erased, making it unusable until reflashed and resetting the unlocking key to 00-00-00-00-00-00-00-00. The cartridge ID will not be altered.\n\nDo you wish to proceed?"
} }
}, },
@ -204,9 +208,8 @@
"yes": "Yes, continue" "yes": "Yes, continue"
}, },
"ExecPickerScreen": { "FilePickerScreen": {
"title": "{CART_ICON} Select executable to run", "title": "{CART_ICON} Select file from hard drive",
"prompt": "Note that PlayStation executables built without proper System 573 support will not run unless the watchdog is manually disabled.",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back", "itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
"parentDir": "[Parent directory]", "parentDir": "[Parent directory]",
@ -257,8 +260,9 @@
"prompt": "View information about this tool, including open source licenses." "prompt": "View information about this tool, including open source licenses."
}, },
"runExecutable": { "runExecutable": {
"name": "Run executable from hard drive", "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)." "prompt": "Load and launch a System 573 executable file from the IDE hard drive or CF card connected as secondary drive (if any).",
"filePrompt": "Note that PlayStation executables built without proper System 573 support will not run unless the watchdog is manually disabled."
}, },
"ejectCD": { "ejectCD": {
"name": "Eject CD-ROM", "name": "Eject CD-ROM",

View File

@ -209,7 +209,7 @@ DeviceError Device::enumerate(void) {
delayMicroseconds(5000); delayMicroseconds(5000);
_write(CS1_DEVICE_CTRL, CS1_DEVICE_CTRL_IEN); _write(CS1_DEVICE_CTRL, CS1_DEVICE_CTRL_IEN);
delayMicroseconds(5000); delayMicroseconds(5000);
_select(0); _select();
auto error = _waitForStatus(CS0_STATUS_BSY, 0, _RESET_STATUS_TIMEOUT); auto error = _waitForStatus(CS0_STATUS_BSY, 0, _RESET_STATUS_TIMEOUT);
if (error) if (error)
@ -333,12 +333,32 @@ DeviceError Device::_ideReadWrite(
return NO_ERROR; return NO_ERROR;
} }
DeviceError Device::ideIdle(bool standby) {
_select();
auto error = _command(standby ? ATA_STANDBY_IMMEDIATE : ATA_IDLE_IMMEDIATE);
#if 0
if (error) {
// If the immediate command failed, fall back to setting the inactivity
// timeout to the lowest allowed value (5 seconds).
// FIXME: the original timeout would have to be restored once the drive
// is accessed again
_write(CS0_COUNT, 1);
return _command(standby ? ATA_STANDBY : ATA_IDLE);
}
#endif
return error;
}
DeviceError Device::ideFlushCache(void) { DeviceError Device::ideFlushCache(void) {
if (!(flags & DEVICE_HAS_FLUSH)) if (!(flags & DEVICE_HAS_FLUSH))
return NO_ERROR; return NO_ERROR;
//return UNSUPPORTED_OP; //return UNSUPPORTED_OP;
_select(CS0_DEVICE_SEL_LBA); _select();
return _command( return _command(
(flags & DEVICE_HAS_LBA48) ? ATA_FLUSH_CACHE_EXT : ATA_FLUSH_CACHE (flags & DEVICE_HAS_LBA48) ? ATA_FLUSH_CACHE_EXT : ATA_FLUSH_CACHE
); );
@ -348,7 +368,7 @@ DeviceError Device::atapiPacket(Packet &packet, size_t transferLength) {
if (!(flags & DEVICE_ATAPI)) if (!(flags & DEVICE_ATAPI))
return UNSUPPORTED_OP; return UNSUPPORTED_OP;
_select(0); _select();
_write(CS0_CYLINDER_L, (transferLength >> 0) & 0xff); _write(CS0_CYLINDER_L, (transferLength >> 0) & 0xff);
_write(CS0_CYLINDER_H, (transferLength >> 8) & 0xff); _write(CS0_CYLINDER_H, (transferLength >> 8) & 0xff);

View File

@ -79,6 +79,7 @@ enum ATACommand : uint8_t {
ATA_IDLE_IMMEDIATE = 0xe1, ATA_IDLE_IMMEDIATE = 0xe1,
ATA_STANDBY = 0xe2, ATA_STANDBY = 0xe2,
ATA_IDLE = 0xe3, ATA_IDLE = 0xe3,
ATA_CHECK_POWER_MODE = 0xe5,
ATA_SLEEP = 0xe6, ATA_SLEEP = 0xe6,
ATA_FLUSH_CACHE = 0xe7, ATA_FLUSH_CACHE = 0xe7,
ATA_FLUSH_CACHE_EXT = 0xea, ATA_FLUSH_CACHE_EXT = 0xea,
@ -310,7 +311,7 @@ private:
SYS573_IDE_CS1_BASE[reg] = value; SYS573_IDE_CS1_BASE[reg] = value;
} }
inline void _select(uint8_t regFlags) { inline void _select(uint8_t regFlags = 0) {
if (flags & DEVICE_SECONDARY) if (flags & DEVICE_SECONDARY)
_write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_SECONDARY); _write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_SECONDARY);
else else
@ -347,6 +348,7 @@ public:
} }
DeviceError enumerate(void); DeviceError enumerate(void);
DeviceError ideIdle(bool standby = false);
DeviceError ideFlushCache(void); DeviceError ideFlushCache(void);
DeviceError atapiPacket( DeviceError atapiPacket(
Packet &packet, size_t transferLength = ATAPI_SECTOR_SIZE Packet &packet, size_t transferLength = ATAPI_SECTOR_SIZE

View File

@ -285,6 +285,7 @@ void ExecutableLoader::addArgument(const char *arg) {
[[noreturn]] void ExecutableLoader::run(void) { [[noreturn]] void ExecutableLoader::run(void) {
disableInterrupts(); disableInterrupts();
flushCache();
register int a0 __asm__("a0") = _argCount; register int a0 __asm__("a0") = _argCount;
register char **a1 __asm__("a1") = _argListPtr; register char **a1 __asm__("a1") = _argListPtr;

View File

@ -172,7 +172,7 @@ void Launcher::exit(void) {
if (_fs.fs_type) if (_fs.fs_type)
f_unmount(_settings.drive); f_unmount(_settings.drive);
uninstallExceptionHandler(); //uninstallExceptionHandler();
} }
[[noreturn]] void Launcher::run(void) { [[noreturn]] void Launcher::run(void) {
@ -186,6 +186,10 @@ void Launcher::exit(void) {
} }
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
#if 0
// Exception handling code bloats the binary significantly (especially in
// debug builds, as it pulls in the crash handler), so the watchdog is
// cleared manually instead.
installExceptionHandler(); installExceptionHandler();
io::init(); io::init();
@ -196,6 +200,7 @@ int main(int argc, const char **argv) {
IRQ_MASK = 1 << IRQ_VSYNC; IRQ_MASK = 1 << IRQ_VSYNC;
enableInterrupts(); enableInterrupts();
#endif
Settings settings; Settings settings;
Launcher launcher(settings); Launcher launcher(settings);
@ -210,9 +215,11 @@ int main(int argc, const char **argv) {
//util::logger.setupSyslog(settings.baudRate); //util::logger.setupSyslog(settings.baudRate);
io::clearWatchdog();
if (!launcher.openFile()) if (!launcher.openFile())
return 1; return 1;
io::clearWatchdog();
if (!launcher.readHeader(0)) { if (!launcher.readHeader(0)) {
// If the file is not an executable, check if it is a flash image that // If the file is not an executable, check if it is a flash image that
// contains an executable. Note that the CRC32 is not validated. // contains an executable. Note that the CRC32 is not validated.
@ -220,9 +227,11 @@ int main(int argc, const char **argv) {
return 2; return 2;
} }
io::clearWatchdog();
if (!launcher.readBody()) if (!launcher.readBody())
return 3; return 3;
io::clearWatchdog();
launcher.run(); launcher.run();
return 0; return 0;
} }

View File

@ -71,13 +71,14 @@ _driver(nullptr), _parser(nullptr), _identified(nullptr) {
App::~App(void) { App::~App(void) {
_unloadCartData(); _unloadCartData();
//_resourceProvider.close();
if (_resourceFile)
delete _resourceFile;
//_fileProvider.close();
delete[] _workerStack; delete[] _workerStack;
if (_resourceFile) {
//_resourceFile->close();
delete _resourceFile;
}
} }
void App::_unloadCartData(void) { void App::_unloadCartData(void) {

View File

@ -98,13 +98,13 @@ class App {
friend class WorkerStatusScreen; friend class WorkerStatusScreen;
friend class MessageScreen; friend class MessageScreen;
friend class ConfirmScreen; friend class ConfirmScreen;
friend class FilePickerScreen;
friend class WarningScreen; friend class WarningScreen;
friend class ButtonMappingScreen; friend class ButtonMappingScreen;
friend class MainMenuScreen; friend class MainMenuScreen;
friend class SystemInfoScreen; friend class SystemInfoScreen;
friend class ResolutionScreen; friend class ResolutionScreen;
friend class AboutScreen; friend class AboutScreen;
friend class ExecPickerScreen;
friend class CartInfoScreen; friend class CartInfoScreen;
friend class UnlockKeyScreen; friend class UnlockKeyScreen;
friend class KeyEntryScreen; friend class KeyEntryScreen;
@ -118,13 +118,13 @@ private:
WorkerStatusScreen _workerStatusScreen; WorkerStatusScreen _workerStatusScreen;
MessageScreen _messageScreen; MessageScreen _messageScreen;
ConfirmScreen _confirmScreen; ConfirmScreen _confirmScreen;
FilePickerScreen _filePickerScreen;
WarningScreen _warningScreen; WarningScreen _warningScreen;
ButtonMappingScreen _buttonMappingScreen; ButtonMappingScreen _buttonMappingScreen;
MainMenuScreen _mainMenuScreen; MainMenuScreen _mainMenuScreen;
SystemInfoScreen _systemInfoScreen; SystemInfoScreen _systemInfoScreen;
ResolutionScreen _resolutionScreen; ResolutionScreen _resolutionScreen;
AboutScreen _aboutScreen; AboutScreen _aboutScreen;
ExecPickerScreen _execPickerScreen;
CartInfoScreen _cartInfoScreen; CartInfoScreen _cartInfoScreen;
UnlockKeyScreen _unlockKeyScreen; UnlockKeyScreen _unlockKeyScreen;
KeyEntryScreen _keyEntryScreen; KeyEntryScreen _keyEntryScreen;

View File

@ -177,18 +177,16 @@ void MainMenuScreen::about(ui::Context &ctx) {
} }
void MainMenuScreen::runExecutable(ui::Context &ctx) { void MainMenuScreen::runExecutable(ui::Context &ctx) {
int count = APP->_execPickerScreen.loadDirectory(ctx, ""); APP->_filePickerScreen.setMessage(
*this,
[](ui::Context &ctx) {
APP->_setupWorker(&App::_executableWorker);
ctx.show(APP->_workerStatusScreen, false, true);
},
STR("MainMenuScreen.runExecutable.filePrompt")
);
if (count > 0) { APP->_filePickerScreen.loadRootAndShow(ctx);
ctx.show(APP->_execPickerScreen, false, true);
} else {
auto error = (count < 0)
? "ExecPickerScreen.rootError"_h
: "ExecPickerScreen.noFilesError"_h;
APP->_messageScreen.setMessage(MESSAGE_ERROR, *this, STRH(error));
ctx.show(APP->_messageScreen, false, true);
}
} }
void MainMenuScreen::ejectCD(ui::Context &ctx) { void MainMenuScreen::ejectCD(ui::Context &ctx) {

View File

@ -198,183 +198,6 @@ void SystemInfoScreen::update(ui::Context &ctx) {
ctx.show(APP->_mainMenuScreen, true, true); ctx.show(APP->_mainMenuScreen, true, true);
} }
/* Executable picker screen */
void ExecPickerScreen::_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 ExecPickerScreen::_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 ExecPickerScreen::_unloadDirectory(void) {
_numFiles = 0;
_numDirectories = 0;
_files.destroy();
_directories.destroy();
}
const char *ExecPickerScreen::_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--;
const char *path;
char icon;
if (index < 0) {
path = STR("ExecPickerScreen.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);
return name;
}
int ExecPickerScreen::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);
if (!directory)
return -1;
file::FileInfo info;
while (directory->getEntry(info)) {
if (info.attributes & file::DIRECTORY)
_numDirectories++;
else
_numFiles++;
}
delete directory;
_activeItem = 0;
_listLength = _numFiles + _numDirectories;
if (path[0])
_listLength++;
LOG("path: %s", path);
LOG("found %d files, %d folders", _numFiles, _numDirectories);
if (_numFiles)
_files.allocate(sizeof(file::FileInfo) * _numFiles);
if (_numDirectories)
_directories.allocate(sizeof(file::FileInfo) * _numDirectories);
auto files = _files.as<file::FileInfo>();
auto directories = _directories.as<file::FileInfo>();
// Iterate over all entries again to populate the newly allocated arrays.
directory = APP->_fileProvider.openDirectory(path);
if (!directory)
return -1;
while (directory->getEntry(info)) {
auto ptr = (info.attributes & file::DIRECTORY)
? (directories++) : (files++);
__builtin_memcpy(ptr, &info, sizeof(file::FileInfo));
}
delete directory;
__builtin_strncpy(_currentPath, path, sizeof(_currentPath));
return _numFiles + _numDirectories;
}
void ExecPickerScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("ExecPickerScreen.title");
_prompt = STR("ExecPickerScreen.prompt");
_itemPrompt = STR("ExecPickerScreen.itemPrompt");
//loadDirectory(ctx, "");
ListScreen::show(ctx, goBack);
}
void ExecPickerScreen::hide(ui::Context &ctx, bool goBack) {
//_unloadDirectory();
ListScreen::hide(ctx, goBack);
}
void ExecPickerScreen::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(APP->_mainMenuScreen, true, true);
} else {
int index = _activeItem;
if (_currentPath[0])
index--;
if (index < _numDirectories) {
auto entries = _directories.as<file::FileInfo>();
if (index < 0)
_setPathToParent();
else
_setPathToChild(entries[index].name);
if (loadDirectory(ctx, selectedPath) < 0) {
APP->_messageScreen.setMessage(
MESSAGE_ERROR, *this,
STR("ExecPickerScreen.subdirError"), selectedPath
);
ctx.show(APP->_messageScreen, false, true);
}
} else {
auto entries = _files.as<file::FileInfo>();
_setPathToChild(entries[index - _numDirectories].name);
APP->_setupWorker(&App::_executableWorker);
ctx.show(APP->_workerStatusScreen, false, true);
}
}
}
}
/* Misc. screens */ /* Misc. screens */
struct Resolution { struct Resolution {
@ -453,7 +276,7 @@ void ResolutionScreen::update(ui::Context &ctx) {
ListScreen::update(ctx); ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) { if (ctx.buttons.pressed(ui::BTN_START)) {
if (!ctx.buttons.held(ui::BTN_LEFT) && !ctx.buttons.held(ui::BTN_RIGHT)) if (!ctx.buttons.held(ui::BTN_LEFT) && !ctx.buttons.held(ui::BTN_RIGHT))
ctx.gpuCtx.setResolution( ctx.gpuCtx.setResolution(
GP1_MODE_NTSC, res.width, res.height, res.forceInterlace GP1_MODE_NTSC, res.width, res.height, res.forceInterlace
); );

View File

@ -17,30 +17,6 @@ public:
void update(ui::Context &ctx); void update(ui::Context &ctx);
}; };
/* Executable picker screen */
class ExecPickerScreen : public ui::ListScreen {
private:
char _currentPath[file::MAX_PATH_LENGTH];
int _numFiles, _numDirectories;
util::Data _files, _directories;
void _setPathToParent(void);
void _setPathToChild(const char *entry);
void _unloadDirectory(void);
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
public:
char selectedPath[file::MAX_PATH_LENGTH];
int loadDirectory(ui::Context &ctx, const char *path);
void show(ui::Context &ctx, bool goBack = false);
void hide(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
/* Misc. screens */ /* Misc. screens */
class ResolutionScreen : public ui::ListScreen { class ResolutionScreen : public ui::ListScreen {

View File

@ -20,8 +20,20 @@ bool App::_startupWorker(void) {
#endif #endif
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
auto &dev = ide::devices[i];
_workerStatus.update(i, 4, WSTR("App.startupWorker.initIDE")); _workerStatus.update(i, 4, WSTR("App.startupWorker.initIDE"));
ide::devices[i].enumerate();
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);
dev.atapiPacket(packet);
} }
_workerStatus.update(2, 4, WSTR("App.startupWorker.initFAT")); _workerStatus.update(2, 4, WSTR("App.startupWorker.initFAT"));
@ -283,7 +295,7 @@ static const LauncherEntry _LAUNCHERS[]{
bool App::_executableWorker(void) { bool App::_executableWorker(void) {
_workerStatus.update(0, 1, WSTR("App.executableWorker.init")); _workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
const char *path = _execPickerScreen.selectedPath; const char *path = _filePickerScreen.selectedPath;
util::ExecutableHeader header; util::ExecutableHeader header;
uintptr_t executableEnd, stackTop; uintptr_t executableEnd, stackTop;
@ -372,16 +384,18 @@ bool App::_executableWorker(void) {
// main() before starting the new executable. // main() before starting the new executable.
_unloadCartData(); _unloadCartData();
_resourceProvider.close(); _resourceProvider.close();
if (_resourceFile) if (_resourceFile)
delete _resourceFile; delete _resourceFile;
_fileProvider.close(); _fileProvider.close();
uninstallExceptionHandler(); uninstallExceptionHandler();
loader.run(); loader.run();
} }
_messageScreen.setMessage( _messageScreen.setMessage(
MESSAGE_ERROR, _execPickerScreen, MESSAGE_ERROR, _filePickerScreen,
WSTR("App.executableWorker.addressError"), path, header.textOffset, WSTR("App.executableWorker.addressError"), path, header.textOffset,
executableEnd - 1, stackTop executableEnd - 1, stackTop
); );
@ -393,7 +407,7 @@ _fileError:
_fileOpenError: _fileOpenError:
_messageScreen.setMessage( _messageScreen.setMessage(
MESSAGE_ERROR, _execPickerScreen, MESSAGE_ERROR, _filePickerScreen,
WSTR("App.executableWorker.fileError"), path WSTR("App.executableWorker.fileError"), path
); );
_workerStatus.setNextScreen(_messageScreen); _workerStatus.setNextScreen(_messageScreen);

View File

@ -1,10 +1,13 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/util.hpp" #include "common/util.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/modals.hpp" #include "main/app/modals.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "main/uicommon.hpp"
/* Modal screens */ /* Modal screens */
@ -103,3 +106,209 @@ void ConfirmScreen::update(ui::Context &ctx) {
ctx.show(*_prevScreen, true, true); ctx.show(*_prevScreen, true, true);
} }
} }
/* 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--;
const char *path;
char 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);
return name;
}
void FilePickerScreen::setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx), const char *format,
...
) {
_prevScreen = &prev;
_callback = callback;
va_list ap;
va_start(ap, format);
vsnprintf(_promptText, sizeof(_promptText), format, ap);
va_end(ap);
}
int FilePickerScreen::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);
if (!directory)
return -1;
file::FileInfo info;
while (directory->getEntry(info)) {
if (info.attributes & file::DIRECTORY)
_numDirectories++;
else
_numFiles++;
}
delete directory;
_activeItem = 0;
_listLength = _numFiles + _numDirectories;
if (path[0])
_listLength++;
LOG("path: %s", path);
LOG("files=%d, dirs=%d", _numFiles, _numDirectories);
if (_numFiles)
_files.allocate(sizeof(file::FileInfo) * _numFiles);
if (_numDirectories)
_directories.allocate(sizeof(file::FileInfo) * _numDirectories);
auto files = _files.as<file::FileInfo>();
auto directories = _directories.as<file::FileInfo>();
// Iterate over all entries again to populate the newly allocated arrays.
directory = APP->_fileProvider.openDirectory(path);
if (!directory)
return -1;
while (directory->getEntry(info)) {
auto ptr = (info.attributes & file::DIRECTORY)
? (directories++) : (files++);
__builtin_memcpy(ptr, &info, sizeof(file::FileInfo));
}
delete directory;
__builtin_strncpy(_currentPath, path, sizeof(_currentPath));
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, *this, 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, "");
ListScreen::show(ctx, goBack);
}
void FilePickerScreen::hide(ui::Context &ctx, bool goBack) {
//_unloadDirectory();
ListScreen::hide(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)) {
_unloadDirectory();
ctx.show(*_prevScreen, true, true);
} else {
int index = _activeItem;
if (_currentPath[0])
index--;
if (index < _numDirectories) {
auto entries = _directories.as<file::FileInfo>();
if (index < 0)
_setPathToParent();
else
_setPathToChild(entries[index].name);
if (loadDirectory(ctx, selectedPath) < 0) {
APP->_messageScreen.setMessage(
MESSAGE_ERROR, *this,
STR("FilePickerScreen.subdirError"), selectedPath
);
ctx.show(APP->_messageScreen, false, true);
}
} else {
auto entries = _files.as<file::FileInfo>();
_setPathToChild(entries[index - _numDirectories].name);
_callback(ctx);
}
}
}
}

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "common/file.hpp"
#include "common/util.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "main/uicommon.hpp" #include "main/uicommon.hpp"
@ -47,3 +49,37 @@ public:
void show(ui::Context &ctx, bool goBack = false); void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx); void update(ui::Context &ctx);
}; };
/* File picker screen */
class FilePickerScreen : public ui::ListScreen {
private:
char _promptText[512];
ui::Screen *_prevScreen;
void (*_callback)(ui::Context &ctx);
char _currentPath[file::MAX_PATH_LENGTH];
int _numFiles, _numDirectories;
util::Data _files, _directories;
void _setPathToParent(void);
void _setPathToChild(const char *entry);
void _unloadDirectory(void);
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
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

@ -100,8 +100,8 @@ void setInterruptHandler(ArgFunction func, void *arg) {
} }
void flushCache(void) { void flushCache(void) {
//if (!_flushCache) if (!_flushCache)
//_flushCache = BIOS_API_TABLE[0x44]; _flushCache = BIOS_API_TABLE[0x44];
bool enable = disableInterrupts(); bool enable = disableInterrupts();