diff --git a/assets/app.strings.json b/assets/app.strings.json index 0be2d98..097c2c1 100644 --- a/assets/app.strings.json +++ b/assets/app.strings.json @@ -204,6 +204,17 @@ "yes": "Yes, continue" }, + "ExecPickerScreen": { + "title": "{CART_ICON} Select executable to run", + "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", + + "parentDir": "[Parent directory]", + "noFilesError": "No files or directories have been found in the root of the hard drive's file system.", + "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 file system may be corrupted or otherwise inaccessible.\n\nPath: %s\nPress the Test button to view debug logs." + }, + "HexdumpScreen": { "title": "{CART_ICON} Cartridge dump", "prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back." diff --git a/assets/textures/font.metrics.json b/assets/textures/font.metrics.json index 7135d19..430bb35 100644 --- a/assets/textures/font.metrics.json +++ b/assets/textures/font.metrics.json @@ -105,6 +105,10 @@ "\u0081": { "x": 6, "y": 54, "width": 6, "height": 9 }, "\u0082": { "x": 12, "y": 54, "width": 4, "height": 9 }, "\u0083": { "x": 18, "y": 54, "width": 4, "height": 9 }, + "\u0084": { "x": 24, "y": 54, "width": 6, "height": 9 }, + "\u0085": { "x": 30, "y": 54, "width": 6, "height": 9 }, + "\u0086": { "x": 36, "y": 54, "width": 6, "height": 9 }, + "\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 }, @@ -114,9 +118,7 @@ "\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": 9, "height": 10, "icon": true }, - "\u0099": { "x": 78, "y": 63, "width": 9, "height": 10, "icon": true }, - "\u009a": { "x": 0, "y": 73, "width": 12, "height": 10, "icon": true }, - "\u009b": { "x": 12, "y": 73, "width": 14, "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 } } } diff --git a/assets/textures/font.png b/assets/textures/font.png index 21c4f0e..9dfb96e 100644 Binary files a/assets/textures/font.png and b/assets/textures/font.png differ diff --git a/src/common/defs.hpp b/src/common/defs.hpp index 5aa34b7..ccc8b13 100644 --- a/src/common/defs.hpp +++ b/src/common/defs.hpp @@ -13,19 +13,23 @@ #define EXTERNAL_DATA_DIR "cartdata" -#define CH_UP_ARROW "\x80" -#define CH_DOWN_ARROW "\x81" -#define CH_LEFT_ARROW "\x82" -#define CH_RIGHT_ARROW "\x83" -#define CH_LEFT_BUTTON "\x90" -#define CH_RIGHT_BUTTON "\x91" -#define CH_START_BUTTON "\x92" -#define CH_CLOSED_LOCK "\x93" -#define CH_OPEN_LOCK "\x94" -#define CH_DIR_ICON "\x95" -#define CH_PARENT_DIR_ICON "\x96" -#define CH_FILE_ICON "\x97" -#define CH_EXE_FILE_ICON "\x98" -#define CH_DUMP_FILE_ICON "\x99" -#define CH_CHIP_ICON "\x9a" -#define CH_CART_ICON "\x9b" +enum Character : char { + CH_UP_ARROW = '\x80', + CH_DOWN_ARROW = '\x81', + CH_LEFT_ARROW = '\x82', + CH_RIGHT_ARROW = '\x83', + CH_UP_ARROW_ALT = '\x84', + 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' +}; diff --git a/src/common/file.cpp b/src/common/file.cpp index 4e553f6..0a790a6 100644 --- a/src/common/file.cpp +++ b/src/common/file.cpp @@ -175,9 +175,7 @@ Directory::~Directory(void) { close(); } -bool FATDirectory::getEntry( - FileInfo &output, uint32_t attrMask, uint32_t attrValue -) { +bool FATDirectory::getEntry(FileInfo &output) { FILINFO info; auto error = f_readdir(&_fd, &info); @@ -188,10 +186,7 @@ bool FATDirectory::getEntry( if (!info.fname[0]) return false; - __builtin_strncpy( - output.name, info.fname, - util::min(sizeof(output.name), sizeof(info.fname)) - ); + __builtin_strncpy(output.name, info.fname, sizeof(output.name)); output.length = info.fsize; output.attributes = info.fattrib; @@ -473,10 +468,7 @@ bool FATProvider::getFileInfo(FileInfo &output, const char *path) { return false; } - __builtin_strncpy( - output.name, info.fname, - util::min(sizeof(output.name), sizeof(info.fname)) - ); + __builtin_strncpy(output.name, info.fname, sizeof(output.name)); output.length = info.fsize; output.attributes = info.fattrib; @@ -611,10 +603,14 @@ bool ZIPProvider::getFileInfo(FileInfo &output, const char *path) { if (!info.m_is_supported) return false; - __builtin_strncpy( - output.name, info.m_filename, - util::min(sizeof(output.name), sizeof(info.m_filename)) - ); + auto ptr = __builtin_strrchr(info.m_filename, '/'); + + if (ptr) + ptr++; + else + ptr = info.m_filename; + + __builtin_strncpy(output.name, ptr, sizeof(output.name)); output.length = info.m_uncomp_size; output.attributes = READ_ONLY | ARCHIVE; diff --git a/src/common/file.hpp b/src/common/file.hpp index 8d47c5e..d2afe97 100644 --- a/src/common/file.hpp +++ b/src/common/file.hpp @@ -13,6 +13,7 @@ namespace file { /* File classes */ +static constexpr size_t MAX_NAME_LENGTH = 64; static constexpr size_t MAX_PATH_LENGTH = 256; // The first 4 of these map to the FS_* enum used by FatFs. @@ -46,7 +47,7 @@ enum FileAttributeFlag { struct FileInfo { public: - char name[MAX_PATH_LENGTH]; + char name[MAX_NAME_LENGTH]; uint64_t length; uint32_t attributes; }; @@ -102,9 +103,7 @@ class Directory { public: virtual ~Directory(void); - virtual bool getEntry( - FileInfo &output, uint32_t attrMask, uint32_t attrValue - ) { return false; } + virtual bool getEntry(FileInfo &output) { return false; } virtual void close(void) {} }; @@ -115,7 +114,7 @@ private: DIR _fd; public: - bool getEntry(FileInfo &output, uint32_t attrMask, uint32_t attrValue); + bool getEntry(FileInfo &output); void close(void); }; diff --git a/src/common/util.hpp b/src/common/util.hpp index 095c561..129e315 100644 --- a/src/common/util.hpp +++ b/src/common/util.hpp @@ -89,12 +89,15 @@ public: template inline T *as(void) { return reinterpret_cast(ptr); } + template inline const T *as(void) const { + return reinterpret_cast(ptr); + } inline void *allocate(size_t _length) { if (ptr) - free(ptr); + delete[] as(); - ptr = new uint8_t[length]; + ptr = _length ? (new uint8_t[_length]) : nullptr; length = _length; return ptr; diff --git a/src/main/app/app.hpp b/src/main/app/app.hpp index 85a2cc7..6aee6fc 100644 --- a/src/main/app/app.hpp +++ b/src/main/app/app.hpp @@ -104,6 +104,7 @@ class App { friend class SystemInfoScreen; friend class ResolutionScreen; friend class AboutScreen; + friend class ExecPickerScreen; friend class CartInfoScreen; friend class UnlockKeyScreen; friend class KeyEntryScreen; @@ -123,6 +124,7 @@ private: SystemInfoScreen _systemInfoScreen; ResolutionScreen _resolutionScreen; AboutScreen _aboutScreen; + ExecPickerScreen _execPickerScreen; CartInfoScreen _cartInfoScreen; UnlockKeyScreen _unlockKeyScreen; KeyEntryScreen _keyEntryScreen; diff --git a/src/main/app/main.cpp b/src/main/app/main.cpp index 0d6d44e..0abee23 100644 --- a/src/main/app/main.cpp +++ b/src/main/app/main.cpp @@ -177,8 +177,18 @@ void MainMenuScreen::about(ui::Context &ctx) { } void MainMenuScreen::runExecutable(ui::Context &ctx) { - APP->_setupWorker(&App::_executableWorker); - ctx.show(APP->_workerStatusScreen, false, true); + int count = APP->_execPickerScreen.loadDirectory(ctx, ""); + + if (count > 0) { + 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) { diff --git a/src/main/app/misc.cpp b/src/main/app/misc.cpp index c43f0c5..983be59 100644 --- a/src/main/app/misc.cpp +++ b/src/main/app/misc.cpp @@ -1,10 +1,13 @@ #include +#include "common/defs.hpp" +#include "common/file.hpp" #include "common/ide.hpp" #include "common/rom.hpp" #include "common/util.hpp" #include "main/app/app.hpp" #include "main/app/misc.hpp" +#include "main/app/modals.hpp" #include "main/uibase.hpp" #include "ps1/gpucmd.h" @@ -195,6 +198,183 @@ void SystemInfoScreen::update(ui::Context &ctx) { 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(); + + path = entries[index].name; + icon = CH_DIR_ICON; + } else { + auto entries = _files.as(); + + 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(); + auto directories = _directories.as(); + + // 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(); + + 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(); + + _setPathToChild(entries[index - _numDirectories].name); + + APP->_setupWorker(&App::_executableWorker); + ctx.show(APP->_workerStatusScreen, false, true); + } + } + } +} + /* Misc. screens */ struct Resolution { diff --git a/src/main/app/misc.hpp b/src/main/app/misc.hpp index fbb581a..b400af1 100644 --- a/src/main/app/misc.hpp +++ b/src/main/app/misc.hpp @@ -1,6 +1,7 @@ #pragma once +#include "common/file.hpp" #include "common/util.hpp" #include "main/uibase.hpp" #include "main/uicommon.hpp" @@ -16,6 +17,30 @@ public: 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 */ class ResolutionScreen : public ui::ListScreen { diff --git a/src/main/app/miscworkers.cpp b/src/main/app/miscworkers.cpp index e661945..3c7f63a 100644 --- a/src/main/app/miscworkers.cpp +++ b/src/main/app/miscworkers.cpp @@ -283,8 +283,7 @@ static const LauncherEntry _LAUNCHERS[]{ bool App::_executableWorker(void) { _workerStatus.update(0, 1, WSTR("App.executableWorker.init")); - // TODO: implement a file picker screen - const char *path = "psx.exe"; + const char *path = _execPickerScreen.selectedPath; util::ExecutableHeader header; uintptr_t executableEnd, stackTop; @@ -371,7 +370,7 @@ bool App::_executableWorker(void) { } _messageScreen.setMessage( - MESSAGE_ERROR, _mainMenuScreen, + MESSAGE_ERROR, _execPickerScreen, WSTR("App.executableWorker.addressError"), path, header.textOffset, executableEnd - 1, stackTop ); @@ -380,8 +379,8 @@ bool App::_executableWorker(void) { _fileError: _messageScreen.setMessage( - MESSAGE_ERROR, _mainMenuScreen, WSTR("App.executableWorker.fileError"), - path + MESSAGE_ERROR, _execPickerScreen, + WSTR("App.executableWorker.fileError"), path ); _workerStatus.setNextScreen(_messageScreen); return false; diff --git a/src/main/uicommon.cpp b/src/main/uicommon.cpp index c721cb5..e4ffcc3 100644 --- a/src/main/uicommon.cpp +++ b/src/main/uicommon.cpp @@ -520,6 +520,7 @@ void ListScreen::draw(Context &ctx, bool active) const { // Up/down arrow icons gpu::RectWH iconRect; + char arrow[2]{ 0, 0 }; iconRect.x = screenWidth - (ctx.font.metrics.lineHeight + LIST_BOX_PADDING); @@ -527,17 +528,15 @@ void ListScreen::draw(Context &ctx, bool active) const { iconRect.h = ctx.font.metrics.lineHeight; if (_activeItem) { + arrow[0] = CH_UP_ARROW; iconRect.y = LIST_BOX_PADDING; - ctx.font.draw( - ctx.gpuCtx, CH_UP_ARROW, iconRect, ctx.colors[COLOR_TEXT1] - ); + ctx.font.draw(ctx.gpuCtx, arrow, iconRect, ctx.colors[COLOR_TEXT1]); } if (_activeItem < (_listLength - 1)) { + arrow[0] = CH_DOWN_ARROW; iconRect.y = listHeight - (ctx.font.metrics.lineHeight + LIST_BOX_PADDING); - ctx.font.draw( - ctx.gpuCtx, CH_DOWN_ARROW, iconRect, ctx.colors[COLOR_TEXT1] - ); + ctx.font.draw(ctx.gpuCtx, arrow, iconRect, ctx.colors[COLOR_TEXT1]); } } } diff --git a/tools/buildResourceArchive.py b/tools/buildResourceArchive.py index b378f27..4388e13 100755 --- a/tools/buildResourceArchive.py +++ b/tools/buildResourceArchive.py @@ -215,6 +215,10 @@ TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = { b"DOWN_ARROW": b"\x81", b"LEFT_ARROW": b"\x82", b"RIGHT_ARROW": b"\x83", + b"UP_ARROW_ALT": b"\x84", + 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", @@ -223,10 +227,8 @@ TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = { b"DIR_ICON": b"\x95", b"PARENT_DIR_ICON": b"\x96", b"FILE_ICON": b"\x97", - b"EXE_FILE_ICON": b"\x98", - b"DUMP_FILE_ICON": b"\x99", - b"CHIP_ICON": b"\x9a", - b"CART_ICON": b"\x9b" + b"CHIP_ICON": b"\x98", + b"CART_ICON": b"\x99" } def hashString(string: str) -> int: