mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 19:52:05 +01:00
Split off file picker screen, fix executable launcher
This commit is contained in:
parent
d8aa1806b6
commit
ed4cc53631
@ -108,6 +108,20 @@
|
||||
"name": "View cartridge hexdump",
|
||||
"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": {
|
||||
"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.",
|
||||
@ -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.",
|
||||
"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."
|
||||
},
|
||||
"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"
|
||||
},
|
||||
|
||||
"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.",
|
||||
"FilePickerScreen": {
|
||||
"title": "{CART_ICON} Select file from hard drive",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
|
||||
"parentDir": "[Parent directory]",
|
||||
@ -258,7 +261,8 @@
|
||||
},
|
||||
"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)."
|
||||
"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": {
|
||||
"name": "Eject CD-ROM",
|
||||
|
@ -209,7 +209,7 @@ DeviceError Device::enumerate(void) {
|
||||
delayMicroseconds(5000);
|
||||
_write(CS1_DEVICE_CTRL, CS1_DEVICE_CTRL_IEN);
|
||||
delayMicroseconds(5000);
|
||||
_select(0);
|
||||
_select();
|
||||
|
||||
auto error = _waitForStatus(CS0_STATUS_BSY, 0, _RESET_STATUS_TIMEOUT);
|
||||
if (error)
|
||||
@ -333,12 +333,32 @@ DeviceError Device::_ideReadWrite(
|
||||
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) {
|
||||
if (!(flags & DEVICE_HAS_FLUSH))
|
||||
return NO_ERROR;
|
||||
//return UNSUPPORTED_OP;
|
||||
|
||||
_select(CS0_DEVICE_SEL_LBA);
|
||||
_select();
|
||||
|
||||
return _command(
|
||||
(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))
|
||||
return UNSUPPORTED_OP;
|
||||
|
||||
_select(0);
|
||||
_select();
|
||||
|
||||
_write(CS0_CYLINDER_L, (transferLength >> 0) & 0xff);
|
||||
_write(CS0_CYLINDER_H, (transferLength >> 8) & 0xff);
|
||||
|
@ -79,6 +79,7 @@ enum ATACommand : uint8_t {
|
||||
ATA_IDLE_IMMEDIATE = 0xe1,
|
||||
ATA_STANDBY = 0xe2,
|
||||
ATA_IDLE = 0xe3,
|
||||
ATA_CHECK_POWER_MODE = 0xe5,
|
||||
ATA_SLEEP = 0xe6,
|
||||
ATA_FLUSH_CACHE = 0xe7,
|
||||
ATA_FLUSH_CACHE_EXT = 0xea,
|
||||
@ -310,7 +311,7 @@ private:
|
||||
SYS573_IDE_CS1_BASE[reg] = value;
|
||||
}
|
||||
|
||||
inline void _select(uint8_t regFlags) {
|
||||
inline void _select(uint8_t regFlags = 0) {
|
||||
if (flags & DEVICE_SECONDARY)
|
||||
_write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_SECONDARY);
|
||||
else
|
||||
@ -347,6 +348,7 @@ public:
|
||||
}
|
||||
|
||||
DeviceError enumerate(void);
|
||||
DeviceError ideIdle(bool standby = false);
|
||||
DeviceError ideFlushCache(void);
|
||||
DeviceError atapiPacket(
|
||||
Packet &packet, size_t transferLength = ATAPI_SECTOR_SIZE
|
||||
|
@ -285,6 +285,7 @@ void ExecutableLoader::addArgument(const char *arg) {
|
||||
|
||||
[[noreturn]] void ExecutableLoader::run(void) {
|
||||
disableInterrupts();
|
||||
flushCache();
|
||||
|
||||
register int a0 __asm__("a0") = _argCount;
|
||||
register char **a1 __asm__("a1") = _argListPtr;
|
||||
|
@ -172,7 +172,7 @@ void Launcher::exit(void) {
|
||||
if (_fs.fs_type)
|
||||
f_unmount(_settings.drive);
|
||||
|
||||
uninstallExceptionHandler();
|
||||
//uninstallExceptionHandler();
|
||||
}
|
||||
|
||||
[[noreturn]] void Launcher::run(void) {
|
||||
@ -186,6 +186,10 @@ void Launcher::exit(void) {
|
||||
}
|
||||
|
||||
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();
|
||||
io::init();
|
||||
|
||||
@ -196,6 +200,7 @@ int main(int argc, const char **argv) {
|
||||
|
||||
IRQ_MASK = 1 << IRQ_VSYNC;
|
||||
enableInterrupts();
|
||||
#endif
|
||||
|
||||
Settings settings;
|
||||
Launcher launcher(settings);
|
||||
@ -210,9 +215,11 @@ int main(int argc, const char **argv) {
|
||||
|
||||
//util::logger.setupSyslog(settings.baudRate);
|
||||
|
||||
io::clearWatchdog();
|
||||
if (!launcher.openFile())
|
||||
return 1;
|
||||
|
||||
io::clearWatchdog();
|
||||
if (!launcher.readHeader(0)) {
|
||||
// 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.
|
||||
@ -220,9 +227,11 @@ int main(int argc, const char **argv) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
io::clearWatchdog();
|
||||
if (!launcher.readBody())
|
||||
return 3;
|
||||
|
||||
io::clearWatchdog();
|
||||
launcher.run();
|
||||
return 0;
|
||||
}
|
||||
|
@ -71,13 +71,14 @@ _driver(nullptr), _parser(nullptr), _identified(nullptr) {
|
||||
|
||||
App::~App(void) {
|
||||
_unloadCartData();
|
||||
//_resourceProvider.close();
|
||||
|
||||
if (_resourceFile)
|
||||
delete _resourceFile;
|
||||
|
||||
//_fileProvider.close();
|
||||
|
||||
delete[] _workerStack;
|
||||
|
||||
if (_resourceFile) {
|
||||
//_resourceFile->close();
|
||||
delete _resourceFile;
|
||||
}
|
||||
}
|
||||
|
||||
void App::_unloadCartData(void) {
|
||||
|
@ -98,13 +98,13 @@ class App {
|
||||
friend class WorkerStatusScreen;
|
||||
friend class MessageScreen;
|
||||
friend class ConfirmScreen;
|
||||
friend class FilePickerScreen;
|
||||
friend class WarningScreen;
|
||||
friend class ButtonMappingScreen;
|
||||
friend class MainMenuScreen;
|
||||
friend class SystemInfoScreen;
|
||||
friend class ResolutionScreen;
|
||||
friend class AboutScreen;
|
||||
friend class ExecPickerScreen;
|
||||
friend class CartInfoScreen;
|
||||
friend class UnlockKeyScreen;
|
||||
friend class KeyEntryScreen;
|
||||
@ -118,13 +118,13 @@ private:
|
||||
WorkerStatusScreen _workerStatusScreen;
|
||||
MessageScreen _messageScreen;
|
||||
ConfirmScreen _confirmScreen;
|
||||
FilePickerScreen _filePickerScreen;
|
||||
WarningScreen _warningScreen;
|
||||
ButtonMappingScreen _buttonMappingScreen;
|
||||
MainMenuScreen _mainMenuScreen;
|
||||
SystemInfoScreen _systemInfoScreen;
|
||||
ResolutionScreen _resolutionScreen;
|
||||
AboutScreen _aboutScreen;
|
||||
ExecPickerScreen _execPickerScreen;
|
||||
CartInfoScreen _cartInfoScreen;
|
||||
UnlockKeyScreen _unlockKeyScreen;
|
||||
KeyEntryScreen _keyEntryScreen;
|
||||
|
@ -177,18 +177,16 @@ void MainMenuScreen::about(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) {
|
||||
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);
|
||||
}
|
||||
APP->_filePickerScreen.loadRootAndShow(ctx);
|
||||
}
|
||||
|
||||
void MainMenuScreen::ejectCD(ui::Context &ctx) {
|
||||
|
@ -198,183 +198,6 @@ 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<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 */
|
||||
|
||||
struct Resolution {
|
||||
|
@ -17,30 +17,6 @@ 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 {
|
||||
|
@ -20,8 +20,20 @@ bool App::_startupWorker(void) {
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
auto &dev = ide::devices[i];
|
||||
|
||||
_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"));
|
||||
@ -283,7 +295,7 @@ static const LauncherEntry _LAUNCHERS[]{
|
||||
bool App::_executableWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
|
||||
|
||||
const char *path = _execPickerScreen.selectedPath;
|
||||
const char *path = _filePickerScreen.selectedPath;
|
||||
|
||||
util::ExecutableHeader header;
|
||||
uintptr_t executableEnd, stackTop;
|
||||
@ -372,16 +384,18 @@ bool App::_executableWorker(void) {
|
||||
// main() before starting the new executable.
|
||||
_unloadCartData();
|
||||
_resourceProvider.close();
|
||||
|
||||
if (_resourceFile)
|
||||
delete _resourceFile;
|
||||
|
||||
_fileProvider.close();
|
||||
|
||||
uninstallExceptionHandler();
|
||||
loader.run();
|
||||
}
|
||||
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, _execPickerScreen,
|
||||
MESSAGE_ERROR, _filePickerScreen,
|
||||
WSTR("App.executableWorker.addressError"), path, header.textOffset,
|
||||
executableEnd - 1, stackTop
|
||||
);
|
||||
@ -393,7 +407,7 @@ _fileError:
|
||||
|
||||
_fileOpenError:
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, _execPickerScreen,
|
||||
MESSAGE_ERROR, _filePickerScreen,
|
||||
WSTR("App.executableWorker.fileError"), path
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
|
@ -1,10 +1,13 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "common/defs.hpp"
|
||||
#include "common/file.hpp"
|
||||
#include "common/util.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/modals.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
#include "main/uicommon.hpp"
|
||||
|
||||
/* Modal screens */
|
||||
|
||||
@ -103,3 +106,209 @@ void ConfirmScreen::update(ui::Context &ctx) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/file.hpp"
|
||||
#include "common/util.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
#include "main/uicommon.hpp"
|
||||
|
||||
@ -47,3 +49,37 @@ public:
|
||||
void show(ui::Context &ctx, bool goBack = false);
|
||||
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);
|
||||
};
|
||||
|
@ -100,8 +100,8 @@ void setInterruptHandler(ArgFunction func, void *arg) {
|
||||
}
|
||||
|
||||
void flushCache(void) {
|
||||
//if (!_flushCache)
|
||||
//_flushCache = BIOS_API_TABLE[0x44];
|
||||
if (!_flushCache)
|
||||
_flushCache = BIOS_API_TABLE[0x44];
|
||||
|
||||
bool enable = disableInterrupts();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user