Implement executable file picker

This commit is contained in:
spicyjpeg 2024-01-03 14:13:02 +01:00
parent 2f1699bb3f
commit b8e96e1d7e
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
14 changed files with 291 additions and 59 deletions

View File

@ -204,6 +204,17 @@
"yes": "Yes, continue" "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": { "HexdumpScreen": {
"title": "{CART_ICON} Cartridge dump", "title": "{CART_ICON} Cartridge dump",
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back." "prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back."

View File

@ -105,6 +105,10 @@
"\u0081": { "x": 6, "y": 54, "width": 6, "height": 9 }, "\u0081": { "x": 6, "y": 54, "width": 6, "height": 9 },
"\u0082": { "x": 12, "y": 54, "width": 4, "height": 9 }, "\u0082": { "x": 12, "y": 54, "width": 4, "height": 9 },
"\u0083": { "x": 18, "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 }, "\u0090": { "x": 0, "y": 63, "width": 7, "height": 9, "icon": true },
"\u0091": { "x": 7, "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 }, "\u0095": { "x": 42, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0096": { "x": 51, "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 }, "\u0097": { "x": 60, "y": 63, "width": 9, "height": 10, "icon": true },
"\u0098": { "x": 69, "y": 63, "width": 9, "height": 10, "icon": true }, "\u0098": { "x": 69, "y": 63, "width": 12, "height": 10, "icon": true },
"\u0099": { "x": 78, "y": 63, "width": 9, "height": 10, "icon": true }, "\u0099": { "x": 81, "y": 63, "width": 14, "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 }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -13,19 +13,23 @@
#define EXTERNAL_DATA_DIR "cartdata" #define EXTERNAL_DATA_DIR "cartdata"
#define CH_UP_ARROW "\x80" enum Character : char {
#define CH_DOWN_ARROW "\x81" CH_UP_ARROW = '\x80',
#define CH_LEFT_ARROW "\x82" CH_DOWN_ARROW = '\x81',
#define CH_RIGHT_ARROW "\x83" CH_LEFT_ARROW = '\x82',
#define CH_LEFT_BUTTON "\x90" CH_RIGHT_ARROW = '\x83',
#define CH_RIGHT_BUTTON "\x91" CH_UP_ARROW_ALT = '\x84',
#define CH_START_BUTTON "\x92" CH_DOWN_ARROW_ALT = '\x85',
#define CH_CLOSED_LOCK "\x93" CH_LEFT_ARROW_ALT = '\x86',
#define CH_OPEN_LOCK "\x94" CH_RIGHT_ARROW_ALT = '\x87',
#define CH_DIR_ICON "\x95" CH_LEFT_BUTTON = '\x90',
#define CH_PARENT_DIR_ICON "\x96" CH_RIGHT_BUTTON = '\x91',
#define CH_FILE_ICON "\x97" CH_START_BUTTON = '\x92',
#define CH_EXE_FILE_ICON "\x98" CH_CLOSED_LOCK = '\x93',
#define CH_DUMP_FILE_ICON "\x99" CH_OPEN_LOCK = '\x94',
#define CH_CHIP_ICON "\x9a" CH_DIR_ICON = '\x95',
#define CH_CART_ICON "\x9b" CH_PARENT_DIR_ICON = '\x96',
CH_FILE_ICON = '\x97',
CH_CHIP_ICON = '\x98',
CH_CART_ICON = '\x99'
};

View File

@ -175,9 +175,7 @@ Directory::~Directory(void) {
close(); close();
} }
bool FATDirectory::getEntry( bool FATDirectory::getEntry(FileInfo &output) {
FileInfo &output, uint32_t attrMask, uint32_t attrValue
) {
FILINFO info; FILINFO info;
auto error = f_readdir(&_fd, &info); auto error = f_readdir(&_fd, &info);
@ -188,10 +186,7 @@ bool FATDirectory::getEntry(
if (!info.fname[0]) if (!info.fname[0])
return false; return false;
__builtin_strncpy( __builtin_strncpy(output.name, info.fname, sizeof(output.name));
output.name, info.fname,
util::min(sizeof(output.name), sizeof(info.fname))
);
output.length = info.fsize; output.length = info.fsize;
output.attributes = info.fattrib; output.attributes = info.fattrib;
@ -473,10 +468,7 @@ bool FATProvider::getFileInfo(FileInfo &output, const char *path) {
return false; return false;
} }
__builtin_strncpy( __builtin_strncpy(output.name, info.fname, sizeof(output.name));
output.name, info.fname,
util::min(sizeof(output.name), sizeof(info.fname))
);
output.length = info.fsize; output.length = info.fsize;
output.attributes = info.fattrib; output.attributes = info.fattrib;
@ -611,10 +603,14 @@ bool ZIPProvider::getFileInfo(FileInfo &output, const char *path) {
if (!info.m_is_supported) if (!info.m_is_supported)
return false; return false;
__builtin_strncpy( auto ptr = __builtin_strrchr(info.m_filename, '/');
output.name, info.m_filename,
util::min(sizeof(output.name), sizeof(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.length = info.m_uncomp_size;
output.attributes = READ_ONLY | ARCHIVE; output.attributes = READ_ONLY | ARCHIVE;

View File

@ -13,6 +13,7 @@ namespace file {
/* File classes */ /* File classes */
static constexpr size_t MAX_NAME_LENGTH = 64;
static constexpr size_t MAX_PATH_LENGTH = 256; static constexpr size_t MAX_PATH_LENGTH = 256;
// The first 4 of these map to the FS_* enum used by FatFs. // The first 4 of these map to the FS_* enum used by FatFs.
@ -46,7 +47,7 @@ enum FileAttributeFlag {
struct FileInfo { struct FileInfo {
public: public:
char name[MAX_PATH_LENGTH]; char name[MAX_NAME_LENGTH];
uint64_t length; uint64_t length;
uint32_t attributes; uint32_t attributes;
}; };
@ -102,9 +103,7 @@ class Directory {
public: public:
virtual ~Directory(void); virtual ~Directory(void);
virtual bool getEntry( virtual bool getEntry(FileInfo &output) { return false; }
FileInfo &output, uint32_t attrMask, uint32_t attrValue
) { return false; }
virtual void close(void) {} virtual void close(void) {}
}; };
@ -115,7 +114,7 @@ private:
DIR _fd; DIR _fd;
public: public:
bool getEntry(FileInfo &output, uint32_t attrMask, uint32_t attrValue); bool getEntry(FileInfo &output);
void close(void); void close(void);
}; };

View File

@ -89,12 +89,15 @@ public:
template<typename T> inline T *as(void) { template<typename T> inline T *as(void) {
return reinterpret_cast<T *>(ptr); return reinterpret_cast<T *>(ptr);
} }
template<typename T> inline const T *as(void) const {
return reinterpret_cast<const T *>(ptr);
}
inline void *allocate(size_t _length) { inline void *allocate(size_t _length) {
if (ptr) if (ptr)
free(ptr); delete[] as<uint8_t>();
ptr = new uint8_t[length]; ptr = _length ? (new uint8_t[_length]) : nullptr;
length = _length; length = _length;
return ptr; return ptr;

View File

@ -104,6 +104,7 @@ class App {
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;
@ -123,6 +124,7 @@ private:
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,8 +177,18 @@ void MainMenuScreen::about(ui::Context &ctx) {
} }
void MainMenuScreen::runExecutable(ui::Context &ctx) { void MainMenuScreen::runExecutable(ui::Context &ctx) {
APP->_setupWorker(&App::_executableWorker); int count = APP->_execPickerScreen.loadDirectory(ctx, "");
ctx.show(APP->_workerStatusScreen, false, true);
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) { void MainMenuScreen::ejectCD(ui::Context &ctx) {

View File

@ -1,10 +1,13 @@
#include <stdint.h> #include <stdint.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/ide.hpp" #include "common/ide.hpp"
#include "common/rom.hpp" #include "common/rom.hpp"
#include "common/util.hpp" #include "common/util.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/misc.hpp" #include "main/app/misc.hpp"
#include "main/app/modals.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "ps1/gpucmd.h" #include "ps1/gpucmd.h"
@ -195,6 +198,183 @@ 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 {

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "common/file.hpp"
#include "common/util.hpp" #include "common/util.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "main/uicommon.hpp" #include "main/uicommon.hpp"
@ -16,6 +17,30 @@ 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

@ -283,8 +283,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"));
// TODO: implement a file picker screen const char *path = _execPickerScreen.selectedPath;
const char *path = "psx.exe";
util::ExecutableHeader header; util::ExecutableHeader header;
uintptr_t executableEnd, stackTop; uintptr_t executableEnd, stackTop;
@ -371,7 +370,7 @@ bool App::_executableWorker(void) {
} }
_messageScreen.setMessage( _messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, MESSAGE_ERROR, _execPickerScreen,
WSTR("App.executableWorker.addressError"), path, header.textOffset, WSTR("App.executableWorker.addressError"), path, header.textOffset,
executableEnd - 1, stackTop executableEnd - 1, stackTop
); );
@ -380,8 +379,8 @@ bool App::_executableWorker(void) {
_fileError: _fileError:
_messageScreen.setMessage( _messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.executableWorker.fileError"), MESSAGE_ERROR, _execPickerScreen,
path WSTR("App.executableWorker.fileError"), path
); );
_workerStatus.setNextScreen(_messageScreen); _workerStatus.setNextScreen(_messageScreen);
return false; return false;

View File

@ -520,6 +520,7 @@ void ListScreen::draw(Context &ctx, bool active) const {
// Up/down arrow icons // Up/down arrow icons
gpu::RectWH iconRect; gpu::RectWH iconRect;
char arrow[2]{ 0, 0 };
iconRect.x = screenWidth - iconRect.x = screenWidth -
(ctx.font.metrics.lineHeight + LIST_BOX_PADDING); (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; iconRect.h = ctx.font.metrics.lineHeight;
if (_activeItem) { if (_activeItem) {
arrow[0] = CH_UP_ARROW;
iconRect.y = LIST_BOX_PADDING; iconRect.y = LIST_BOX_PADDING;
ctx.font.draw( ctx.font.draw(ctx.gpuCtx, arrow, iconRect, ctx.colors[COLOR_TEXT1]);
ctx.gpuCtx, CH_UP_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
);
} }
if (_activeItem < (_listLength - 1)) { if (_activeItem < (_listLength - 1)) {
arrow[0] = CH_DOWN_ARROW;
iconRect.y = listHeight - iconRect.y = listHeight -
(ctx.font.metrics.lineHeight + LIST_BOX_PADDING); (ctx.font.metrics.lineHeight + LIST_BOX_PADDING);
ctx.font.draw( ctx.font.draw(ctx.gpuCtx, arrow, iconRect, ctx.colors[COLOR_TEXT1]);
ctx.gpuCtx, CH_DOWN_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
);
} }
} }
} }

View File

@ -215,6 +215,10 @@ TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = {
b"DOWN_ARROW": b"\x81", b"DOWN_ARROW": b"\x81",
b"LEFT_ARROW": b"\x82", b"LEFT_ARROW": b"\x82",
b"RIGHT_ARROW": b"\x83", 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"LEFT_BUTTON": b"\x90",
b"RIGHT_BUTTON": b"\x91", b"RIGHT_BUTTON": b"\x91",
b"START_BUTTON": b"\x92", b"START_BUTTON": b"\x92",
@ -223,10 +227,8 @@ TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = {
b"DIR_ICON": b"\x95", b"DIR_ICON": b"\x95",
b"PARENT_DIR_ICON": b"\x96", b"PARENT_DIR_ICON": b"\x96",
b"FILE_ICON": b"\x97", b"FILE_ICON": b"\x97",
b"EXE_FILE_ICON": b"\x98", b"CHIP_ICON": b"\x98",
b"DUMP_FILE_ICON": b"\x99", b"CART_ICON": b"\x99"
b"CHIP_ICON": b"\x9a",
b"CART_ICON": b"\x9b"
} }
def hashString(string: str) -> int: def hashString(string: str) -> int: