Add system info screen, directory API, new icons, fix delay

This commit is contained in:
spicyjpeg 2023-12-29 19:37:39 +01:00
parent 7e7db5b890
commit f38a96621a
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
28 changed files with 1159 additions and 260 deletions

View File

@ -6,7 +6,7 @@ set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/cmake/toolchain.cmake")
project(
cart_tool_private
LANGUAGES C CXX ASM
VERSION 0.3.4
VERSION 0.3.5
DESCRIPTION "Konami System 573 security cartridge tool"
)
@ -37,6 +37,7 @@ add_executable(
src/io.cpp
src/main.cpp
src/pad.cpp
src/rom.cpp
src/spu.cpp
src/uibase.cpp
src/uicommon.cpp

View File

@ -50,8 +50,15 @@
"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. 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 writing dumps to the hard drive. 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 (%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."
},
"systemInfoWorker": {
"hashBIOS": "Calculating BIOS ROM checksum...",
"hashRTC": "Calculating RTC RAM checksum...",
"hashFlash": "Calculating internal flash checksum...",
"hashPCMCIA1": "Calculating PCMCIA card 1 checksum...",
"hashPCMCIA2": "Calculating PCMCIA card 2 checksum..."
},
"atapiEjectWorker": {
"eject": "Sending eject command...",
@ -123,24 +130,30 @@
},
"CartInfoScreen": {
"title": "{CART_ICON} Cartridge information",
"digitalIOInfo": "Digital I/O ID:\t%s\nDigital I/O SN:\t%s\n\n",
"cartInfo": "Cartridge type:\t%s\nUnlock status:\t%s\nDS2401 identifier:\t%s\nZS01 identifier:\t%s\nConfiguration:\t%s\n\n",
"title": "{CART_ICON} Cartridge information",
"id": {
"error": "read failure",
"noSystemID": "no digital I/O board",
"noCartID": "cartridge has no DS2401",
"noZSID": "not a ZS01 cartridge"
"digitalIO": {
"header": "Digital I/O board:\n",
"info": " DS2401 ID:\t\t%s\n Serial number:\t%s\n",
"error": " Failed to read the board's DS2401\n",
"noBoard": " No digital I/O board installed\n"
},
"config": {
"error": "read failure",
"locked": "unlock required"
"cart": {
"header": "Security cartridge:\n",
"x76f041Info": " Chip type:\t\t{CHIP_ICON} Xicor X76F041\n Unlock status:\t%s\n DS2401 ID:\t\t%s\n Configuration:\t%s\n",
"x76f100Info": " Chip type:\t\t{CHIP_ICON} Xicor X76F041\n Unlock status:\t%s\n DS2401 ID:\t\t%s\n",
"zs01Info": " Chip type:\t\t{CHIP_ICON} Konami ZS01 (PIC16CE625)\n Unlock status:\t%s\n DS2401 ID:\t\t%s\n ZS01 ID:\t\t%s\n Configuration:\t%s\n"
},
"unlockStatus": {
"locked": "{CLOSED_LOCK} locked, game key required",
"unlocked": "{OPEN_LOCK} unlocked"
},
"id": {
"error": "read failure",
"noCartID": "cartridge has no DS2401",
"locked": "unlock required"
},
"pairing": {
"unsupported": "This game does not pair to I/O boards",
"unknown": "Unlock required to view pairing status",
@ -154,12 +167,12 @@
"locked": {
"unidentified": "This cartridge contains data for an unsupported game.\n\nAs the cartridge is currently locked, you will have to manually select which game it belongs to in order to dump its contents or reflash it for use with a supported game, as each game has a different unlocking key.",
"identified": "This cartridge has been identified as:\n %s\n %s\n\nIf this is correct, you may proceed to unlock the cartridge using the appropriate key for this game in order to access and modify its contents.",
"identified": "This cartridge has been identified as:\n %s\n %s\n\nIf this is correct, you may proceed to unlock the cartridge using the appropriate key for this game.",
"unknown": "This cartridge cannot be identified without unlocking it first.\n\nYou will have to manually select which game it belongs to in order to dump its contents or reflash it for use with a supported game, as each game has a different unlocking key."
},
"unlocked": {
"unidentified": "This cartridge contains data for an unsupported game.\n\nThe system identifier (if any) cannot be reset or edited, however you may still dump the cartridge's contents or reflash it for use with a supported game.",
"identified": "This cartridge has been identified as:\n %s\n %s\n\nYou may now proceed to reset the system identifier, edit it or erase and reflash the cartridge for use with another game.",
"identified": "This cartridge has been identified as:\n %s\n %s\n\nYou may now proceed to reset the system identifier, edit it or reflash the cartridge for use with another game.",
"blank": "This cartridge has been previously erased and is now blank.\n\nIt must be flashed and optionally initialized with a system identifier in order to be used with a supported game."
}
},
@ -170,17 +183,14 @@
},
"x76f041": {
"name": "{CHIP_ICON} Xicor X76F041",
"warning": "X76F041 cartridges keep track of how many times a wrong key has been used. If too many unlock attempts fail, they will lock out any further access and thus become PERMANENTLY BRICKED, with no way to restore access.\n\nAre you sure the key is correct?",
"error": "An error occurred while attempting to unlock the cartridge; the key is probably incorrect. If you are sure you selected the proper key, the maximum allowed number of failed unlocking attempts may have been exceeded (and thus the cartridge may have been bricked).\n\nPress the Test button to view debug logs."
},
"x76f100": {
"name": "{CHIP_ICON} Xicor X76F100",
"warning": "X76F100 cartridges keep track of how many times a wrong key has been used. If too many unlock attempts fail, the cartridge will self-erase and will have to be reinitialized to factory data and paired to the system again if the game requires it.\n\nAre you sure the key is correct?",
"error": "An error occurred while attempting to unlock the cartridge; the key is probably incorrect. If you are sure you selected the proper key, the cartridge may have self-erased and reset the key to a null key due to too many failed unlocking attempts.\n\nPress the Test button to view debug logs."
},
"zs01": {
"name": "{CHIP_ICON} Konami ZS01 (PIC16CE625)",
"warning": "ZS01 cartridges keep track of how many times a wrong key has been used. If too many unlock attempts fail, the cartridge will self-erase and will have to be reinitialized to factory data and paired to the system again if the game requires it.\n\nAre you sure the key is correct?",
"error": "An error occurred while attempting to unlock the cartridge; the key is probably incorrect. If you are sure you selected the proper key, the cartridge may have self-erased and reset the key to a null key due to too many failed unlocking attempts.\n\nPress the Test button to view debug logs."
}
@ -221,21 +231,29 @@
"name": "Restore flash, PCMCIA cards or RTC from dump",
"prompt": "Restore the contents of the internal ROMs or any PCMCIA cards from the IDE hard drive or CF card connected as secondary drive (if any)."
},
"systemInfo": {
"name": "View system information",
"prompt": "Display information about the system's I/O board, IDE drives, BIOS ROM, internal flash and PCMCIA flash cards."
},
"setResolution": {
"name": "Change screen resolution",
"prompt": "Switch to a different screen resolution and aspect ratio."
"prompt": "Switch to a different screen resolution and aspect ratio. Some monitors and upscalers may not support all resolutions."
},
"about": {
"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)."
},
"ejectCD": {
"name": "Eject CD-ROM",
"prompt": "Open the CD-ROM drive's tray. You may use this option if the drive's eject button is not easily accessible on your 573."
},
"reboot": {
"name": "Reboot system",
"prompt": "Restart the system. Swap out the CD-ROM and toggle DIP switch 4 before selecting this option to boot the installed game."
"prompt": "Restart the system. Swap out the CD-ROM and toggle DIP switch 4 before selecting this option to boot the game installed on this system."
}
},
@ -260,7 +278,7 @@
},
"ResolutionScreen": {
"title": "{CART_ICON} Change screen resolution",
"title": "{CART_ICON} Select screen resolution",
"prompt": "Select a resolution appropriate for your monitor or upscaler setup. Note that interlaced modes may be subject to flickering.",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
@ -283,8 +301,58 @@
},
"SystemInfoScreen": {
"title": "System information",
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back."
"title": "{CART_ICON} System information",
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back.",
"digitalIO": {
"header": "Digital I/O board:\n",
"info": " DS2401 ID:\t\t%s\n Serial number:\t%s\n",
"error": " Failed to read the board's DS2401\n",
"noBoard": " No digital I/O board installed\n"
},
"ide": {
"header": {
"primary": "Primary IDE drive:\n",
"secondary": "Secondary IDE drive:\n"
},
"commonInfo": " Model name:\t\t%s\n Revision number:\t%s\n Serial number:\t%s\n",
"ataInfo": " Device type:\tATA\n Capacity:\t\t%llu MB (%d-bit LBA)\n",
"atapiInfo": " Device type:\tATAPI\n Packet length:\t%d bytes\n",
"hasTrim": " Supports trim command\n",
"hasFlush": " Supports cache flushing\n",
"error": " No drive present or initialization failed\n"
},
"bios": {
"header": "BIOS ROM:\n",
"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",
"openbios": " Kernel type:\tOpenBIOS\n Kernel build ID:\n %s\n",
"unknown": " Kernel type:\tUnknown\n"
},
"shellInfo": {
"konami": " Shell type:\t\t%s\n Boot file name:\t%s\n",
"unknown": " Shell type:\t\tUnknown\n"
}
},
"rtc": {
"header": "RTC:\n",
"info": " CRC32:\t\t%08X\n",
"batteryLow": " Battery voltage is below 2.5V\n"
},
"flash": {
"header": "Internal flash memory:\n",
"info": " JEDEC ID:\t\t%02X, %02X\n CRC32:\t\t%08X\n",
"bootable": " Valid boot executable present\n"
},
"pcmcia": {
"header": "PCMCIA card in slot %d:\n",
"info": " JEDEC ID:\t\t%02X, %02X\n CRC32 (16 MB):\t%08X\n CRC32 (32 MB):\t%08X\n CRC32 (64 MB):\t%08X\n",
"bootable": " Valid boot executable present\n",
"noCard": " No card inserted\n"
}
},
"UnlockKeyScreen": {

View File

@ -104,14 +104,19 @@
"\u0080": { "x": 0, "y": 54, "width": 6, "height": 9 },
"\u0081": { "x": 6, "y": 54, "width": 6, "height": 9 },
"\u0082": { "x": 12, "y": 54, "width": 4, "height": 9 },
"\u0083": { "x": 18, "y": 54, "width": 4, "height": 9 },
"\u0083": { "x": 18, "y": 54, "width": 4, "height": 9 },
"\u0084": { "x": 24, "y": 54, "width": 7, "height": 9, "icon": true },
"\u0085": { "x": 31, "y": 54, "width": 7, "height": 9, "icon": true },
"\u0086": { "x": 38, "y": 54, "width": 9, "height": 9, "icon": true },
"\u0087": { "x": 47, "y": 54, "width": 8, "height": 10, "icon": true },
"\u0088": { "x": 55, "y": 54, "width": 11, "height": 10, "icon": true },
"\u0089": { "x": 66, "y": 54, "width": 12, "height": 10, "icon": true },
"\u008a": { "x": 78, "y": 54, "width": 14, "height": 10, "icon": true }
"\u0090": { "x": 0, "y": 63, "width": 7, "height": 9, "icon": true },
"\u0091": { "x": 7, "y": 63, "width": 7, "height": 9, "icon": true },
"\u0092": { "x": 14, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0093": { "x": 23, "y": 63, "width": 8, "height": 10, "icon": true },
"\u0094": { "x": 31, "y": 63, "width": 11, "height": 10, "icon": true },
"\u0095": { "x": 42, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0096": { "x": 51, "y": 63, "width": 9, "height": 9, "icon": true },
"\u0097": { "x": 60, "y": 63, "width": 9, "height": 10, "icon": true },
"\u0098": { "x": 69, "y": 63, "width": 9, "height": 10, "icon": true },
"\u0099": { "x": 78, "y": 63, "width": 9, "height": 10, "icon": true },
"\u009a": { "x": 0, "y": 73, "width": 12, "height": 10, "icon": true },
"\u009b": { "x": 12, "y": 73, "width": 14, "height": 10, "icon": true }
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -100,7 +100,7 @@ function(ps1_target_incbin
CONFIGURE
OUTPUT "${_assemblyFile}"
CONTENT [[
.section ${section}.${_symbolName}, "aw"
.section ${section}.${_symbolName}, "a"
.balign 8
.global ${_symbolName}
@ -112,7 +112,7 @@ ${_symbolName}:
.incbin "${_path}"
${_symbolName}_end:
.section ${section}.${_sizeSymbolName}, "aw"
.section ${section}.${_sizeSymbolName}, "a"
.balign 4
.global ${_sizeSymbolName}

View File

@ -3,14 +3,15 @@
#include <stdint.h>
#include "app/cartactions.hpp"
#include "app/cartunlock.hpp"
#include "app/main.hpp"
#include "app/misc.hpp"
#include "app/cartunlock.hpp"
#include "ps1/system.h"
#include "cart.hpp"
#include "cartdata.hpp"
#include "cartio.hpp"
#include "file.hpp"
#include "rom.hpp"
#include "uibase.hpp"
/* Worker status class */
@ -46,6 +47,48 @@ public:
void finish(void);
};
/* System information buffer */
enum FlashRegionInfoFlag : uint8_t {
FLASH_REGION_INFO_PRESENT = 1 << 0,
FLASH_REGION_INFO_BOOTABLE = 1 << 1
};
class FlashRegionInfo {
public:
uint8_t flags, manufacturerID, deviceID;
uint32_t crc[4];
inline void clearFlags(void) {
flags = 0;
}
};
enum SystemInfoFlag : uint32_t {
SYSTEM_INFO_VALID = 1 << 0,
SYSTEM_INFO_RTC_BATTERY_LOW = 1 << 1
};
class SystemInfo {
public:
uint32_t flags;
uint32_t biosCRC, rtcCRC;
const rom::ShellInfo *shell;
FlashRegionInfo flash, pcmcia[2];
inline SystemInfo(void) {
clearFlags();
}
inline void clearFlags(void) {
flags = 0;
flash.flags = 0;
pcmcia[0].clearFlags();
pcmcia[1].clearFlags();
}
};
/* App class */
static constexpr size_t WORKER_STACK_SIZE = 0x20000;
@ -57,6 +100,7 @@ class App {
friend class WarningScreen;
friend class ButtonMappingScreen;
friend class MainMenuScreen;
friend class SystemInfoScreen;
friend class ResolutionScreen;
friend class AboutScreen;
friend class CartInfoScreen;
@ -75,6 +119,7 @@ private:
WarningScreen _warningScreen;
ButtonMappingScreen _buttonMappingScreen;
MainMenuScreen _mainMenuScreen;
SystemInfoScreen _systemInfoScreen;
ResolutionScreen _resolutionScreen;
AboutScreen _aboutScreen;
CartInfoScreen _cartInfoScreen;
@ -97,6 +142,7 @@ private:
cart::Dump _dump;
cart::CartDB _db;
SystemInfo _systemInfo;
Thread _workerThread;
WorkerStatus _workerStatus;
bool (App::*_workerFunction)(void);
@ -120,6 +166,7 @@ private:
bool _cartReflashWorker(void);
bool _cartEraseWorker(void);
bool _romDumpWorker(void);
bool _systemInfoWorker(void);
bool _atapiEjectWorker(void);
bool _rebootWorker(void);

View File

@ -11,6 +11,7 @@
#include "file.hpp"
#include "ide.hpp"
#include "io.hpp"
#include "rom.hpp"
#include "uibase.hpp"
#include "util.hpp"
#include "utilerror.hpp"
@ -232,7 +233,9 @@ bool App::_cartDumpWorker(void) {
char code[8], region[8], path[32];
size_t length = _dump.getDumpLength();
if (!_fileProvider.fileExists(EXTERNAL_DATA_DIR)) {
file::FileInfo info;
if (!_fileProvider.getFileInfo(info, EXTERNAL_DATA_DIR)) {
if (!_fileProvider.createDirectory(EXTERNAL_DATA_DIR))
goto _error;
}
@ -387,71 +390,62 @@ bool App::_cartEraseWorker(void) {
return _cartUnlockWorker();
}
struct DumpRegion {
struct DumpEntry {
public:
util::Hash prompt;
const char *path;
const uint16_t *ptr;
size_t length;
int bank;
uint32_t inputs;
util::Hash dumpPrompt, hashPrompt;
const char *path;
const rom::Region &region;
size_t hashOffset;
};
enum DumpBank {
BANK_NONE_8BIT = -1,
BANK_NONE_16BIT = -2
};
static const DumpRegion _DUMP_REGIONS[]{
static const DumpEntry _DUMP_ENTRIES[]{
{
.prompt = "App.romDumpWorker.dumpBIOS"_h,
.path = EXTERNAL_DATA_DIR "/dump%d/bios.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV2_BASE),
.length = 0x80000,
.bank = BANK_NONE_16BIT,
.inputs = 0
.dumpPrompt = "App.romDumpWorker.dumpBIOS"_h,
.hashPrompt = "App.systemInfoWorker.hashBIOS"_h,
.path = "%s/bios.bin",
.region = rom::bios,
.hashOffset = offsetof(SystemInfo, biosCRC)
}, {
.prompt = "App.romDumpWorker.dumpRTC"_h,
.path = EXTERNAL_DATA_DIR "/dump%d/rtc.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE | 0x620000),
.length = 0x2000,
.bank = BANK_NONE_8BIT,
.inputs = 0
.dumpPrompt = "App.romDumpWorker.dumpRTC"_h,
.hashPrompt = "App.systemInfoWorker.hashRTC"_h,
.path = "%s/rtc.bin",
.region = rom::rtc,
.hashOffset = offsetof(SystemInfo, rtcCRC)
}, {
.prompt = "App.romDumpWorker.dumpFlash"_h,
.path = EXTERNAL_DATA_DIR "/dump%d/flash.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x1000000,
.bank = SYS573_BANK_FLASH,
.inputs = 0
.dumpPrompt = "App.romDumpWorker.dumpFlash"_h,
.hashPrompt = "App.systemInfoWorker.hashFlash"_h,
.path = "%s/flash.bin",
.region = rom::flash,
.hashOffset = offsetof(SystemInfo, flash.crc)
}, {
.prompt = "App.romDumpWorker.dumpPCMCIA1"_h,
.path = EXTERNAL_DATA_DIR "/dump%d/pcmcia1.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x4000000,
.bank = SYS573_BANK_PCMCIA1,
.inputs = io::JAMMA_PCMCIA_CD1
.dumpPrompt = "App.romDumpWorker.dumpPCMCIA1"_h,
.hashPrompt = "App.systemInfoWorker.hashPCMCIA1"_h,
.path = "%s/pcmcia1.bin",
.region = rom::pcmcia[0],
.hashOffset = offsetof(SystemInfo, pcmcia[0].crc)
}, {
.prompt = "App.romDumpWorker.dumpPCMCIA2"_h,
.path = EXTERNAL_DATA_DIR "/dump%d/pcmcia2.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x4000000,
.bank = SYS573_BANK_PCMCIA2,
.inputs = io::JAMMA_PCMCIA_CD2
.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"));
uint32_t inputs = io::getJAMMAInputs();
// Store all dumps in a subdirectory named "dumpN" within the main data
// folder.
int index = 0;
char dirPath[32], filePath[32];
if (!_fileProvider.fileExists(EXTERNAL_DATA_DIR)) {
file::FileInfo info;
if (!_fileProvider.getFileInfo(info, EXTERNAL_DATA_DIR)) {
if (!_fileProvider.createDirectory(EXTERNAL_DATA_DIR))
goto _initError;
}
@ -459,21 +453,28 @@ bool App::_romDumpWorker(void) {
do {
index++;
snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%d", index);
} while (_fileProvider.fileExists(dirPath));
} while (_fileProvider.getFileInfo(info, dirPath));
LOG("saving dumps to %s", dirPath);
if (!_fileProvider.createDirectory(dirPath))
goto _initError;
for (int i = 0; i < util::countOf(_DUMP_REGIONS); i++) {
auto &region = _DUMP_REGIONS[i];
// Skip PCMCIA slots if a card is not inserted.
if (region.inputs && !(inputs & region.inputs))
for (auto &entry : _DUMP_ENTRIES) {
if (!entry.region.isPresent())
continue;
snprintf(filePath, sizeof(filePath), region.path, index);
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
@ -482,49 +483,30 @@ bool App::_romDumpWorker(void) {
if (!_file)
goto _writeError;
// The buffer has to be 8 KB to match the size of RTC RAM.
uint8_t buffer[0x2000];
auto buffer = new uint8_t[chunkLength];
uint32_t offset = 0;
const uint16_t *ptr = region.ptr;
int count = region.length / sizeof(buffer);
int bank = region.bank;
//assert(buffer);
if (bank >= 0)
io::setFlashBank(bank++);
for (size_t i = 0; i < numChunks; i++) {
_workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt));
entry.region.read(buffer, offset, chunkLength);
for (int j = 0; j < count; j++) {
_workerStatus.update(j, count, WSTRH(region.prompt));
// The RTC is an 8-bit device connected to a 16-bit bus, i.e. each
// byte must be read as a 16-bit value and then the upper 8 bits
// must be discarded.
if (bank == BANK_NONE_8BIT) {
uint8_t *output = buffer;
for (size_t k = sizeof(buffer); k; k--)
*(output++) = static_cast<uint8_t>(*(ptr++));
} else {
uint16_t *output = reinterpret_cast<uint16_t *>(buffer);
for (size_t k = sizeof(buffer); k; k -= 2)
*(output++) = *(ptr++);
}
if (
(bank >= 0) &&
(ptr >= reinterpret_cast<const void *>(DEV0_BASE | 0x400000))
) {
ptr = region.ptr;
io::setFlashBank(bank++);
}
if (_file->write(buffer, sizeof(buffer)) < sizeof(buffer)) {
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);
}
@ -537,19 +519,96 @@ bool App::_romDumpWorker(void) {
_initError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.initError")
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.initError"),
dirPath
);
_workerStatus.setNextScreen(_messageScreen);
return false;
_writeError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.dumpError")
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.flash.flags = FLASH_REGION_INFO_PRESENT;
_systemInfo.shell = rom::getShellInfo();
if (io::isRTCBatteryLow())
_systemInfo.flags |= SYSTEM_INFO_RTC_BATTERY_LOW;
if (rom::flash.hasBootExecutable())
_systemInfo.flash.flags |= FLASH_REGION_INFO_BOOTABLE;
// TODO: actually read the JEDEC IDs
_systemInfo.flash.manufacturerID = 0;
_systemInfo.flash.deviceID = 0;
for (int i = 0; i < 2; i++) {
auto &region = rom::pcmcia[i];
auto &card = _systemInfo.pcmcia[i];
if (region.isPresent()) {
card.flags |= FLASH_REGION_INFO_PRESENT;
if (region.hasBootExecutable())
card.flags |= FLASH_REGION_INFO_BOOTABLE;
card.manufacturerID = 0;
card.deviceID = 0;
}
}
return true;
}
bool App::_atapiEjectWorker(void) {
_workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));

View File

@ -221,6 +221,7 @@ void HexdumpScreen::show(ui::Context &ctx, bool goBack) {
}
*(--ptr) = 0;
LOG("remaining=%d", end - ptr);
TextScreen::show(ctx, goBack);
}

View File

@ -13,24 +13,20 @@
struct CartType {
public:
util::Hash name, warning, error;
util::Hash warning, error;
};
static const CartType _CART_TYPES[cart::NUM_CHIP_TYPES]{
{
.name = "CartInfoScreen.noCart.name"_h,
.warning = 0,
.error = 0
}, {
.name = "CartInfoScreen.x76f041.name"_h,
.warning = "CartInfoScreen.x76f041.warning"_h,
.error = "CartInfoScreen.x76f041.error"_h
}, {
.name = "CartInfoScreen.x76f100.name"_h,
.warning = "CartInfoScreen.x76f100.warning"_h,
.error = "CartInfoScreen.x76f100.error"_h
}, {
.name = "CartInfoScreen.zs01.name"_h,
.warning = "CartInfoScreen.zs01.warning"_h,
.error = "CartInfoScreen.zs01.error"_h
}
@ -54,50 +50,38 @@ static const util::Hash _UNLOCKED_PROMPTS[]{
"CartInfoScreen.description.unlocked.blank"_h
};
#define _PRINT(...) (ptr += snprintf(ptr, end - ptr, __VA_ARGS__))
#define _PRINTLN() (*(ptr++) = '\n')
void CartInfoScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("CartInfoScreen.title");
_body = _bodyText;
TextScreen::show(ctx, goBack);
auto &dump = APP->_dump;
char id1[32], id2[32], config[32];
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
// Digital I/O board info
// Digital I/O board
_PRINT(STR("CartInfoScreen.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) {
__builtin_strcpy(id1, STR("CartInfoScreen.id.error"));
__builtin_strcpy(id2, id1);
_PRINT(STR("SystemInfoScreen.digitalIO.error"));
} else {
__builtin_strcpy(id1, STR("CartInfoScreen.id.noSystemID"));
__builtin_strcpy(id2, id1);
_PRINT(STR("SystemInfoScreen.digitalIO.noBoard"));
}
ptr += snprintf(
ptr, end - ptr, STR("CartInfoScreen.digitalIOInfo"), id1, id2
);
_PRINTLN();
// Cartridge info
if (!dump.chipType) {
memccpy(ptr, STR("CartInfoScreen.description.noCart"), 0, end - ptr);
_prompt = STR("CartInfoScreen.prompt.error");
return;
}
if (
//dump.getChipSize().publicDataLength &&
(dump.chipType == cart::ZS01) &&
!(dump.flags & cart::DUMP_PUBLIC_DATA_OK)
) {
memccpy(ptr, STR("CartInfoScreen.description.initError"), 0, end - ptr);
_prompt = STR("CartInfoScreen.prompt.error");
return;
}
// Security cartridge
auto unlockStatus = (dump.flags & cart::DUMP_PRIVATE_DATA_OK)
? STR("CartInfoScreen.unlockStatus.unlocked")
: STR("CartInfoScreen.unlockStatus.locked");
if (dump.flags & cart::DUMP_CART_ID_OK)
dump.cartID.toString(id1);
@ -106,28 +90,55 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) {
else
__builtin_strcpy(id1, STR("CartInfoScreen.id.noCartID"));
if (dump.flags & cart::DUMP_ZS_ID_OK)
dump.zsID.toString(id2);
else if (dump.chipType == cart::ZS01)
__builtin_strcpy(id2, STR("CartInfoScreen.id.error"));
else
__builtin_strcpy(id2, STR("CartInfoScreen.id.noZSID"));
if (dump.flags & cart::DUMP_CONFIG_OK)
util::hexToString(config, dump.config, sizeof(dump.config), '-');
else if (dump.flags & cart::DUMP_PRIVATE_DATA_OK)
__builtin_strcpy(config, STR("CartInfoScreen.config.error"));
__builtin_strcpy(config, STR("CartInfoScreen.id.error"));
else
__builtin_strcpy(config, STR("CartInfoScreen.config.locked"));
__builtin_strcpy(config, STR("CartInfoScreen.id.locked"));
auto unlockStatus = (dump.flags & cart::DUMP_PRIVATE_DATA_OK)
? STR("CartInfoScreen.unlockStatus.unlocked")
: STR("CartInfoScreen.unlockStatus.locked");
switch (dump.chipType) {
case cart::NONE:
_PRINT(STR("CartInfoScreen.description.noCart"));
ptr += snprintf(
ptr, end - ptr, STR("CartInfoScreen.cartInfo"),
STRH(_CART_TYPES[dump.chipType].name), unlockStatus, id1, id2, config
);
_prompt = STR("CartInfoScreen.prompt.error");
goto _done;
case cart::X76F041:
_PRINT(STR("CartInfoScreen.cart.header"));
_PRINT(
STR("CartInfoScreen.cart.x76f041Info"), unlockStatus, id1,
config
);
break;
case cart::X76F100:
_PRINT(STR("CartInfoScreen.cart.header"));
_PRINT(STR("CartInfoScreen.cart.x76f100Info"), unlockStatus, id1);
break;
case cart::ZS01:
if (!(dump.flags & cart::DUMP_PUBLIC_DATA_OK)) {
_PRINT(STR("CartInfoScreen.description.initError"));
_prompt = STR("CartInfoScreen.prompt.error");
goto _done;
}
if (dump.flags & cart::DUMP_ZS_ID_OK)
dump.zsID.toString(id2);
else
__builtin_strcpy(id2, STR("CartInfoScreen.id.error"));
_PRINT(STR("CartInfoScreen.cart.header"));
_PRINT(
STR("CartInfoScreen.cart.zs01Info"), unlockStatus, id1, id2,
config
);
break;
}
_PRINTLN();
// At this point the cartridge can be in one of 8 states:
// - locked, identified
@ -189,19 +200,22 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) {
state = UNKNOWN;
}
// Description
if (dump.flags & cart::DUMP_PRIVATE_DATA_OK) {
ptr += snprintf(
ptr, end - ptr, STRH(_UNLOCKED_PROMPTS[state]), name, pairStatus
);
_PRINT(STRH(_UNLOCKED_PROMPTS[state]), name, pairStatus);
_prompt = STR("CartInfoScreen.prompt.unlocked");
} else {
ptr += snprintf(
ptr, end - ptr, STRH(_LOCKED_PROMPTS[state]), name, pairStatus
);
_PRINT(STRH(_LOCKED_PROMPTS[state]), name, pairStatus);
_prompt = STR("CartInfoScreen.prompt.locked");
}
_done:
//*(--ptr) = 0;
LOG("remaining=%d", end - ptr);
TextScreen::show(ctx, goBack);
}
void CartInfoScreen::update(ui::Context &ctx) {

View File

@ -1,7 +1,10 @@
#include <stdint.h>
#include "app/app.hpp"
#include "app/main.hpp"
#include "ps1/gpucmd.h"
#include "ide.hpp"
#include "rom.hpp"
#include "uibase.hpp"
#include "util.hpp"
@ -99,11 +102,11 @@ static const MenuEntry _MENU_ENTRIES[]{
.prompt = "MainMenuScreen.restore.prompt"_h,
.target = &MainMenuScreen::restore
}, {
#endif
.name = "MainMenuScreen.systemInfo.name"_h,
.prompt = "MainMenuScreen.systemInfo.prompt"_h,
.target = &MainMenuScreen::systemInfo
}, {
#endif
.name = "MainMenuScreen.setResolution.name"_h,
.prompt = "MainMenuScreen.setResolution.prompt"_h,
.target = &MainMenuScreen::setResolution
@ -112,6 +115,12 @@ 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
}, {
#endif
.name = "MainMenuScreen.ejectCD.name"_h,
.prompt = "MainMenuScreen.ejectCD.prompt"_h,
.target = &MainMenuScreen::ejectCD
@ -153,7 +162,12 @@ void MainMenuScreen::restore(ui::Context &ctx) {
}
void MainMenuScreen::systemInfo(ui::Context &ctx) {
//ctx.show(APP->systemInfoScreen, false, true);
if (APP->_systemInfo.flags & SYSTEM_INFO_VALID) {
ctx.show(APP->_systemInfoScreen, false, true);
} else {
APP->_setupWorker(&App::_systemInfoWorker);
ctx.show(APP->_workerStatusScreen, false, true);
}
}
void MainMenuScreen::setResolution(ui::Context &ctx) {
@ -164,6 +178,10 @@ 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::ejectCD(ui::Context &ctx) {
APP->_setupWorker(&App::_atapiEjectWorker);
ctx.show(APP->_workerStatusScreen, false, true);
@ -194,6 +212,163 @@ void MainMenuScreen::update(ui::Context &ctx) {
(this->*action.target)(ctx);
}
static const util::Hash _SYSTEM_INFO_IDE_HEADERS[]{
"SystemInfoScreen.ide.header.primary"_h,
"SystemInfoScreen.ide.header.secondary"_h
};
#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();
}
// 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.manufacturerID,
info.flash.deviceID, 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.manufacturerID,
card.deviceID, 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;

View File

@ -37,6 +37,7 @@ public:
void systemInfo(ui::Context &ctx);
void setResolution(ui::Context &ctx);
void about(ui::Context &ctx);
void launchEXE(ui::Context &ctx);
void ejectCD(ui::Context &ctx);
void reboot(ui::Context &ctx);
@ -44,6 +45,15 @@ public:
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;

View File

@ -13,14 +13,19 @@
#define EXTERNAL_DATA_DIR "cartdata"
#define CH_UP_ARROW "\x80"
#define CH_DOWN_ARROW "\x81"
#define CH_LEFT_ARROW "\x82"
#define CH_RIGHT_ARROW "\x83"
#define CH_LEFT_BUTTON "\x84"
#define CH_RIGHT_BUTTON "\x85"
#define CH_START_BUTTON "\x86"
#define CH_CLOSED_LOCK "\x87"
#define CH_OPEN_LOCK "\x88"
#define CH_CHIP_ICON "\x89"
#define CH_CART_ICON "\x8a"
#define CH_UP_ARROW "\x80"
#define CH_DOWN_ARROW "\x81"
#define CH_LEFT_ARROW "\x82"
#define CH_RIGHT_ARROW "\x83"
#define CH_LEFT_BUTTON "\x90"
#define CH_RIGHT_BUTTON "\x91"
#define CH_START_BUTTON "\x92"
#define CH_CLOSED_LOCK "\x93"
#define CH_OPEN_LOCK "\x94"
#define CH_DIR_ICON "\x95"
#define CH_PARENT_DIR_ICON "\x96"
#define CH_FILE_ICON "\x97"
#define CH_EXE_FILE_ICON "\x98"
#define CH_DUMP_FILE_ICON "\x99"
#define CH_CHIP_ICON "\x9a"
#define CH_CART_ICON "\x9b"

View File

@ -108,6 +108,39 @@ void FATFile::close(void) {
f_close(&_fd);
}
/* Directory classes */
Directory::~Directory(void) {
close();
}
bool FATDirectory::getEntry(
FileInfo &output, uint32_t attrMask, uint32_t attrValue
) {
FILINFO info;
auto error = f_readdir(&_fd, &info);
if (error) {
LOG("%s", util::getErrorString(error));
return false;
}
if (!info.fname[0])
return false;
__builtin_strncpy(
output.name, info.fname,
util::min(sizeof(output.name), sizeof(info.fname))
);
output.length = info.fsize;
output.attributes = info.fattrib;
return true;
}
void FATDirectory::close(void) {
f_closedir(&_fd);
}
/* File and asset provider classes */
uint32_t currentSPUOffset = spu::DUMMY_BLOCK_END;
@ -116,29 +149,22 @@ Provider::~Provider(void) {
close();
}
bool Provider::fileExists(const char *path) {
auto file = openFile(path, READ);
if (!file)
return false;
file->close();
return true;
}
size_t Provider::loadData(util::Data &output, const char *path) {
auto file = openFile(path, READ);
if (!file)
return 0;
//assert(file.length <= SIZE_MAX);
if (!output.allocate(size_t(file->length)))
//assert(file->length <= SIZE_MAX);
if (!output.allocate(size_t(file->length))) {
delete file;
return 0;
}
size_t actualLength = file->read(output.ptr, output.length);
file->close();
delete file;
return actualLength;
}
@ -148,10 +174,11 @@ size_t Provider::loadData(void *output, size_t length, const char *path) {
if (!file)
return 0;
//assert(file.length >= length);
//assert(file->length >= length);
size_t actualLength = file->read(output, length);
file->close();
delete file;
return actualLength;
}
@ -164,6 +191,7 @@ size_t Provider::saveData(const void *input, size_t length, const char *path) {
size_t actualLength = file->write(input, length);
file->close();
delete file;
return actualLength;
}
@ -231,7 +259,7 @@ bool HostProvider::init(void) {
}
bool HostProvider::createDirectory(const char *path) {
int fd = pcdrvCreate(path, PCDRV_ATTR_DIRECTORY);
int fd = pcdrvCreate(path, DIRECTORY);
if (fd < 0) {
LOG("PCDRV error, code=%d", fd);
@ -296,11 +324,42 @@ bool FATProvider::_selectDrive(void) {
return !f_chdrive(_drive);
}
bool FATProvider::fileExists(const char *path) {
bool FATProvider::getFileInfo(FileInfo &output, const char *path) {
if (!_selectDrive())
return false;
return !f_stat(path, nullptr);
FILINFO info;
auto error = f_stat(path, &info);
if (error) {
LOG("%s, drive=%s", util::getErrorString(error), _drive);
return false;
}
__builtin_strncpy(
output.name, info.fname,
util::min(sizeof(output.name), sizeof(info.fname))
);
output.length = info.fsize;
output.attributes = info.fattrib;
return true;
}
Directory *FATProvider::openDirectory(const char *path) {
if (!_selectDrive())
return nullptr;
auto dir = new FATDirectory();
auto error = f_opendir(&(dir->_fd), path);
if (error) {
LOG("%s, drive=%s", util::getErrorString(error), _drive);
delete dir;
return nullptr;
}
return dir;
}
bool FATProvider::createDirectory(const char *path) {
@ -384,12 +443,35 @@ bool ZIPProvider::init(const void *zipData, size_t length) {
void ZIPProvider::close(void) {
mz_zip_reader_end(&_zip);
if (_file)
if (_file) {
_file->close();
delete _file;
}
}
bool ZIPProvider::fileExists(const char *path) {
return (mz_zip_reader_locate_file(&_zip, path, nullptr, 0) >= 0);
bool ZIPProvider::getFileInfo(FileInfo &output, const char *path) {
mz_zip_archive_file_stat info;
int index = mz_zip_reader_locate_file(&_zip, path, nullptr, 0);
if (index < 0)
return false;
if (!mz_zip_reader_file_stat(&_zip, index, &info))
return false;
if (!info.m_is_supported)
return false;
__builtin_strncpy(
output.name, info.m_filename,
util::min(sizeof(output.name), sizeof(info.m_filename))
);
output.length = info.m_uncomp_size;
output.attributes = READ_ONLY | ARCHIVE;
if (info.m_is_directory)
output.attributes |= DIRECTORY;
return true;
}
size_t ZIPProvider::loadData(util::Data &output, const char *path) {

View File

@ -3,6 +3,7 @@
#include <stddef.h>
#include <stdint.h>
#include "file.hpp"
#include "vendor/ff.h"
#include "vendor/miniz.h"
#include "gpu.hpp"
@ -13,6 +14,8 @@ namespace file {
/* File classes */
static constexpr size_t MAX_PATH_LENGTH = 256;
// These are functionally equivalent to the FA_* flags used by FatFs.
enum FileModeFlag {
READ = 1 << 0,
@ -21,6 +24,23 @@ enum FileModeFlag {
ALLOW_CREATE = 1 << 4 // Create file if missing
};
// These are equivalent to the standard MS-DOS file attributes (as well as PCDRV
// attributes and the AM_* flags used by FatFs).
enum FileAttributeFlag {
READ_ONLY = 1 << 0,
HIDDEN = 1 << 1,
SYSTEM = 1 << 2,
DIRECTORY = 1 << 4,
ARCHIVE = 1 << 5
};
struct FileInfo {
public:
char name[MAX_PATH_LENGTH];
uint64_t length;
uint32_t attributes;
};
class File {
public:
uint64_t length;
@ -62,8 +82,32 @@ public:
void close(void);
};
/* Directory classes */
class Directory {
public:
virtual ~Directory(void);
virtual bool getEntry(
FileInfo &output, uint32_t attrMask, uint32_t attrValue
) { return false; }
virtual void close(void) {}
};
class FATDirectory : public Directory {
friend class FATProvider;
private:
DIR _fd;
public:
bool getEntry(FileInfo &output, uint32_t attrMask, uint32_t attrValue);
void close(void);
};
/* File and asset provider classes */
// TODO: move this (and loadTIM/loadVAG) somewhere else
extern uint32_t currentSPUOffset;
class Provider {
@ -79,7 +123,8 @@ public:
virtual void close(void) {}
virtual bool fileExists(const char *path);
virtual bool getFileInfo(FileInfo &output, const char *path) { return false; }
virtual Directory *openDirectory(const char *path) { return nullptr; }
virtual bool createDirectory(const char *path) { return false; }
virtual File *openFile(const char *path, uint32_t flags) { return nullptr; }
@ -102,7 +147,7 @@ public:
class FATProvider : public Provider {
private:
FATFS _fs;
char _drive[8];
char _drive[8];
bool _selectDrive(void);
@ -114,7 +159,8 @@ public:
bool init(const char *drive);
void close(void);
bool fileExists(const char *path);
bool getFileInfo(FileInfo &output, const char *path);
Directory *openDirectory(const char *path);
bool createDirectory(const char *path);
File *openFile(const char *path, uint32_t flags);
@ -131,7 +177,7 @@ public:
bool init(const void *zipData, size_t length);
void close(void);
bool fileExists(const char *path);
bool getFileInfo(FileInfo &output, const char *path);
size_t loadData(util::Data &output, const char *path);
size_t loadData(void *output, size_t length, const char *path);

View File

@ -69,6 +69,8 @@ uint32_t getRTCTime(void) {
int year = SYS573_RTC_YEAR, month = SYS573_RTC_MONTH, day = SYS573_RTC_DAY;
int hour = SYS573_RTC_HOUR, min = SYS573_RTC_MINUTE, sec = SYS573_RTC_SECOND;
SYS573_RTC_CTRL &= ~SYS573_RTC_CTRL_READ;
year = (year & 15) + 10 * ((year >> 4) & 15); // 0-99
month = (month & 15) + 10 * ((month >> 4) & 1); // 1-12
day = (day & 15) + 10 * ((day >> 4) & 3); // 1-31
@ -89,6 +91,12 @@ uint32_t getRTCTime(void) {
| (sec >> 1);
}
bool isRTCBatteryLow(void) {
SYS573_RTC_DAY |= SYS573_RTC_DAY_BATTERY_MONITOR;
return (SYS573_RTC_DAY / SYS573_RTC_DAY_LOW_BATTERY) & 1;
}
/* Digital I/O board driver */
static void _writeBitstreamLSB(const uint8_t *data, size_t length) {

View File

@ -1,6 +1,7 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "ps1/registers.h"
@ -158,6 +159,7 @@ static inline void setDIO1Wire(bool value) {
void init(void);
uint32_t getJAMMAInputs(void);
uint32_t getRTCTime(void);
bool isRTCBatteryLow(void);
bool loadBitstream(const uint8_t *data, size_t length);
void initKonamiBitstream(void);

View File

@ -70,7 +70,7 @@ int _start(int argc, const char **argv) {
int returnValue = main(argc, argv);
for (const Function *dtor = _initArrayStart; dtor < _initArrayEnd; dtor++)
for (const Function *dtor = _finiArrayStart; dtor < _finiArrayEnd; dtor++)
(*dtor)();
return returnValue;

View File

@ -128,8 +128,8 @@ _resourceInitDone:
io::setMiscOutput(io::MISC_SPU_ENABLE, true);
app->run();
delete app;
delete uiCtx;
delete gpuCtx;
delete app, uiCtx, gpuCtx;
uninstallExceptionHandler();
return 0;
}

View File

@ -90,22 +90,21 @@ size_t Port::exchangePacket(
uint8_t address, const uint8_t *request, uint8_t *response,
size_t reqLength, size_t maxRespLength
) const {
if (!start(address))
return 0;
size_t respLength = 0;
while (respLength < maxRespLength) {
if (reqLength) {
*(response++) = exchangeByte(*(request++));
reqLength--;
} else {
*(response++) = exchangeByte(0);
}
if (start(address)) {
while (respLength < maxRespLength) {
if (reqLength) {
*(response++) = exchangeByte(*(request++));
reqLength--;
} else {
*(response++) = exchangeByte(0);
}
respLength++;
if (!waitForInterrupt(IRQ_SIO0, _ACK_TIMEOUT))
break;
respLength++;
if (!waitForInterrupt(IRQ_SIO0, _ACK_TIMEOUT))
break;
}
}
stop();

View File

@ -496,6 +496,19 @@ typedef enum {
SYS573_RTC_CTRL_WRITE = 1 << 7
} Sys573RTCControlFlag;
typedef enum {
SYS573_RTC_SECOND_UNITS_BITMASK = 15 << 0,
SYS573_RTC_SECOND_TENS_BITMASK = 7 << 4,
SYS573_RTC_SECOND_STOP = 1 << 7
} Sys573RTCSecondFlag;
typedef enum {
SYS573_RTC_DAY_UNITS_BITMASK = 15 << 0,
SYS573_RTC_DAY_TENS_BITMASK = 3 << 4,
SYS573_RTC_DAY_LOW_BATTERY = 1 << 6,
SYS573_RTC_DAY_BATTERY_MONITOR = 1 << 7
} Sys573RTCDayFlag;
#define SYS573_RTC_CTRL _MMIO16(DEV0_BASE | 0x623ff0)
#define SYS573_RTC_SECOND _MMIO16(DEV0_BASE | 0x623ff2)
#define SYS573_RTC_MINUTE _MMIO16(DEV0_BASE | 0x623ff4)

View File

@ -28,6 +28,8 @@
/* Internal state */
static uint32_t _savedBreakpointVector[4];
static uint32_t _savedExceptionVector[4];
static VoidFunction _flushCache = 0;
static Thread _mainThread;
@ -59,6 +61,8 @@ void installExceptionHandler(void) {
// Overwrite the default breakpoint and exception handlers placed into RAM
// by the BIOS with a function that will jump to our custom handler.
__builtin_memcpy(_savedBreakpointVector, BIOS_BP_VECTOR, 16);
__builtin_memcpy(_savedExceptionVector, BIOS_EXC_VECTOR, 16);
__builtin_memcpy(BIOS_BP_VECTOR, &_exceptionVector, 16);
__builtin_memcpy(BIOS_EXC_VECTOR, &_exceptionVector, 16);
_flushCache();
@ -67,6 +71,23 @@ void installExceptionHandler(void) {
DMA_DICR = DMA_DICR_IRQ_ENABLE;
}
void uninstallExceptionHandler(void) {
// Clear all pending IRQ flags and prevent the interrupt controller from
// generating further IRQs.
IRQ_MASK = 0;
IRQ_STAT = 0;
DMA_DPCR = 0;
DMA_DICR = DMA_DICR_CH_STAT_BITMASK;
// Disable interrupts and the GTE at the COP0 side.
cop0_setSR(COP0_SR_CU0);
// Restore the original BIOS breakpoint and exception handlers.
__builtin_memcpy(BIOS_BP_VECTOR, _savedBreakpointVector, 16);
__builtin_memcpy(BIOS_EXC_VECTOR, _savedExceptionVector, 16);
_flushCache();
}
void setInterruptHandler(ArgFunction func, void *arg) {
disableInterrupts();

View File

@ -96,12 +96,18 @@ static inline void initThread(
}
/**
* @brief Sets up the exception handler, removes the BIOS from memory and
* flushes the instruction cache. Must be called only once, before *any* other
* function in this header is used.
* @brief Sets up the exception handler, disables the one provided by the BIOS
* kernel and flushes the instruction cache. Must be called only once, before
* *any* other function in this header is used.
*/
void installExceptionHandler(void);
/**
* @brief Restores the BIOS kernel's exception handler. Must be called before
* returning to the kernel or launching another executable.
*/
void uninstallExceptionHandler(void);
/**
* @brief Disables interrupts and sets the function that will be called whenever
* a future interrupt or syscall occurs. Must be called after

View File

@ -206,7 +206,7 @@ delayMicroseconds:
# Compensate for the overhead of calculating the cycle count, entering the
# loop and returning.
addiu $a0, -(6 + 1 + 2 + 3 + 2)
addiu $a0, -(6 + 1 + 2 + 4 + 2)
# Reset timer 2 to its default setting of counting system clock edges.
lui $v1, %hi(IO_BASE)
@ -218,11 +218,10 @@ delayMicroseconds:
# around rather than saturating on overflow.
li $a1, 0xff00
slt $v0, $a1, $a0
#beqz $v0, .LshortDelay
#nop
beqz $v0, .LskipLongDelay
beqz $v0, .LshortDelay
li $a2, 0xff00 + 3
.LlongDelay: # for (; cycles > 0xff00; cycles -= 0xff00)
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3))
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0
li $v0, 0
@ -234,12 +233,11 @@ delayMicroseconds:
slt $v0, $a1, $a0
bnez $v0, .LlongDelay
subu $a0, $a1
subu $a0, $a2
.LshortDelay:
# Run the last busy loop once less than 0xff00 cycles are remaining.
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0
.LskipLongDelay:
li $v0, 0
.LshortDelayLoop: # while (TIMER2_VALUE < cycles);

213
src/rom.cpp Normal file
View File

@ -0,0 +1,213 @@
#include "ps1/registers.h"
#include "io.hpp"
#include "rom.hpp"
#include "util.hpp"
namespace rom {
/* ROM region dumpers */
// TODO: implement bounds checks in these
static constexpr size_t _FLASH_BANK_LENGTH = 0x400000;
static constexpr uint32_t _FLASH_CRC_OFFSET = 0x20;
static constexpr uint32_t _FLASH_EXE_OFFSET = 0x24;
void Region::read(void *data, uint32_t offset, size_t length) const {
auto source = reinterpret_cast<const uint32_t *>(ptr + offset);
auto dest = reinterpret_cast<uint32_t *>(data);
util::assertAligned<uint32_t>(source);
util::assertAligned<uint32_t>(dest);
for (; length; length -= 4)
*(dest++) = *(source++);
}
uint32_t Region::zipCRC32(uint32_t offset, size_t length, uint32_t crc) const {
auto source = reinterpret_cast<const uint32_t *>(ptr + offset);
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
crc = ~crc;
util::assertAligned<uint32_t>(source);
for (; length; length -= 4) {
uint32_t data = *(source++);
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 8;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 8;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 8;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
}
return ~crc;
}
void RTCRegion::read(void *data, uint32_t offset, size_t length) const {
auto source = reinterpret_cast<const uint16_t *>(ptr + offset * 2);
auto dest = reinterpret_cast<uint8_t *>(data);
// The RTC is an 8-bit device connected to a 16-bit bus, i.e. each byte must
// be read as a 16-bit value and then the upper 8 bits must be discarded.
for (; length; length--)
*(dest++) = *(source++) & 0xff;
}
uint32_t RTCRegion::zipCRC32(
uint32_t offset, size_t length, uint32_t crc
) const {
auto source = reinterpret_cast<const uint32_t *>(ptr + offset);
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
crc = ~crc;
util::assertAligned<uint32_t>(source);
for (; length; length -= 2) {
uint32_t data = *(source++);
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 16;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
}
return ~crc;
}
bool FlashRegion::isPresent(void) const {
if (!inputs)
return true;
if (io::getJAMMAInputs() & inputs)
return true;
return false;
}
void FlashRegion::read(void *data, uint32_t offset, size_t length) const {
// The internal flash and PCMCIA cards can only be accessed 4 MB at a time.
// FIXME: this implementation will not handle reads that cross bank
// boundaries properly
int bankOffset = offset / _FLASH_BANK_LENGTH;
int ptrOffset = offset % _FLASH_BANK_LENGTH;
auto source = reinterpret_cast<const uint32_t *>(ptr + ptrOffset);
auto dest = reinterpret_cast<uint32_t *>(data);
util::assertAligned<uint32_t>(source);
util::assertAligned<uint32_t>(dest);
io::setFlashBank(bank + bankOffset);
for (; length; length -= 4)
*(dest++) = *(source++);
}
uint32_t FlashRegion::zipCRC32(
uint32_t offset, size_t length, uint32_t crc
) const {
int bankOffset = offset / _FLASH_BANK_LENGTH;
int ptrOffset = offset % _FLASH_BANK_LENGTH;
auto source = reinterpret_cast<const uint32_t *>(ptr + ptrOffset);
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
crc = ~crc;
util::assertAligned<uint32_t>(source);
io::setFlashBank(bank + bankOffset);
for (; length; length -= 4) {
uint32_t data = *(source++);
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 8;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 8;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
data >>= 8;
crc = (crc >> 8) ^ table[(crc ^ data) & 0xff];
}
return ~crc;
}
bool FlashRegion::hasBootExecutable(void) const {
auto data = reinterpret_cast<const uint8_t *>(ptr + _FLASH_EXE_OFFSET);
auto crcPtr = reinterpret_cast<const uint32_t *>(ptr + _FLASH_CRC_OFFSET);
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
io::setFlashBank(bank);
auto &header = *reinterpret_cast<const util::ExecutableHeader *>(data);
if (!header.validateMagic())
return false;
// The integrity of the executable is verified by calculating the CRC32 of
// its bytes whose offsets are powers of 2 (i.e. the bytes at indices 0, 1,
// 2, 4, 8 and so on).
size_t length = header.textLength + 2048;
uint32_t crc = ~0;
crc = (crc >> 8) ^ table[(crc ^ *data) & 0xff];
for (size_t i = 1; i < length; i <<= 1)
crc = (crc >> 8) ^ table[(crc ^ data[i]) & 0xff];
return (~crc == *crcPtr);
}
const BIOSRegion bios;
const RTCRegion rtc;
const FlashRegion flash(SYS573_BANK_FLASH, 0x1000000);
const FlashRegion pcmcia[2]{
{ SYS573_BANK_PCMCIA1, 0x4000000, io::JAMMA_PCMCIA_CD1 },
{ SYS573_BANK_PCMCIA2, 0x4000000, io::JAMMA_PCMCIA_CD2 }
};
/* BIOS ROM headers */
static const ShellInfo _SHELL_VERSIONS[]{
{
.name = "700A01",
.bootFileName = reinterpret_cast<const char *>(DEV2_BASE | 0x40890),
.headerPtr = reinterpret_cast<const uint8_t *>(DEV2_BASE | 0x40000),
.headerHash = 0x9c615f57
}, {
.name = "700A01 (Gachagachamp)",
.bootFileName = reinterpret_cast<const char *>(DEV2_BASE | 0x40890),
.headerPtr = reinterpret_cast<const uint8_t *>(DEV2_BASE | 0x40000),
.headerHash = 0x7e31a844
}, {
.name = "700B01",
.bootFileName = reinterpret_cast<const char *>(DEV2_BASE | 0x61334),
.headerPtr = reinterpret_cast<const uint8_t *>(DEV2_BASE | 0x28000),
.headerHash = 0xb257d3b5
}
};
bool SonyKernelHeader::validateMagic(void) const {
return (
util::hash(magic, sizeof(magic)) == "Sony Computer Entertainment Inc."_h
);
}
bool OpenBIOSHeader::validateMagic(void) const {
return (util::hash(magic, sizeof(magic)) == "OpenBIOS"_h);
}
bool ShellInfo::validateHash(void) const {
return (util::hash(headerPtr, sizeof(util::ExecutableHeader)) == headerHash);
}
const ShellInfo *getShellInfo(void) {
for (auto &shell : _SHELL_VERSIONS) {
if (shell.validateHash())
return &shell;
}
return nullptr;
}
}

103
src/rom.hpp Normal file
View File

@ -0,0 +1,103 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "ps1/registers.h"
#include "util.hpp"
namespace rom {
/* ROM region dumpers */
class Region {
public:
uintptr_t ptr;
size_t regionLength;
inline Region(uintptr_t ptr, size_t regionLength)
: ptr(ptr), regionLength(regionLength) {}
virtual bool isPresent(void) const { return true; }
virtual void read(void *data, uint32_t offset, size_t length) const;
virtual uint32_t zipCRC32(
uint32_t offset, size_t length, uint32_t crc = 0
) const;
};
class BIOSRegion : public Region {
public:
inline BIOSRegion(void)
: Region(DEV2_BASE, 0x80000) {}
};
class RTCRegion : public Region {
public:
inline RTCRegion(void)
: Region(DEV0_BASE | 0x620000, 0x1ff8) {}
void read(void *data, uint32_t offset, size_t length) const;
uint32_t zipCRC32(uint32_t offset, size_t length, uint32_t crc = 0) const;
};
class FlashRegion : public Region {
public:
int bank;
uint32_t inputs;
inline FlashRegion(int bank, size_t regionLength, uint32_t inputs = 0)
: Region(DEV0_BASE, regionLength), bank(bank), inputs(inputs) {}
bool isPresent(void) const;
void read(void *data, uint32_t offset, size_t length) const;
uint32_t zipCRC32(uint32_t offset, size_t length, uint32_t crc = 0) const;
bool hasBootExecutable(void) const;
};
extern const BIOSRegion bios;
extern const RTCRegion rtc;
extern const FlashRegion flash, pcmcia[2];
/* BIOS ROM headers */
struct [[gnu::packed]] SonyKernelHeader {
public:
uint8_t day, month;
uint16_t year;
uint32_t flags;
uint8_t magic[32], _pad[4], version[36];
bool validateMagic(void) const;
};
struct [[gnu::packed]] OpenBIOSHeader {
public:
uint8_t magic[8];
uint32_t idNameLength, idDescLength, idType;
uint8_t idData[24];
inline size_t getBuildID(char *output) const {
return util::hexToString(output, &idData[idNameLength], idDescLength);
}
bool validateMagic(void) const;
};
struct ShellInfo {
public:
const char *name, *bootFileName;
const uint8_t *headerPtr;
util::Hash headerHash;
bool validateHash(void) const;
};
static const auto &sonyKernelHeader =
*reinterpret_cast<const SonyKernelHeader *>(DEV2_BASE | 0x100);
static const auto &openBIOSHeader =
*reinterpret_cast<const OpenBIOSHeader *>(DEV2_BASE | 0x78);
const ShellInfo *getShellInfo(void);
}

View File

@ -113,12 +113,8 @@ uint32_t zipCRC32(const uint8_t *data, size_t length, uint32_t crc) {
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
crc = ~crc;
for (; length; length--) {
uint32_t temp = crc;
crc >>= 8;
crc ^= table[(temp ^ *(data++)) & 0xff];
}
for (; length; length--)
crc = (crc >> 8) ^ table[(crc ^ *(data++)) & 0xff];
return ~crc;
}
@ -223,6 +219,12 @@ size_t encodeBase41(char *output, const uint8_t *input, size_t length) {
return outLength;
}
/* PS1 executable header */
bool ExecutableHeader::validateMagic(void) const {
return (hash(magic, sizeof(magic)) == "PS-X EXE"_h);
}
/* Error strings */
const char *const CART_DRIVER_ERROR_NAMES[]{

View File

@ -246,6 +246,22 @@ public:
extern Logger logger;
/* PS1 executable header */
struct [[gnu::packed]] ExecutableHeader {
public:
uint8_t magic[8], _pad[8];
uint32_t entryPoint, initialGP;
uint32_t textOffset, textLength;
uint32_t dataOffset, dataLength;
uint32_t bssOffset, bssLength;
uint32_t stackOffset, stackLength;
uint32_t _reserved[5];
bool validateMagic(void) const;
};
/* Other APIs */
uint8_t dsCRC8(const uint8_t *data, size_t length);

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__version__ = "0.3.4"
__version__ = "0.3.5"
__author__ = "spicyjpeg"
import json, re
@ -211,17 +211,22 @@ TABLE_STRING_ALIGN: int = 4
TABLE_ESCAPE_REGEX: re.Pattern = re.compile(rb"\$?\{(.+?)\}")
TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = {
b"UP_ARROW": b"\x80",
b"DOWN_ARROW": b"\x81",
b"LEFT_ARROW": b"\x82",
b"RIGHT_ARROW": b"\x83",
b"LEFT_BUTTON": b"\x84",
b"RIGHT_BUTTON": b"\x85",
b"START_BUTTON": b"\x86",
b"CLOSED_LOCK": b"\x87",
b"OPEN_LOCK": b"\x88",
b"CHIP_ICON": b"\x89",
b"CART_ICON": b"\x8a"
b"UP_ARROW": b"\x80",
b"DOWN_ARROW": b"\x81",
b"LEFT_ARROW": b"\x82",
b"RIGHT_ARROW": b"\x83",
b"LEFT_BUTTON": b"\x90",
b"RIGHT_BUTTON": b"\x91",
b"START_BUTTON": b"\x92",
b"CLOSED_LOCK": b"\x93",
b"OPEN_LOCK": b"\x94",
b"DIR_ICON": b"\x95",
b"PARENT_DIR_ICON": b"\x96",
b"FILE_ICON": b"\x97",
b"EXE_FILE_ICON": b"\x98",
b"DUMP_FILE_ICON": b"\x99",
b"CHIP_ICON": b"\x9a",
b"CART_ICON": b"\x9b"
}
def hashString(string: str) -> int: