Fix IDE reset, initial executable loader implementation

This commit is contained in:
spicyjpeg 2024-01-02 12:58:58 +01:00
parent 324fa36f22
commit 2f1699bb3f
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
18 changed files with 1025 additions and 813 deletions

View File

@ -83,11 +83,13 @@ addExecutable(
src/main/uicommon.cpp
src/main/zs01.cpp
src/main/app/app.cpp
src/main/app/appworkers.cpp
src/main/app/cartactions.cpp
src/main/app/cartunlock.cpp
src/main/app/cartworkers.cpp
src/main/app/main.cpp
src/main/app/misc.cpp
src/main/app/miscworkers.cpp
src/main/app/modals.cpp
src/vendor/ff.c
src/vendor/ffunicode.c
src/vendor/miniz.c
@ -136,7 +138,7 @@ function(addLauncher address stackTop)
target_compile_definitions(
launcher${address} PRIVATE
$<IF:$<CONFIG:Debug>,
ENABLE_LOGGING=1
#ENABLE_LOGGING=1
#ENABLE_FILE_WRITING=1
,
#ENABLE_LOGGING=1

View File

@ -39,7 +39,13 @@
"cartDumpWorker": {
"save": "Saving cartridge dump...\nDo not turn off the 573.",
"success": "A dump of the cartridge and all its identifiers has been saved as %s in the root of the drive. The dump can be decoded and viewed using the decodeDump.py script provided with this tool.",
"error": "An error occurred while saving the dump (%s). 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."
"error": "An error occurred while saving the dump. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary and formatted with a single FAT16, FAT32 or exFAT partition.\n\nFile: %s\nPress the Test button to view debug logs."
},
"executableWorker": {
"init": "Validating executable file...\nDo not turn off the 573.",
"load": "Loading executable file...\nDo not turn off the 573.",
"fileError": "The selected file is not a valid System 573 executable. Make sure the file has been built, extracted or copied properly.\n\nFile: %s",
"addressError": "The selected file cannot be loaded as it overlaps the memory region reserved for use by the executable launcher.\n\nFile:\t\t%s\nRegion:\t%08X-%08X\nStack top:\t%08X"
},
"qrCodeWorker": {
"compress": "Compressing cartridge dump...",
@ -53,8 +59,8 @@
"dumpPCMCIA1": "Dumping PCMCIA card in slot 1...\nDo not turn off the 573.",
"dumpPCMCIA2": "Dumping PCMCIA card in slot 2...\nDo not turn off the 573.",
"success": "All dumps have been saved to the %s directory in the root of the drive.",
"initError": "An error occurred while creating the dump directory (%s). 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.",
"dumpError": "An error occurred while saving one of the dumps (%s). Ensure the drive has at least 32 MB of free space (256 MB if both PCMCIA cards are inserted) and the filesystem is not damaged.\n\nPress the Test button to view debug logs."
"initError": "An error occurred while creating the dump directory. Turn off the system and make sure the drive is connected to the IDE bus properly, set as secondary and formatted with a single FAT16, FAT32 or exFAT partition.\n\nPath: %s\nPress the Test button to view debug logs.",
"dumpError": "An error occurred while saving one of the dumps. Ensure the drive has at least 32 MB of free space (256 MB if both PCMCIA cards are inserted) and the filesystem is not damaged.\n\nFile: %s\nPress the Test button to view debug logs."
},
"systemInfoWorker": {
"hashBIOS": "Calculating BIOS ROM checksum...",
@ -239,9 +245,9 @@
"name": "About this tool",
"prompt": "View information about this tool, including open source licenses."
},
"launchEXE": {
"name": "Launch executable from hard drive",
"prompt": "Load and run a System 573 executable file from the IDE hard drive or CF card connected as secondary drive (if any)."
"runExecutable": {
"name": "Run executable from hard drive",
"prompt": "Load and launch a System 573 executable file from the IDE hard drive or CF card connected as secondary drive (if any)."
},
"ejectCD": {
"name": "Eject CD-ROM",
@ -329,7 +335,7 @@
"commonInfo": " CRC32:\t\t%08X\n",
"kernelInfo": {
"sony": " Kernel type:\tSony PS1 kernel\n Kernel version:\t%s\n Kernel date:\t%04X-%02X-%02X\n",
"sony": " Kernel type:\tSony PlayStation kernel\n Kernel version:\t%s\n Kernel date:\t%04X-%02X-%02X\n",
"openbios": " Kernel type:\tOpenBIOS\n Kernel build ID:\n %s\n",
"unknown": " Kernel type:\tUnknown\n"
},

View File

@ -179,6 +179,10 @@ public:
_drive[0] = 0;
}
inline const char *getDriveString(void) {
return _drive;
}
bool init(const char *drive);
void close(void);

View File

@ -209,6 +209,7 @@ DeviceError Device::enumerate(void) {
delayMicroseconds(5000);
_write(CS1_DEVICE_CTRL, CS1_DEVICE_CTRL_IEN);
delayMicroseconds(5000);
_select(0);
auto error = _waitForStatus(CS0_STATUS_BSY, 0, _RESET_STATUS_TIMEOUT);
if (error)
@ -219,8 +220,6 @@ DeviceError Device::enumerate(void) {
// FIXME: some drives may not provide the signature immediately
IdentifyBlock block;
_select(0);
if ((_read(CS0_CYLINDER_L) == 0x14) && (_read(CS0_CYLINDER_H) == 0xeb)) {
flags |= DEVICE_ATAPI;
@ -269,7 +268,7 @@ DeviceError Device::enumerate(void) {
_copyString(revision, block.revision, sizeof(revision));
_copyString(serialNumber, block.serialNumber, sizeof(serialNumber));
LOG("%s=%s", (flags & DEVICE_SECONDARY) ? "sec" : "pri", model);
LOG("drive %d: %s", (flags / DEVICE_SECONDARY) & 1, model);
// Find out the fastest PIO transfer mode supported and enable it.
int mode = block.getHighestPIOMode();

View File

@ -87,6 +87,8 @@ void Logger::log(const char *format, ...) {
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
putchar('\n');
}
if (enable)
@ -257,12 +259,12 @@ bool ExecutableHeader::validateMagic(void) const {
ExecutableLoader::ExecutableLoader(
const ExecutableHeader &header, void *defaultStackTop
) : _header(header), _argCount(0) {
uintptr_t stackTop = header.stackOffset + header.stackLength;
auto stackTop = header.getStackPtr();
if (!stackTop)
stackTop = reinterpret_cast<uintptr_t>(defaultStackTop);
stackTop = defaultStackTop;
_argListPtr = reinterpret_cast<char **>(stackTop & ~7)
_argListPtr = reinterpret_cast<char **>(uintptr_t(stackTop) & ~7)
- MAX_EXECUTABLE_ARGS;
_currentStackPtr = reinterpret_cast<char *>(_argListPtr);
}

View File

@ -43,7 +43,7 @@ template<typename T> static inline T roundUpToMultiple(T value, T length) {
}
template<typename T, typename X> static inline void assertAligned(X *ptr) {
assert(!(reinterpret_cast<uintptr_t>(ptr) % alignof(T)));
//assert(!(reinterpret_cast<uintptr_t>(ptr) % alignof(T)));
}
template<typename T> static constexpr inline size_t countOf(T &array) {
@ -278,6 +278,16 @@ public:
uint32_t stackOffset, stackLength;
uint32_t _reserved[5];
inline void *getTextPtr(void) const {
return reinterpret_cast<void *>(textOffset);
}
inline void *getStackPtr(void) const {
return reinterpret_cast<void *>(stackOffset + stackLength);
}
inline void copyFrom(const void *source) {
__builtin_memcpy(this, source, sizeof(ExecutableHeader));
}
bool validateMagic(void) const;
};

View File

@ -26,17 +26,20 @@ bool Settings::parse(const char *arg) {
return false;
switch (util::hash(arg, '=')) {
#if 0
case "boot.rom"_h:
//LOG("boot.rom=%s", &arg[9]);
LOG("boot.rom=%s", &arg[9]);
return true;
case "boot.from"_h:
//LOG("boot.from=%s", &arg[10]);
LOG("boot.from=%s", &arg[10]);
return true;
case "console"_h:
// Disabled to avoid pulling in strtol.
baudRate = int(strtol(&arg[8], nullptr, 0));
return true;
#endif
case "launcher.drive"_h:
drive = &arg[15];
@ -130,26 +133,23 @@ bool Launcher::readHeader(void) {
return false;
}
auto ptr = reinterpret_cast<void *>(_header.textOffset);
if (ptr >= _textStart) {
if (_header.getTextPtr() >= _textStart) {
LOG("executable overlaps launcher");
return false;
}
LOG("ptr=0x%08x, length=0x%08x", ptr, _header.textLength);
LOG("ptr=0x%08x, length=0x%x", _header.textOffset, _header.textLength);
return true;
}
bool Launcher::readBody(void) {
auto ptr = reinterpret_cast<void *>(_header.textOffset);
size_t length;
if (f_lseek(&_file, util::EXECUTABLE_BODY_OFFSET)) {
LOG("seek to body failed, path=%s", _settings.path);
return false;
}
if (f_read(&_file, ptr, _header.textLength, &length)) {
if (f_read(&_file, _header.getTextPtr(), _header.textLength, &length)) {
LOG("body read failed, path=%s", _settings.path);
return false;
}
@ -203,7 +203,7 @@ int main(int argc, const char **argv) {
for (; argc > 0; argc--)
settings.parse(*(argv++));
util::logger.setupSyslog(settings.baudRate);
//util::logger.setupSyslog(settings.baudRate);
if (!launcher.openFile())
return 1;

View File

@ -8,6 +8,7 @@
#include "main/app/cartunlock.hpp"
#include "main/app/main.hpp"
#include "main/app/misc.hpp"
#include "main/app/modals.hpp"
#include "main/cart.hpp"
#include "main/cartdata.hpp"
#include "main/cartio.hpp"
@ -160,7 +161,6 @@ private:
void _setupInterrupts(void);
void _loadResources(void);
bool _startupWorker(void);
bool _cartDetectWorker(void);
bool _cartUnlockWorker(void);
bool _qrCodeWorker(void);
@ -168,8 +168,11 @@ private:
bool _cartWriteWorker(void);
bool _cartReflashWorker(void);
bool _cartEraseWorker(void);
bool _startupWorker(void);
bool _romDumpWorker(void);
bool _systemInfoWorker(void);
bool _executableWorker(void);
bool _atapiEjectWorker(void);
bool _rebootWorker(void);

View File

@ -4,52 +4,12 @@
#include <stdio.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/ide.hpp"
#include "common/io.hpp"
#include "common/rom.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/cart.hpp"
#include "main/cartdata.hpp"
#include "main/cartio.hpp"
#include "main/uibase.hpp"
#include "ps1/system.h"
bool App::_startupWorker(void) {
#ifdef NDEBUG
_workerStatus.setNextScreen(_warningScreen);
#else
// Skip the warning screen in debug builds.
_workerStatus.setNextScreen(_buttonMappingScreen);
#endif
for (int i = 0; i < 2; i++) {
_workerStatus.update(i, 4, WSTR("App.startupWorker.initIDE"));
ide::devices[i].enumerate();
}
_workerStatus.update(2, 4, WSTR("App.startupWorker.initFAT"));
// Attempt to mount the secondary drive first, then in case of failure try
// mounting the primary drive instead.
if (!_fileProvider.init("1:"))
_fileProvider.init("0:");
_workerStatus.update(3, 4, WSTR("App.startupWorker.loadResources"));
_resourceFile = _fileProvider.openFile(
EXTERNAL_DATA_DIR "/resource.zip", file::READ
);
if (_resourceFile) {
_resourceProvider.close();
if (_resourceProvider.init(_resourceFile))
_loadResources();
}
_ctx.sounds[ui::SOUND_STARTUP].play();
return true;
}
static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
nullptr,
@ -246,10 +206,20 @@ bool App::_cartDumpWorker(void) {
goto _error;
}
if (_identified && _parser->getCode(code) && _parser->getRegion(region))
snprintf(path, sizeof(path), EXTERNAL_DATA_DIR "/%s%s.573", code, region);
else
__builtin_strcpy(path, EXTERNAL_DATA_DIR "/unknown.573");
if (_identified && _parser->getCode(code) && _parser->getRegion(region)) {
snprintf(
path, sizeof(path), EXTERNAL_DATA_DIR "/%s%s.573", code, region
);
} else {
int index = 0;
do {
index++;
snprintf(
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%d.573", index
);
} while (_fileProvider.getFileInfo(info, path));
}
LOG("saving %s, length=%d", path, length);
@ -392,265 +362,3 @@ bool App::_cartEraseWorker(void) {
return _cartUnlockWorker();
}
struct DumpEntry {
public:
util::Hash dumpPrompt, hashPrompt;
const char *path;
const rom::Region &region;
size_t hashOffset;
};
static const DumpEntry _DUMP_ENTRIES[]{
{
.dumpPrompt = "App.romDumpWorker.dumpBIOS"_h,
.hashPrompt = "App.systemInfoWorker.hashBIOS"_h,
.path = "%s/bios.bin",
.region = rom::bios,
.hashOffset = offsetof(SystemInfo, biosCRC)
}, {
.dumpPrompt = "App.romDumpWorker.dumpRTC"_h,
.hashPrompt = "App.systemInfoWorker.hashRTC"_h,
.path = "%s/rtc.bin",
.region = rom::rtc,
.hashOffset = offsetof(SystemInfo, rtcCRC)
}, {
.dumpPrompt = "App.romDumpWorker.dumpFlash"_h,
.hashPrompt = "App.systemInfoWorker.hashFlash"_h,
.path = "%s/flash.bin",
.region = rom::flash,
.hashOffset = offsetof(SystemInfo, flash.crc)
}, {
.dumpPrompt = "App.romDumpWorker.dumpPCMCIA1"_h,
.hashPrompt = "App.systemInfoWorker.hashPCMCIA1"_h,
.path = "%s/pcmcia1.bin",
.region = rom::pcmcia[0],
.hashOffset = offsetof(SystemInfo, pcmcia[0].crc)
}, {
.dumpPrompt = "App.romDumpWorker.dumpPCMCIA2"_h,
.hashPrompt = "App.systemInfoWorker.hashPCMCIA2"_h,
.path = "%s/pcmcia2.bin",
.region = rom::pcmcia[1],
.hashOffset = offsetof(SystemInfo, pcmcia[1].crc)
}
};
static constexpr size_t _DUMP_CHUNK_LENGTH = 0x80000;
static constexpr size_t _DUMP_CHUNKS_PER_CRC = 32; // Save CRC32 every 16 MB
bool App::_romDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
// Store all dumps in a subdirectory named "dumpN" within the main data
// folder.
int index = 0;
char dirPath[32], filePath[32];
file::FileInfo info;
if (!_fileProvider.getFileInfo(info, EXTERNAL_DATA_DIR)) {
if (!_fileProvider.createDirectory(EXTERNAL_DATA_DIR))
goto _initError;
}
do {
index++;
snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%d", index);
} while (_fileProvider.getFileInfo(info, dirPath));
LOG("saving dumps to %s", dirPath);
if (!_fileProvider.createDirectory(dirPath))
goto _initError;
for (auto &entry : _DUMP_ENTRIES) {
if (!entry.region.isPresent())
continue;
size_t chunkLength, numChunks;
if (entry.region.regionLength < _DUMP_CHUNK_LENGTH) {
chunkLength = entry.region.regionLength;
numChunks = 1;
} else {
chunkLength = _DUMP_CHUNK_LENGTH;
numChunks = entry.region.regionLength / _DUMP_CHUNK_LENGTH;
}
snprintf(filePath, sizeof(filePath), entry.path, dirPath);
auto _file = _fileProvider.openFile(
filePath, file::WRITE | file::ALLOW_CREATE
);
if (!_file)
goto _writeError;
auto buffer = new uint8_t[chunkLength];
uint32_t offset = 0;
//assert(buffer);
for (size_t i = 0; i < numChunks; i++) {
_workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt));
entry.region.read(buffer, offset, chunkLength);
if (_file->write(buffer, chunkLength) < chunkLength) {
_file->close();
delete _file;
delete[] buffer;
goto _writeError;
}
offset += chunkLength;
}
_file->close();
delete _file;
delete[] buffer;
LOG("%s saved", filePath);
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _mainMenuScreen, WSTR("App.romDumpWorker.success"),
dirPath
);
_workerStatus.setNextScreen(_messageScreen);
return true;
_initError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.initError"),
dirPath
);
_workerStatus.setNextScreen(_messageScreen);
return false;
_writeError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.dumpError"),
filePath
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
bool App::_systemInfoWorker(void) {
// This is necessary to ensure the digital I/O ID is read at least once.
if (!_driver)
_cartDetectWorker();
_workerStatus.setNextScreen(_systemInfoScreen);
_systemInfo.clearFlags();
for (auto &entry : _DUMP_ENTRIES) {
if (!entry.region.isPresent())
continue;
size_t chunkLength, numChunks;
if (entry.region.regionLength < _DUMP_CHUNK_LENGTH) {
chunkLength = entry.region.regionLength;
numChunks = 1;
} else {
chunkLength = _DUMP_CHUNK_LENGTH;
numChunks = entry.region.regionLength / _DUMP_CHUNK_LENGTH;
}
uint32_t offset = 0;
uint32_t crc = 0;
auto crcPtr = reinterpret_cast<uint32_t *>(
reinterpret_cast<uintptr_t>(&_systemInfo) + entry.hashOffset
);
// Flash cards can be 16, 32 or 64 MB, so copies of the current CRC are
// saved after the first 16, then 32, 48 and finally 64 MB are read.
for (size_t i = 0; i < numChunks; i += _DUMP_CHUNKS_PER_CRC) {
size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks);
for (size_t j = i; j < end; j++) {
_workerStatus.update(j, numChunks, WSTRH(entry.hashPrompt));
crc = entry.region.zipCRC32(offset, chunkLength, crc);
offset += chunkLength;
}
*(crcPtr++) = crc;
}
}
_systemInfo.flags = SYSTEM_INFO_VALID;
_systemInfo.shell = rom::getShellInfo();
if (io::isRTCBatteryLow())
_systemInfo.flags |= SYSTEM_INFO_RTC_BATTERY_LOW;
_systemInfo.flash.jedecID = rom::flash.getJEDECID();
_systemInfo.flash.flags = FLASH_REGION_INFO_PRESENT;
if (rom::flash.hasBootExecutable())
_systemInfo.flash.flags |= FLASH_REGION_INFO_BOOTABLE;
for (int i = 0; i < 2; i++) {
auto &region = rom::pcmcia[i];
auto &card = _systemInfo.pcmcia[i];
if (region.isPresent()) {
card.jedecID = region.getJEDECID();
card.flags = FLASH_REGION_INFO_PRESENT;
if (region.hasBootExecutable())
card.flags |= FLASH_REGION_INFO_BOOTABLE;
}
}
return true;
}
bool App::_atapiEjectWorker(void) {
_workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
if (!(ide::devices[0].flags & ide::DEVICE_ATAPI)) {
LOG("primary drive is not ATAPI");
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.atapiEjectWorker.atapiError")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
ide::Packet packet;
packet.setStartStopUnit(ide::START_STOP_MODE_OPEN_TRAY);
auto error = ide::devices[0].atapiPacket(packet);
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.atapiEjectWorker.ejectError"), ide::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _mainMenuScreen, WSTR("App.atapiEjectWorker.success")
);
_workerStatus.setNextScreen(_messageScreen);
return true;
}
bool App::_rebootWorker(void) {
_workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
_workerStatus.setStatus(WORKER_REBOOT);
// Fall back to a soft reboot if the watchdog fails to reset the system.
delayMicroseconds(2000000);
softReset();
return true;
}

View File

@ -1,12 +1,8 @@
#include <stdint.h>
#include "common/ide.hpp"
#include "common/rom.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/app/main.hpp"
#include "main/uibase.hpp"
#include "ps1/gpucmd.h"
/* Main menu screens */
@ -119,12 +115,10 @@ static const MenuEntry _MENU_ENTRIES[]{
.prompt = "MainMenuScreen.about.prompt"_h,
.target = &MainMenuScreen::about
}, {
#if 0
.name = "MainMenuScreen.launchEXE.name"_h,
.prompt = "MainMenuScreen.launchEXE.prompt"_h,
.target = &MainMenuScreen::launchEXE
.name = "MainMenuScreen.runExecutable.name"_h,
.prompt = "MainMenuScreen.runExecutable.prompt"_h,
.target = &MainMenuScreen::runExecutable
}, {
#endif
.name = "MainMenuScreen.ejectCD.name"_h,
.prompt = "MainMenuScreen.ejectCD.prompt"_h,
.target = &MainMenuScreen::ejectCD
@ -182,8 +176,9 @@ void MainMenuScreen::about(ui::Context &ctx) {
ctx.show(APP->_aboutScreen, false, true);
}
void MainMenuScreen::launchEXE(ui::Context &ctx) {
//ctx.show(APP->_aboutScreen, false, true);
void MainMenuScreen::runExecutable(ui::Context &ctx) {
APP->_setupWorker(&App::_executableWorker);
ctx.show(APP->_workerStatusScreen, false, true);
}
void MainMenuScreen::ejectCD(ui::Context &ctx) {
@ -215,316 +210,3 @@ void MainMenuScreen::update(ui::Context &ctx) {
if (ctx.buttons.pressed(ui::BTN_START))
(this->*action.target)(ctx);
}
static const util::Hash _SYSTEM_INFO_IDE_HEADERS[]{
"SystemInfoScreen.ide.header.primary"_h,
"SystemInfoScreen.ide.header.secondary"_h
};
static const char *const _FILE_SYSTEM_TYPES[]{
nullptr,
"FAT12",
"FAT16",
"FAT32",
"exFAT"
};
#define _PRINT(...) (ptr += snprintf(ptr, end - ptr, __VA_ARGS__))
#define _PRINTLN() (*(ptr++) = '\n')
void SystemInfoScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("SystemInfoScreen.title");
_body = _bodyText;
_prompt = STR("SystemInfoScreen.prompt");
char id1[32], id2[32];
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
// Digital I/O board
auto &dump = APP->_dump;
_PRINT(STR("SystemInfoScreen.digitalIO.header"));
if (dump.flags & cart::DUMP_SYSTEM_ID_OK) {
dump.systemID.toString(id1);
dump.systemID.toSerialNumber(id2);
_PRINT(STR("SystemInfoScreen.digitalIO.info"), id1, id2);
} else if (dump.flags & cart::DUMP_HAS_SYSTEM_ID) {
_PRINT(STR("SystemInfoScreen.digitalIO.error"));
} else {
_PRINT(STR("SystemInfoScreen.digitalIO.noBoard"));
}
_PRINTLN();
// IDE drives
for (int i = 0; i < 2; i++) {
auto &dev = ide::devices[i];
_PRINT(STRH(_SYSTEM_INFO_IDE_HEADERS[i]));
if (dev.flags & ide::DEVICE_READY) {
_PRINT(
STR("SystemInfoScreen.ide.commonInfo"), dev.model, dev.revision,
dev.serialNumber
);
if (dev.flags & ide::DEVICE_ATAPI) {
_PRINT(
STR("SystemInfoScreen.ide.atapiInfo"),
(dev.flags & ide::DEVICE_HAS_PACKET16) ? 16 : 12
);
} else {
_PRINT(
STR("SystemInfoScreen.ide.ataInfo"),
uint64_t(dev.capacity / (0x100000 / ide::ATA_SECTOR_SIZE)),
(dev.flags & ide::DEVICE_HAS_LBA48) ? 48 : 28
);
if (dev.flags & ide::DEVICE_HAS_TRIM)
_PRINT(STR("SystemInfoScreen.ide.hasTrim"));
if (dev.flags & ide::DEVICE_HAS_FLUSH)
_PRINT(STR("SystemInfoScreen.ide.hasFlush"));
}
} else {
_PRINT(STR("SystemInfoScreen.ide.error"));
}
_PRINTLN();
}
// FAT file system
auto &fs = APP->_fileProvider;
auto fsType = fs.getFileSystemType();
_PRINT(STR("SystemInfoScreen.fat.header"));
if (fsType) {
char label[32];
fs.getVolumeLabel(label, sizeof(label));
_PRINT(
STR("SystemInfoScreen.fat.info"), _FILE_SYSTEM_TYPES[fsType], label,
fs.getSerialNumber(), fs.getCapacity() / 0x100000,
fs.getFreeSpace() / 0x100000
);
} else {
_PRINT(STR("SystemInfoScreen.fat.error"));
}
_PRINTLN();
// BIOS ROM
auto &info = APP->_systemInfo;
_PRINT(STR("SystemInfoScreen.bios.header"));
_PRINT(STR("SystemInfoScreen.bios.commonInfo"), info.biosCRC);
if (rom::sonyKernelHeader.validateMagic()) {
_PRINT(
STR("SystemInfoScreen.bios.kernelInfo.sony"),
rom::sonyKernelHeader.version, rom::sonyKernelHeader.year,
rom::sonyKernelHeader.month, rom::sonyKernelHeader.day
);
} else if (rom::openBIOSHeader.validateMagic()) {
char buildID[64];
rom::openBIOSHeader.getBuildID(buildID);
_PRINT(STR("SystemInfoScreen.bios.kernelInfo.openbios"), buildID);
} else {
_PRINT(STR("SystemInfoScreen.bios.kernelInfo.unknown"));
}
if (info.shell)
_PRINT(
STR("SystemInfoScreen.bios.shellInfo.konami"), info.shell->name,
info.shell->bootFileName
);
else
_PRINT(STR("SystemInfoScreen.bios.shellInfo.unknown"));
_PRINTLN();
// RTC RAM
_PRINT(STR("SystemInfoScreen.rtc.header"));
_PRINT(STR("SystemInfoScreen.rtc.info"), info.rtcCRC);
if (info.flags & SYSTEM_INFO_RTC_BATTERY_LOW)
_PRINT(STR("SystemInfoScreen.rtc.batteryLow"));
_PRINTLN();
// Flash
_PRINT(STR("SystemInfoScreen.flash.header"));
_PRINT(
STR("SystemInfoScreen.flash.info"), info.flash.jedecID & 0xff,
info.flash.jedecID >> 8, info.flash.crc[0]
);
if (info.flash.flags & FLASH_REGION_INFO_BOOTABLE)
_PRINT(STR("SystemInfoScreen.flash.bootable"));
_PRINTLN();
// PCMCIA cards
for (int i = 0; i < 2; i++) {
auto card = info.pcmcia[i];
_PRINT(STR("SystemInfoScreen.pcmcia.header"), i + 1);
if (card.flags & FLASH_REGION_INFO_PRESENT) {
_PRINT(
STR("SystemInfoScreen.pcmcia.info"), card.jedecID & 0xff,
card.jedecID >> 8, card.crc[0], card.crc[1], card.crc[3]
);
if (card.flags & FLASH_REGION_INFO_BOOTABLE)
_PRINT(STR("SystemInfoScreen.pcmcia.bootable"));
} else {
_PRINT(STR("SystemInfoScreen.pcmcia.noCard"));
}
_PRINTLN();
}
*(--ptr) = 0;
LOG("remaining=%d", end - ptr);
TextScreen::show(ctx, goBack);
}
void SystemInfoScreen::update(ui::Context &ctx) {
TextScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(APP->_mainMenuScreen, true, true);
}
struct Resolution {
public:
util::Hash name;
int width, height;
bool forceInterlace;
};
static const Resolution _RESOLUTIONS[]{
{
.name = "ResolutionScreen.320x240p"_h,
.width = 320,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.320x240i"_h,
.width = 320,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.368x240p"_h,
.width = 368,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.368x240i"_h,
.width = 368,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.512x240p"_h,
.width = 512,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.512x240i"_h,
.width = 512,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.640x240p"_h,
.width = 640,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.640x240i"_h,
.width = 640,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.640x480i"_h,
.width = 640,
.height = 480,
.forceInterlace = true
}
};
const char *ResolutionScreen::_getItemName(ui::Context &ctx, int index) const {
return STRH(_RESOLUTIONS[index].name);
}
void ResolutionScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("ResolutionScreen.title");
_prompt = STR("ResolutionScreen.prompt");
_itemPrompt = STR("ResolutionScreen.itemPrompt");
_listLength = util::countOf(_RESOLUTIONS);
ListScreen::show(ctx, goBack);
}
void ResolutionScreen::update(ui::Context &ctx) {
auto &res = _RESOLUTIONS[_activeItem];
ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (!ctx.buttons.held(ui::BTN_LEFT) && !ctx.buttons.held(ui::BTN_RIGHT))
ctx.gpuCtx.setResolution(
GP1_MODE_NTSC, res.width, res.height, res.forceInterlace
);
ctx.show(APP->_mainMenuScreen, true, true);
}
}
void AboutScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("AboutScreen.title");
_prompt = STR("AboutScreen.prompt");
APP->_resourceProvider.loadData(_text, "assets/about.txt");
auto ptr = reinterpret_cast<char *>(_text.ptr);
_body = ptr;
// Replace single newlines with spaces to reflow the text, unless the line
// preceding the newline ends with a space. The last character is also cut
// off and replaced with a null terminator.
for (size_t i = _text.length - 1; i; i--, ptr++) {
if (*ptr != '\n')
continue;
if (__builtin_isspace(ptr[-1]))
continue;
if (ptr[1] == '\n')
i--, ptr++;
else
*ptr = ' ';
}
*ptr = 0;
TextScreen::show(ctx, goBack);
}
void AboutScreen::hide(ui::Context &ctx, bool goBack) {
_body = nullptr;
_text.destroy();
TextScreen::hide(ctx, goBack);
}
void AboutScreen::update(ui::Context &ctx) {
TextScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(APP->_mainMenuScreen, true, true);
}

View File

@ -1,7 +1,6 @@
#pragma once
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
@ -37,38 +36,10 @@ public:
void systemInfo(ui::Context &ctx);
void setResolution(ui::Context &ctx);
void about(ui::Context &ctx);
void launchEXE(ui::Context &ctx);
void runExecutable(ui::Context &ctx);
void ejectCD(ui::Context &ctx);
void reboot(ui::Context &ctx);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class SystemInfoScreen : public ui::TextScreen {
private:
char _bodyText[2048];
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class ResolutionScreen : public ui::ListScreen {
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class AboutScreen : public ui::TextScreen {
private:
util::Data _text;
public:
void show(ui::Context &ctx, bool goBack = false);
void hide(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};

View File

@ -1,105 +1,326 @@
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include "common/ide.hpp"
#include "common/rom.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/app/misc.hpp"
#include "main/uibase.hpp"
#include "ps1/gpucmd.h"
/* Common screens */
/* System information screen */
void WorkerStatusScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("WorkerStatusScreen.title");
ProgressScreen::show(ctx, goBack);
}
void WorkerStatusScreen::update(ui::Context &ctx) {
auto &worker = APP->_workerStatus;
auto nextScreen = worker.nextScreen;
if ((worker.status == WORKER_NEXT) || (worker.status == WORKER_NEXT_BACK)) {
worker.reset();
ctx.show(*nextScreen, worker.status == WORKER_NEXT_BACK);
LOG("worker finished, next=0x%08x", nextScreen);
return;
}
_setProgress(ctx, worker.progress, worker.progressTotal);
_body = worker.message;
}
static const util::Hash _MESSAGE_TITLES[]{
"MessageScreen.title.success"_h,
"MessageScreen.title.error"_h
static const util::Hash _SYSTEM_INFO_IDE_HEADERS[]{
"SystemInfoScreen.ide.header.primary"_h,
"SystemInfoScreen.ide.header.secondary"_h
};
void MessageScreen::setMessage(
MessageType type, ui::Screen &prev, const char *format, ...
) {
_type = type;
_prevScreen = &prev;
static const char *const _FILE_SYSTEM_TYPES[]{
nullptr,
"FAT12",
"FAT16",
"FAT32",
"exFAT"
};
va_list ap;
#define _PRINT(...) (ptr += snprintf(ptr, end - ptr, __VA_ARGS__))
#define _PRINTLN() (*(ptr++) = '\n')
va_start(ap, format);
vsnprintf(_bodyText, sizeof(_bodyText), format, ap);
va_end(ap);
void SystemInfoScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("SystemInfoScreen.title");
_body = _bodyText;
_prompt = STR("SystemInfoScreen.prompt");
char id1[32], id2[32];
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
// Digital I/O board
auto &dump = APP->_dump;
_PRINT(STR("SystemInfoScreen.digitalIO.header"));
if (dump.flags & cart::DUMP_SYSTEM_ID_OK) {
dump.systemID.toString(id1);
dump.systemID.toSerialNumber(id2);
_PRINT(STR("SystemInfoScreen.digitalIO.info"), id1, id2);
} else if (dump.flags & cart::DUMP_HAS_SYSTEM_ID) {
_PRINT(STR("SystemInfoScreen.digitalIO.error"));
} else {
_PRINT(STR("SystemInfoScreen.digitalIO.noBoard"));
}
_PRINTLN();
// IDE drives
for (int i = 0; i < 2; i++) {
auto &dev = ide::devices[i];
_PRINT(STRH(_SYSTEM_INFO_IDE_HEADERS[i]));
if (dev.flags & ide::DEVICE_READY) {
_PRINT(
STR("SystemInfoScreen.ide.commonInfo"), dev.model, dev.revision,
dev.serialNumber
);
if (dev.flags & ide::DEVICE_ATAPI) {
_PRINT(
STR("SystemInfoScreen.ide.atapiInfo"),
(dev.flags & ide::DEVICE_HAS_PACKET16) ? 16 : 12
);
} else {
_PRINT(
STR("SystemInfoScreen.ide.ataInfo"),
uint64_t(dev.capacity / (0x100000 / ide::ATA_SECTOR_SIZE)),
(dev.flags & ide::DEVICE_HAS_LBA48) ? 48 : 28
);
if (dev.flags & ide::DEVICE_HAS_TRIM)
_PRINT(STR("SystemInfoScreen.ide.hasTrim"));
if (dev.flags & ide::DEVICE_HAS_FLUSH)
_PRINT(STR("SystemInfoScreen.ide.hasFlush"));
}
} else {
_PRINT(STR("SystemInfoScreen.ide.error"));
}
_PRINTLN();
}
// FAT file system
auto &fs = APP->_fileProvider;
auto fsType = fs.getFileSystemType();
_PRINT(STR("SystemInfoScreen.fat.header"));
if (fsType) {
char label[32];
fs.getVolumeLabel(label, sizeof(label));
_PRINT(
STR("SystemInfoScreen.fat.info"), _FILE_SYSTEM_TYPES[fsType], label,
fs.getSerialNumber(), fs.getCapacity() / 0x100000,
fs.getFreeSpace() / 0x100000
);
} else {
_PRINT(STR("SystemInfoScreen.fat.error"));
}
_PRINTLN();
// BIOS ROM
auto &info = APP->_systemInfo;
_PRINT(STR("SystemInfoScreen.bios.header"));
_PRINT(STR("SystemInfoScreen.bios.commonInfo"), info.biosCRC);
if (rom::sonyKernelHeader.validateMagic()) {
_PRINT(
STR("SystemInfoScreen.bios.kernelInfo.sony"),
rom::sonyKernelHeader.version, rom::sonyKernelHeader.year,
rom::sonyKernelHeader.month, rom::sonyKernelHeader.day
);
} else if (rom::openBIOSHeader.validateMagic()) {
char buildID[64];
rom::openBIOSHeader.getBuildID(buildID);
_PRINT(STR("SystemInfoScreen.bios.kernelInfo.openbios"), buildID);
} else {
_PRINT(STR("SystemInfoScreen.bios.kernelInfo.unknown"));
}
if (info.shell)
_PRINT(
STR("SystemInfoScreen.bios.shellInfo.konami"), info.shell->name,
info.shell->bootFileName
);
else
_PRINT(STR("SystemInfoScreen.bios.shellInfo.unknown"));
_PRINTLN();
// RTC RAM
_PRINT(STR("SystemInfoScreen.rtc.header"));
_PRINT(STR("SystemInfoScreen.rtc.info"), info.rtcCRC);
if (info.flags & SYSTEM_INFO_RTC_BATTERY_LOW)
_PRINT(STR("SystemInfoScreen.rtc.batteryLow"));
_PRINTLN();
// Flash
_PRINT(STR("SystemInfoScreen.flash.header"));
_PRINT(
STR("SystemInfoScreen.flash.info"), info.flash.jedecID & 0xff,
info.flash.jedecID >> 8, info.flash.crc[0]
);
if (info.flash.flags & FLASH_REGION_INFO_BOOTABLE)
_PRINT(STR("SystemInfoScreen.flash.bootable"));
_PRINTLN();
// PCMCIA cards
for (int i = 0; i < 2; i++) {
auto card = info.pcmcia[i];
_PRINT(STR("SystemInfoScreen.pcmcia.header"), i + 1);
if (card.flags & FLASH_REGION_INFO_PRESENT) {
_PRINT(
STR("SystemInfoScreen.pcmcia.info"), card.jedecID & 0xff,
card.jedecID >> 8, card.crc[0], card.crc[1], card.crc[3]
);
if (card.flags & FLASH_REGION_INFO_BOOTABLE)
_PRINT(STR("SystemInfoScreen.pcmcia.bootable"));
} else {
_PRINT(STR("SystemInfoScreen.pcmcia.noCard"));
}
_PRINTLN();
}
*(--ptr) = 0;
LOG("remaining=%d", end - ptr);
TextScreen::show(ctx, goBack);
}
void MessageScreen::show(ui::Context &ctx, bool goBack) {
_title = STRH(_MESSAGE_TITLES[_type]);
_body = _bodyText;
_buttons[0] = STR("MessageScreen.ok");
_numButtons = 1;
_locked = _prevScreen ? false : true;
MessageBoxScreen::show(ctx, goBack);
ctx.sounds[ui::SOUND_ALERT].play();
}
void MessageScreen::update(ui::Context &ctx) {
MessageBoxScreen::update(ctx);
void SystemInfoScreen::update(ui::Context &ctx) {
TextScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(*_prevScreen, true, true);
ctx.show(APP->_mainMenuScreen, true, true);
}
void ConfirmScreen::setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx), const char *format,
...
) {
_prevScreen = &prev;
_callback = callback;
/* Misc. screens */
va_list ap;
struct Resolution {
public:
util::Hash name;
int width, height;
bool forceInterlace;
};
va_start(ap, format);
vsnprintf(_bodyText, sizeof(_bodyText), format, ap);
va_end(ap);
static const Resolution _RESOLUTIONS[]{
{
.name = "ResolutionScreen.320x240p"_h,
.width = 320,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.320x240i"_h,
.width = 320,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.368x240p"_h,
.width = 368,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.368x240i"_h,
.width = 368,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.512x240p"_h,
.width = 512,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.512x240i"_h,
.width = 512,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.640x240p"_h,
.width = 640,
.height = 240,
.forceInterlace = false
}, {
.name = "ResolutionScreen.640x240i"_h,
.width = 640,
.height = 240,
.forceInterlace = true
}, {
.name = "ResolutionScreen.640x480i"_h,
.width = 640,
.height = 480,
.forceInterlace = true
}
};
const char *ResolutionScreen::_getItemName(ui::Context &ctx, int index) const {
return STRH(_RESOLUTIONS[index].name);
}
void ConfirmScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("ConfirmScreen.title");
_body = _bodyText;
_buttons[0] = STR("ConfirmScreen.no");
_buttons[1] = STR("ConfirmScreen.yes");
void ResolutionScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("ResolutionScreen.title");
_prompt = STR("ResolutionScreen.prompt");
_itemPrompt = STR("ResolutionScreen.itemPrompt");
_numButtons = 2;
_listLength = util::countOf(_RESOLUTIONS);
MessageBoxScreen::show(ctx, goBack);
//ctx.sounds[ui::SOUND_ALERT].play();
ListScreen::show(ctx, goBack);
}
void ConfirmScreen::update(ui::Context &ctx) {
MessageBoxScreen::update(ctx);
void ResolutionScreen::update(ui::Context &ctx) {
auto &res = _RESOLUTIONS[_activeItem];
ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (_activeButton)
_callback(ctx);
else
ctx.show(*_prevScreen, true, true);
if (!ctx.buttons.held(ui::BTN_LEFT) && !ctx.buttons.held(ui::BTN_RIGHT))
ctx.gpuCtx.setResolution(
GP1_MODE_NTSC, res.width, res.height, res.forceInterlace
);
ctx.show(APP->_mainMenuScreen, true, true);
}
}
void AboutScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("AboutScreen.title");
_prompt = STR("AboutScreen.prompt");
APP->_resourceProvider.loadData(_text, "assets/about.txt");
auto ptr = reinterpret_cast<char *>(_text.ptr);
_body = ptr;
// Replace single newlines with spaces to reflow the text, unless the line
// preceding the newline ends with a space. The last character is also cut
// off and replaced with a null terminator.
for (size_t i = _text.length - 1; i; i--, ptr++) {
if (*ptr != '\n')
continue;
if (__builtin_isspace(ptr[-1]))
continue;
if (ptr[1] == '\n')
i--, ptr++;
else
*ptr = ' ';
}
*ptr = 0;
TextScreen::show(ctx, goBack);
}
void AboutScreen::hide(ui::Context &ctx, bool goBack) {
_body = nullptr;
_text.destroy();
TextScreen::hide(ctx, goBack);
}
void AboutScreen::update(ui::Context &ctx) {
TextScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(APP->_mainMenuScreen, true, true);
}

View File

@ -1,49 +1,38 @@
#pragma once
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
/* Common screens */
/* System information screen */
class WorkerStatusScreen : public ui::ProgressScreen {
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
enum MessageType {
MESSAGE_SUCCESS = 0,
MESSAGE_ERROR = 1
};
class MessageScreen : public ui::MessageBoxScreen {
class SystemInfoScreen : public ui::TextScreen {
private:
MessageType _type;
char _bodyText[512];
ui::Screen *_prevScreen;
char _bodyText[2048];
public:
void setMessage(
MessageType type, ui::Screen &prev, const char *format, ...
);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class ConfirmScreen : public ui::MessageBoxScreen {
/* Misc. screens */
class ResolutionScreen : public ui::ListScreen {
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class AboutScreen : public ui::TextScreen {
private:
char _bodyText[512];
ui::Screen *_prevScreen;
void (*_callback)(ui::Context &ctx);
util::Data _text;
public:
void setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx),
const char *format, ...
);
void show(ui::Context &ctx, bool goBack = false);
void hide(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};

View File

@ -0,0 +1,434 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/ide.hpp"
#include "common/io.hpp"
#include "common/rom.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "ps1/system.h"
bool App::_startupWorker(void) {
#ifdef NDEBUG
_workerStatus.setNextScreen(_warningScreen);
#else
// Skip the warning screen in debug builds.
_workerStatus.setNextScreen(_buttonMappingScreen);
#endif
for (int i = 0; i < 2; i++) {
_workerStatus.update(i, 4, WSTR("App.startupWorker.initIDE"));
ide::devices[i].enumerate();
}
_workerStatus.update(2, 4, WSTR("App.startupWorker.initFAT"));
// Attempt to mount the secondary drive first, then in case of failure try
// mounting the primary drive instead.
if (!_fileProvider.init("1:"))
_fileProvider.init("0:");
_workerStatus.update(3, 4, WSTR("App.startupWorker.loadResources"));
_resourceFile = _fileProvider.openFile(
EXTERNAL_DATA_DIR "/resource.zip", file::READ
);
if (_resourceFile) {
_resourceProvider.close();
if (_resourceProvider.init(_resourceFile))
_loadResources();
}
_ctx.sounds[ui::SOUND_STARTUP].play();
return true;
}
struct DumpEntry {
public:
util::Hash dumpPrompt, hashPrompt;
const char *path;
const rom::Region &region;
size_t hashOffset;
};
static const DumpEntry _DUMP_ENTRIES[]{
{
.dumpPrompt = "App.romDumpWorker.dumpBIOS"_h,
.hashPrompt = "App.systemInfoWorker.hashBIOS"_h,
.path = "%s/bios.bin",
.region = rom::bios,
.hashOffset = offsetof(SystemInfo, biosCRC)
}, {
.dumpPrompt = "App.romDumpWorker.dumpRTC"_h,
.hashPrompt = "App.systemInfoWorker.hashRTC"_h,
.path = "%s/rtc.bin",
.region = rom::rtc,
.hashOffset = offsetof(SystemInfo, rtcCRC)
}, {
.dumpPrompt = "App.romDumpWorker.dumpFlash"_h,
.hashPrompt = "App.systemInfoWorker.hashFlash"_h,
.path = "%s/flash.bin",
.region = rom::flash,
.hashOffset = offsetof(SystemInfo, flash.crc)
}, {
.dumpPrompt = "App.romDumpWorker.dumpPCMCIA1"_h,
.hashPrompt = "App.systemInfoWorker.hashPCMCIA1"_h,
.path = "%s/pcmcia1.bin",
.region = rom::pcmcia[0],
.hashOffset = offsetof(SystemInfo, pcmcia[0].crc)
}, {
.dumpPrompt = "App.romDumpWorker.dumpPCMCIA2"_h,
.hashPrompt = "App.systemInfoWorker.hashPCMCIA2"_h,
.path = "%s/pcmcia2.bin",
.region = rom::pcmcia[1],
.hashOffset = offsetof(SystemInfo, pcmcia[1].crc)
}
};
static constexpr size_t _DUMP_CHUNK_LENGTH = 0x80000;
static constexpr size_t _DUMP_CHUNKS_PER_CRC = 32; // Save CRC32 every 16 MB
bool App::_romDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
// Store all dumps in a subdirectory named "dumpN" within the main data
// folder.
int index = 0;
char dirPath[32], filePath[32];
file::FileInfo info;
if (!_fileProvider.getFileInfo(info, EXTERNAL_DATA_DIR)) {
if (!_fileProvider.createDirectory(EXTERNAL_DATA_DIR))
goto _initError;
}
do {
index++;
snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%d", index);
} while (_fileProvider.getFileInfo(info, dirPath));
LOG("saving dumps to %s", dirPath);
if (!_fileProvider.createDirectory(dirPath))
goto _initError;
for (auto &entry : _DUMP_ENTRIES) {
if (!entry.region.isPresent())
continue;
size_t chunkLength, numChunks;
if (entry.region.regionLength < _DUMP_CHUNK_LENGTH) {
chunkLength = entry.region.regionLength;
numChunks = 1;
} else {
chunkLength = _DUMP_CHUNK_LENGTH;
numChunks = entry.region.regionLength / _DUMP_CHUNK_LENGTH;
}
snprintf(filePath, sizeof(filePath), entry.path, dirPath);
auto _file = _fileProvider.openFile(
filePath, file::WRITE | file::ALLOW_CREATE
);
if (!_file)
goto _writeError;
auto buffer = new uint8_t[chunkLength];
uint32_t offset = 0;
//assert(buffer);
for (size_t i = 0; i < numChunks; i++) {
_workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt));
entry.region.read(buffer, offset, chunkLength);
if (_file->write(buffer, chunkLength) < chunkLength) {
delete _file;
delete[] buffer;
goto _writeError;
}
offset += chunkLength;
}
delete _file;
delete[] buffer;
LOG("%s saved", filePath);
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _mainMenuScreen, WSTR("App.romDumpWorker.success"),
dirPath
);
_workerStatus.setNextScreen(_messageScreen);
return true;
_initError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.initError"),
dirPath
);
_workerStatus.setNextScreen(_messageScreen);
return false;
_writeError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.dumpError"),
filePath
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
bool App::_systemInfoWorker(void) {
// This is necessary to ensure the digital I/O ID is read at least once.
if (!_driver)
_cartDetectWorker();
_workerStatus.setNextScreen(_systemInfoScreen);
_systemInfo.clearFlags();
for (auto &entry : _DUMP_ENTRIES) {
if (!entry.region.isPresent())
continue;
size_t chunkLength, numChunks;
if (entry.region.regionLength < _DUMP_CHUNK_LENGTH) {
chunkLength = entry.region.regionLength;
numChunks = 1;
} else {
chunkLength = _DUMP_CHUNK_LENGTH;
numChunks = entry.region.regionLength / _DUMP_CHUNK_LENGTH;
}
uint32_t offset = 0;
uint32_t crc = 0;
auto crcPtr = reinterpret_cast<uint32_t *>(
reinterpret_cast<uintptr_t>(&_systemInfo) + entry.hashOffset
);
// Flash cards can be 16, 32 or 64 MB, so copies of the current CRC are
// saved after the first 16, then 32, 48 and finally 64 MB are read.
for (size_t i = 0; i < numChunks; i += _DUMP_CHUNKS_PER_CRC) {
size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks);
for (size_t j = i; j < end; j++) {
_workerStatus.update(j, numChunks, WSTRH(entry.hashPrompt));
crc = entry.region.zipCRC32(offset, chunkLength, crc);
offset += chunkLength;
}
*(crcPtr++) = crc;
}
}
_systemInfo.flags = SYSTEM_INFO_VALID;
_systemInfo.shell = rom::getShellInfo();
if (io::isRTCBatteryLow())
_systemInfo.flags |= SYSTEM_INFO_RTC_BATTERY_LOW;
_systemInfo.flash.jedecID = rom::flash.getJEDECID();
_systemInfo.flash.flags = FLASH_REGION_INFO_PRESENT;
if (rom::flash.hasBootExecutable())
_systemInfo.flash.flags |= FLASH_REGION_INFO_BOOTABLE;
for (int i = 0; i < 2; i++) {
auto &region = rom::pcmcia[i];
auto &card = _systemInfo.pcmcia[i];
if (region.isPresent()) {
card.jedecID = region.getJEDECID();
card.flags = FLASH_REGION_INFO_PRESENT;
if (region.hasBootExecutable())
card.flags |= FLASH_REGION_INFO_BOOTABLE;
}
}
return true;
}
struct LauncherEntry {
public:
const char *path;
uintptr_t loadOffset;
size_t length;
};
static const LauncherEntry _LAUNCHERS[]{
{
.path = "launchers/801f4000.psexe",
.loadOffset = 0x801f4000,
.length = 0xc000
}, {
.path = "launchers/803f4000.psexe",
.loadOffset = 0x803f4000,
.length = 0xc000
}
};
bool App::_executableWorker(void) {
_workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
// TODO: implement a file picker screen
const char *path = "psx.exe";
util::ExecutableHeader header;
uintptr_t executableEnd, stackTop;
auto _file = _fileProvider.openFile(path, file::READ);
size_t length;
if (!_file)
goto _fileError;
length = _file->read(&header, sizeof(header));
delete _file;
if (length != sizeof(header))
goto _fileError;
if (!header.validateMagic())
goto _fileError;
executableEnd = header.textOffset + header.textLength;
stackTop = uintptr_t(header.getStackPtr());
LOG("ptr=0x%08x, length=0x%x", header.textOffset, header.textLength);
// Find a launcher that does not overlap the new executable and can thus be
// used to load it. Note that this implicitly assumes that none of the
// launchers overlap the main executable.
for (auto &entry : _LAUNCHERS) {
uintptr_t launcherEnd = entry.loadOffset + entry.length;
if (
!(executableEnd <= entry.loadOffset) &&
!(launcherEnd <= header.textOffset)
)
continue;
if (
stackTop &&
(stackTop >= entry.loadOffset) &&
(stackTop <= launcherEnd)
)
continue;
// Load the launcher into memory, relocate it to the appropriate
// location and pass it the path to the executable to be loaded.
util::Data data;
if (!_resourceProvider.loadData(data, entry.path))
continue;
LOG("using %s", entry.path);
_workerStatus.update(0, 1, WSTR("App.executableWorker.load"));
header.copyFrom(data.ptr);
__builtin_memcpy(
header.getTextPtr(),
reinterpret_cast<void *>(
uintptr_t(data.ptr) + util::EXECUTABLE_BODY_OFFSET
),
header.textLength
);
data.destroy();
util::ExecutableLoader loader(
header, reinterpret_cast<void *>(launcherEnd)
);
char arg[128];
snprintf(
arg, sizeof(arg), "launcher.drive=%s", _fileProvider.getDriveString()
);
loader.addArgument(arg);
snprintf(arg, sizeof(arg), "launcher.path=%s", path);
loader.addArgument(arg);
// All destructors must be invoked manually as we are not returning to
// main() before starting the new executable.
_unloadCartData();
_resourceProvider.close();
if (_resourceFile)
delete _resourceFile;
_fileProvider.close();
uninstallExceptionHandler();
loader.run();
}
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.executableWorker.addressError"), path, header.textOffset,
executableEnd - 1, stackTop
);
_workerStatus.setNextScreen(_messageScreen);
return false;
_fileError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.executableWorker.fileError"),
path
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
bool App::_atapiEjectWorker(void) {
_workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
if (!(ide::devices[0].flags & ide::DEVICE_ATAPI)) {
LOG("primary drive is not ATAPI");
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.atapiEjectWorker.atapiError")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
ide::Packet packet;
packet.setStartStopUnit(ide::START_STOP_MODE_OPEN_TRAY);
auto error = ide::devices[0].atapiPacket(packet);
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.atapiEjectWorker.ejectError"), ide::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _mainMenuScreen, WSTR("App.atapiEjectWorker.success")
);
_workerStatus.setNextScreen(_messageScreen);
return true;
}
bool App::_rebootWorker(void) {
_workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
_workerStatus.setStatus(WORKER_REBOOT);
// Fall back to a soft reboot if the watchdog fails to reset the system.
delayMicroseconds(2000000);
softReset();
return true;
}

105
src/main/app/modals.cpp Normal file
View File

@ -0,0 +1,105 @@
#include <stdarg.h>
#include <stdio.h>
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/app/modals.hpp"
#include "main/uibase.hpp"
/* Modal screens */
void WorkerStatusScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("WorkerStatusScreen.title");
ProgressScreen::show(ctx, goBack);
}
void WorkerStatusScreen::update(ui::Context &ctx) {
auto &worker = APP->_workerStatus;
auto nextScreen = worker.nextScreen;
if ((worker.status == WORKER_NEXT) || (worker.status == WORKER_NEXT_BACK)) {
worker.reset();
ctx.show(*nextScreen, worker.status == WORKER_NEXT_BACK);
LOG("worker finished, next=0x%08x", nextScreen);
return;
}
_setProgress(ctx, worker.progress, worker.progressTotal);
_body = worker.message;
}
static const util::Hash _MESSAGE_TITLES[]{
"MessageScreen.title.success"_h,
"MessageScreen.title.error"_h
};
void MessageScreen::setMessage(
MessageType type, ui::Screen &prev, const char *format, ...
) {
_type = type;
_prevScreen = &prev;
va_list ap;
va_start(ap, format);
vsnprintf(_bodyText, sizeof(_bodyText), format, ap);
va_end(ap);
}
void MessageScreen::show(ui::Context &ctx, bool goBack) {
_title = STRH(_MESSAGE_TITLES[_type]);
_body = _bodyText;
_buttons[0] = STR("MessageScreen.ok");
_numButtons = 1;
_locked = _prevScreen ? false : true;
MessageBoxScreen::show(ctx, goBack);
ctx.sounds[ui::SOUND_ALERT].play();
}
void MessageScreen::update(ui::Context &ctx) {
MessageBoxScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(*_prevScreen, true, true);
}
void ConfirmScreen::setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx), const char *format,
...
) {
_prevScreen = &prev;
_callback = callback;
va_list ap;
va_start(ap, format);
vsnprintf(_bodyText, sizeof(_bodyText), format, ap);
va_end(ap);
}
void ConfirmScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("ConfirmScreen.title");
_body = _bodyText;
_buttons[0] = STR("ConfirmScreen.no");
_buttons[1] = STR("ConfirmScreen.yes");
_numButtons = 2;
MessageBoxScreen::show(ctx, goBack);
//ctx.sounds[ui::SOUND_ALERT].play();
}
void ConfirmScreen::update(ui::Context &ctx) {
MessageBoxScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (_activeButton)
_callback(ctx);
else
ctx.show(*_prevScreen, true, true);
}
}

49
src/main/app/modals.hpp Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
/* Modal screens */
class WorkerStatusScreen : public ui::ProgressScreen {
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
enum MessageType {
MESSAGE_SUCCESS = 0,
MESSAGE_ERROR = 1
};
class MessageScreen : public ui::MessageBoxScreen {
private:
MessageType _type;
char _bodyText[512];
ui::Screen *_prevScreen;
public:
void setMessage(
MessageType type, ui::Screen &prev, const char *format, ...
);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class ConfirmScreen : public ui::MessageBoxScreen {
private:
char _bodyText[512];
ui::Screen *_prevScreen;
void (*_callback)(ui::Context &ctx);
public:
void setMessage(
ui::Screen &prev, void (*callback)(ui::Context &ctx),
const char *format, ...
);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};

View File

@ -1,7 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/gpu.hpp"
#include "common/io.hpp"
@ -35,13 +34,15 @@ bool Settings::parse(const char *arg) {
return false;
switch (util::hash(arg, '=')) {
#if 0
case "boot.rom"_h:
//LOG("boot.rom=%s", &arg[9]);
LOG("boot.rom=%s", &arg[9]);
return true;
case "boot.from"_h:
//LOG("boot.from=%s", &arg[10]);
LOG("boot.from=%s", &arg[10]);
return true;
#endif
case "console"_h:
baudRate = int(strtol(&arg[8], nullptr, 0));

View File

@ -16,7 +16,8 @@
* If any exception other than an IRQ or syscall (such as a bus or alignment
* error) occurs, the exception handler defined in system.s will call
* _unhandledException() to safely halt the program. This is a very simple
* implementation of it that prints the state of all registers then locks up.
* implementation of it that prints the state of all registers (without using
* printf() to avoid bloating the binary) then locks up.
*/
#include <stdint.h>
@ -25,15 +26,15 @@
#ifndef NDEBUG
static const char *const _causeNames[] = {
"load address error",
"store address error",
"instruction bus error",
"data bus error",
"syscall",
"break instruction",
"reserved instruction",
"coprocessor unusable",
"arithmetic overflow"
"Load address error",
"Store address error",
"Instruction bus error",
"Data bus error",
"Syscall",
"Break instruction",
"Reserved instruction",
"Coprocessor unusable",
"Arithmetic overflow"
};
static const char _registerNames[] =
@ -41,35 +42,60 @@ static const char _registerNames[] =
"t0" "t1" "t2" "t3" "t4" "t5" "t6" "t7"
"s0" "s1" "s2" "s3" "s4" "s5" "s6" "s7"
"t8" "t9" "gp" "sp" "fp" "ra" "hi" "lo";
static const char _hexDigits[] = "0123456789abcdef";
static void _printHexValue(uint32_t value) {
for (int i = 8; i; i--, value <<= 4)
putchar(_hexDigits[value >> 28]);
}
#endif
void _unhandledException(int cause, uint32_t badv) {
#ifndef NDEBUG
if (cause <= 5)
printf("Exception: %s (%08x)\nRegister dump:\n", _causeNames[cause - 4], badv);
else
printf("Exception: %s\nRegister dump:\n", _causeNames[cause - 4]);
puts(_causeNames[cause - 4]);
if (cause <= 5) {
putchar('@');
putchar(' ');
_printHexValue(badv);
putchar('\n');
}
puts("Register dump:");
const char *name = _registerNames;
uint32_t *reg = (uint32_t *) &(currentThread->pc);
for (int i = 31; i >= 0; i--) {
char a = *(name++), b = *(name++);
putchar(' ');
putchar(' ');
putchar(*(name++));
putchar(*(name++));
putchar('=');
_printHexValue(*(reg++));
printf(" %c%c=%08x", a, b, *(reg++));
if (!(i % 4))
putchar('\n');
}
printf("Stack dump:\n");
puts("Stack dump:");
uint32_t *addr = ((uint32_t *) currentThread->sp) - 7;
uint32_t *end = ((uint32_t *) currentThread->sp) + 7;
for (; addr <= end; addr++) {
char arrow = (((uint32_t) addr) == currentThread->sp) ? '>' : ' ';
if (((uint32_t) addr) == currentThread->sp)
putchar('>');
else
putchar(' ');
printf("%c %08x: %08x\n", arrow, addr, *addr);
putchar(' ');
_printHexValue((uint32_t) addr);
putchar(':');
putchar(' ');
_printHexValue(*addr);
putchar('\n');
}
#endif