Rearrange app class structure and clean up main()

This commit is contained in:
spicyjpeg 2023-09-19 21:25:22 +02:00
parent 9cf2908921
commit d0767b962d
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
8 changed files with 692 additions and 692 deletions

View File

@ -43,6 +43,7 @@ add_executable(
src/util.cpp
src/zs01.cpp
src/app/app.cpp
src/app/appworkers.cpp
src/app/cartactions.cpp
src/app/cartunlock.cpp
src/app/main.cpp

View File

@ -5,6 +5,11 @@
},
"App": {
"startupWorker": {
"initIDE": "Initializing IDE devices...",
"initFAT": "Attempting to mount FAT filesystem...",
"loadResources": "Loading resource pack..."
},
"cartDetectWorker": {
"readDigitalIO": "Retrieving digital I/O board ID...",
"readCart": "Reading security cartridge...",

View File

@ -1,20 +1,74 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "app/app.hpp"
#include "ps1/system.h"
#include "cart.hpp"
#include "cartdata.hpp"
#include "cartio.hpp"
#include "defs.hpp"
#include "file.hpp"
#include "io.hpp"
#include "uibase.hpp"
#include "util.hpp"
#include "utilerror.hpp"
/* Worker status class */
void WorkerStatus::reset(void) {
status = WORKER_IDLE;
progress = 0;
progressTotal = 1;
message = nullptr;
nextScreen = nullptr;
}
void WorkerStatus::update(int part, int total, const char *text) {
auto enable = disableInterrupts();
status = WORKER_BUSY;
progress = part;
progressTotal = total;
if (text)
message = text;
if (enable)
enableInterrupts();
}
void WorkerStatus::setStatus(WorkerStatusType value) {
auto enable = disableInterrupts();
status = value;
if (enable)
enableInterrupts();
}
void WorkerStatus::setNextScreen(ui::Screen &next, bool goBack) {
auto enable = disableInterrupts();
_nextGoBack = goBack;
_nextScreen = &next;
if (enable)
enableInterrupts();
}
void WorkerStatus::finish(void) {
auto enable = disableInterrupts();
status = _nextGoBack ? WORKER_NEXT_BACK : WORKER_NEXT;
nextScreen = _nextScreen;
if (enable)
enableInterrupts();
}
/* App class */
App::~App(void) {
_unloadCartData();
delete[] _workerStack;
if (_resourceFile) {
//_resourceFile->close();
delete _resourceFile;
}
}
void App::_unloadCartData(void) {
if (_driver) {
delete _driver;
@ -60,524 +114,26 @@ void App::_setupInterrupts(void) {
enableInterrupts();
}
/* Worker functions */
static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
nullptr,
"data/x76f041.cartdb",
"data/x76f100.cartdb",
"data/zs01.cartdb"
static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
"assets/sounds/startup.vag", // ui::SOUND_STARTUP
"assets/sounds/error.vag", // ui::SOUND_ERROR
"assets/sounds/move.vag", // ui::SOUND_MOVE
"assets/sounds/enter.vag", // ui::SOUND_ENTER
"assets/sounds/exit.vag", // ui::SOUND_EXIT
"assets/sounds/click.vag" // ui::SOUND_CLICK
};
bool App::_cartDetectWorker(void) {
_workerStatus.setNextScreen(_cartInfoScreen);
_workerStatus.update(0, 3, WSTR("App.cartDetectWorker.readCart"));
_unloadCartData();
void App::_loadResources(void) {
_resourceProvider.loadTIM(_backgroundLayer.tile, "assets/textures/background.tim");
_resourceProvider.loadTIM(_ctx.font.image, "assets/textures/font.tim");
_resourceProvider.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics");
_resourceProvider.loadStruct(_ctx.colors, "assets/app.palette");
_resourceProvider.loadData(_stringTable, "assets/app.strings");
#ifdef ENABLE_DUMMY_DRIVER
if (!cart::dummyDriverDump.chipType)
_resourceProvider->loadStruct(cart::dummyDriverDump, "data/test.573");
if (cart::dummyDriverDump.chipType) {
LOG("using dummy cart driver");
_driver = new cart::DummyDriver(_dump);
_driver->readSystemID();
} else {
_driver = cart::newCartDriver(_dump);
}
#else
_driver = cart::newCartDriver(_dump);
#endif
if (_dump.chipType) {
LOG("cart dump @ 0x%08x", &_dump);
LOG("cart driver @ 0x%08x", _driver);
auto error = _driver->readCartID();
if (error)
LOG("SID error [%s]", util::getErrorString(error));
error = _driver->readPublicData();
if (error)
LOG("read error [%s]", util::getErrorString(error));
else if (!_dump.isReadableDataEmpty())
_parser = cart::newCartParser(_dump);
LOG("cart parser @ 0x%08x", _parser);
_workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
if (!_db.ptr) {
if (!_resourceProvider->loadData(
_db, _CARTDB_PATHS[_dump.chipType])
) {
LOG("%s not found", _CARTDB_PATHS[_dump.chipType]);
goto _cartInitDone;
}
}
char code[8], region[8];
if (!_parser)
goto _cartInitDone;
if (_parser->getCode(code) && _parser->getRegion(region))
_identified = _db.lookup(code, region);
if (!_identified)
goto _cartInitDone;
// Force the parser to use correct format for the game (to prevent
// ambiguity between different formats).
delete _parser;
_parser = cart::newCartParser(
_dump, _identified->formatType, _identified->flags
);
LOG("new cart parser @ 0x%08x", _parser);
}
_cartInitDone:
_workerStatus.update(2, 3, WSTR("App.cartDetectWorker.readDigitalIO"));
#ifdef ENABLE_DUMMY_DRIVER
if (io::isDigitalIOPresent() && !(_dump.flags & cart::DUMP_SYSTEM_ID_OK)) {
#else
if (io::isDigitalIOPresent()) {
#endif
util::Data bitstream;
bool ready;
if (!_resourceProvider->loadData(bitstream, "data/fpga.bit")) {
LOG("bitstream unavailable");
return true;
}
ready = io::loadBitstream(bitstream.as<uint8_t>(), bitstream.length);
bitstream.destroy();
if (!ready) {
LOG("bitstream upload failed");
return true;
}
delayMicroseconds(5000); // Probably not necessary
io::initKonamiBitstream();
auto error = _driver->readSystemID();
if (error)
LOG("XID error [%s]", util::getErrorString(error));
}
return true;
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
_resourceProvider.loadVAG(_ctx.sounds[i], _UI_SOUND_PATHS[i]);
}
bool App::_cartUnlockWorker(void) {
_workerStatus.setNextScreen(_cartInfoScreen, true);
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
auto error = _driver->readPrivateData();
if (error) {
LOG("read error [%s]", util::getErrorString(error));
/*_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartUnlockWorker.error")
);*/
_workerStatus.setNextScreen(_messageScreen);
return false;
}
if (_parser)
delete _parser;
_parser = cart::newCartParser(_dump);
if (!_parser)
return true;
LOG("cart parser @ 0x%08x", _parser);
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identifyGame"));
char code[8], region[8];
if (_parser->getCode(code) && _parser->getRegion(region))
_identified = _db.lookup(code, region);
// If auto-identification failed (e.g. because the format has no game code),
// use the game whose unlocking key was selected as a hint.
if (!_identified) {
if (_selectedEntry) {
LOG("identify failed, using key as hint");
_identified = _selectedEntry;
} else {
return true;
}
}
delete _parser;
_parser = cart::newCartParser(
_dump, _identified->formatType, _identified->flags
);
LOG("new cart parser @ 0x%08x", _parser);
return true;
}
bool App::_qrCodeWorker(void) {
char qrString[cart::MAX_QR_STRING_LENGTH];
_workerStatus.setNextScreen(_qrCodeScreen);
_workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
_dump.toQRString(qrString);
_workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
_qrCodeScreen.generateCode(qrString);
return true;
}
bool App::_cartDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartDumpWorker.save"));
char code[8], region[8], path[32];
if (_identified && _parser->getCode(code) && _parser->getRegion(region))
snprintf(path, sizeof(path), "%s%s.573", code, region);
else
__builtin_strcpy(path, "unknown.573");
size_t length = _dump.getDumpLength();
LOG("saving %s, length=%d", path, length);
if (_fileProvider->saveData(&_dump, length, path) != length) {
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartDumpWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _cartInfoScreen, WSTR("App.cartDumpWorker.success"),
path
);
_workerStatus.setNextScreen(_messageScreen);
return true;
}
bool App::_cartWriteWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
uint8_t key[8];
auto error = _driver->writeData();
if (!error)
_identified->copyKeyTo(key);
_cartDetectWorker();
if (error) {
LOG("write error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartWriteWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_dump.copyKeyFrom(key);
return _cartUnlockWorker();
}
bool App::_cartReflashWorker(void) {
if (!_cartEraseWorker())
return false;
_workerStatus.update(1, 2, WSTR("App.cartReflashWorker.flash"));
if (_parser)
delete _parser;
_parser = cart::newCartParser(
_dump, _selectedEntry->formatType, _selectedEntry->flags
);
auto pri = _parser->getIdentifiers();
auto pub = _parser->getPublicIdentifiers();
_dump.clearData();
_dump.initConfig(9, _selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION);
if (pri) {
if (_selectedEntry->flags & cart::DATA_HAS_CART_ID)
pri->cartID.copyFrom(_dump.cartID.data);
if (_selectedEntry->flags & cart::DATA_HAS_TRACE_ID)
pri->updateTraceID(
_selectedEntry->traceIDType, _selectedEntry->traceIDParam
);
if (_selectedEntry->flags & cart::DATA_HAS_INSTALL_ID) {
// The private installation ID seems to be unused on carts with a
// public data section.
if (pub)
pub->setInstallID(_selectedEntry->installIDPrefix);
else
pri->setInstallID(_selectedEntry->installIDPrefix);
}
}
_parser->setCode(_selectedEntry->code);
_parser->setRegion(_selectedEntry->region);
_parser->setYear(_selectedEntry->year);
_parser->flush();
auto error = _driver->setDataKey(_selectedEntry->dataKey);
if (error)
LOG("key error [%s]", util::getErrorString(error));
else
error = _driver->writeData();
_cartDetectWorker();
if (error) {
LOG("write error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartReflashWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
return _cartUnlockWorker();
}
bool App::_cartEraseWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
auto error = _driver->erase();
_cartDetectWorker();
if (error) {
LOG("erase error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartEraseWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
return _cartUnlockWorker();
}
struct DumpRegion {
public:
util::Hash prompt;
const char *path;
const uint16_t *ptr;
size_t length;
int bank;
uint32_t inputs;
};
enum DumpBank {
BANK_NONE_8BIT = -1,
BANK_NONE_16BIT = -2
};
static constexpr int _NUM_DUMP_REGIONS = 5;
static const DumpRegion _DUMP_REGIONS[_NUM_DUMP_REGIONS]{
{
.prompt = "App.romDumpWorker.dumpBIOS"_h,
.path = "dump_%d/bios.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV2_BASE),
.length = 0x80000,
.bank = BANK_NONE_16BIT,
.inputs = 0
}, {
.prompt = "App.romDumpWorker.dumpRTC"_h,
.path = "dump_%d/rtc.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE | 0x620000),
.length = 0x2000,
.bank = BANK_NONE_8BIT,
.inputs = 0
}, {
.prompt = "App.romDumpWorker.dumpFlash"_h,
.path = "dump_%d/flash.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x1000000,
.bank = SYS573_BANK_FLASH,
.inputs = 0
}, {
.prompt = "App.romDumpWorker.dumpPCMCIA1"_h,
.path = "dump_%d/pcmcia1.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x4000000,
.bank = SYS573_BANK_PCMCIA1,
.inputs = io::JAMMA_PCMCIA_CD1
}, {
.prompt = "App.romDumpWorker.dumpPCMCIA2"_h,
.path = "dump_%d/pcmcia2.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x4000000,
.bank = SYS573_BANK_PCMCIA2,
.inputs = io::JAMMA_PCMCIA_CD2
}
};
bool App::_romDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
// Store all dumps in a directory named "dump_N".
int index = 0;
char directoryPath[32], filePath[32];
do {
index++;
snprintf(directoryPath, sizeof(directoryPath), "dump_%d", index);
} while (_fileProvider->fileExists(directoryPath));
LOG("saving dumps to %s", directoryPath);
if (!_fileProvider->createDirectory(directoryPath)) {
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.initError")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
uint32_t inputs = io::getJAMMAInputs();
for (int i = 0; i < _NUM_DUMP_REGIONS; i++) {
auto &region = _DUMP_REGIONS[i];
// Skip PCMCIA slots if a card is not inserted.
if (region.inputs && !(inputs & region.inputs))
continue;
snprintf(filePath, sizeof(filePath), region.path, index);
auto _file = _fileProvider->openFile(
filePath, file::WRITE | file::ALLOW_CREATE
);
if (!_file)
goto _writeError;
// The buffer has to be 8 KB to match the size of RTC RAM.
uint8_t buffer[0x2000];
const uint16_t *ptr = region.ptr;
int count = region.length / sizeof(buffer);
int bank = region.bank;
if (bank >= 0)
io::setFlashBank(bank++);
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)) {
_file->close();
goto _writeError;
}
}
_file->close();
LOG("%s saved", filePath);
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _mainMenuScreen, WSTR("App.romDumpWorker.success"),
directoryPath
);
_workerStatus.setNextScreen(_messageScreen);
return true;
_writeError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.dumpError")
);
_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) {
LOG("eject error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.atapiEjectWorker.ejectError")
);
_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;
}
/* Misc. functions */
void App::_worker(void) {
if (_workerFunction) {
(this->*_workerFunction)();
@ -591,7 +147,7 @@ void App::_worker(void) {
void App::_interruptHandler(void) {
if (acknowledgeInterrupt(IRQ_VSYNC)) {
_ctx->tick();
_ctx.tick();
if (_workerStatus.status != WORKER_REBOOT)
io::clearWatchdog();
@ -600,35 +156,24 @@ void App::_interruptHandler(void) {
}
}
void App::run(
ui::Context &ctx, file::Provider &resourceProvider,
file::Provider &fileProvider, file::StringTable &stringTable
) {
void App::run(void) {
LOG("starting app @ 0x%08x", this);
_ctx = &ctx;
_resourceProvider = &resourceProvider;
_fileProvider = &fileProvider;
_stringTable = &stringTable;
ctx.screenData = this;
#ifdef NDEBUG
ctx.show(_warningScreen);
ctx.sounds[ui::SOUND_STARTUP].play();
#else
// Skip the warning screen in debug builds.
ctx.show(_buttonMappingScreen);
#endif
_setupWorker(nullptr);
_ctx.screenData = this;
_setupWorker(&App::_startupWorker);
_setupInterrupts();
_loadResources();
_backgroundLayer.text = "v" VERSION_STRING;
_ctx.setBackgroundLayer(_backgroundLayer);
_ctx.setOverlayLayer(_overlayLayer);
_ctx.show(_workerStatusScreen);
for (;;) {
ctx.update();
ctx.draw();
_ctx.update();
_ctx.draw();
switchThreadImmediate(&_workerThread);
ctx.gpuCtx.flip();
_ctx.gpuCtx.flip();
}
}

View File

@ -6,7 +6,6 @@
#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"
@ -39,47 +38,11 @@ public:
const char *volatile message;
ui::Screen *volatile nextScreen;
inline void reset(void) {
status = WORKER_IDLE;
progress = 0;
progressTotal = 1;
message = nullptr;
nextScreen = nullptr;
}
inline void update(int part, int total, const char *text = nullptr) {
auto enable = disableInterrupts();
status = WORKER_BUSY;
progress = part;
progressTotal = total;
if (text)
message = text;
if (enable)
enableInterrupts();
}
inline void setStatus(WorkerStatusType value) {
auto enable = disableInterrupts();
status = value;
if (enable)
enableInterrupts();
}
inline void setNextScreen(ui::Screen &next, bool goBack = false) {
auto enable = disableInterrupts();
_nextGoBack = goBack;
_nextScreen = &next;
if (enable)
enableInterrupts();
}
inline void finish(void) {
auto enable = disableInterrupts();
status = _nextGoBack ? WORKER_NEXT_BACK : WORKER_NEXT;
nextScreen = _nextScreen;
if (enable)
enableInterrupts();
}
void reset(void);
void update(int part, int total, const char *text = nullptr);
void setStatus(WorkerStatusType value);
void setNextScreen(ui::Screen &next, bool goBack = false);
void finish(void);
};
/* App class */
@ -120,9 +83,14 @@ private:
ReflashGameScreen _reflashGameScreen;
SystemIDEntryScreen _systemIDEntryScreen;
ui::Context *_ctx;
file::Provider *_resourceProvider, *_fileProvider;
file::StringTable *_stringTable;
ui::TiledBackground _backgroundLayer;
ui::LogOverlay _overlayLayer;
ui::Context &_ctx;
file::ZIPProvider &_resourceProvider;
file::File *_resourceFile;
file::FATProvider _fileProvider;
file::StringTable _stringTable;
cart::Dump _dump;
cart::CartDB _db;
@ -138,7 +106,9 @@ private:
void _unloadCartData(void);
void _setupWorker(bool (App::*func)(void));
void _setupInterrupts(void);
void _loadResources(void);
bool _startupWorker(void);
bool _cartDetectWorker(void);
bool _cartUnlockWorker(void);
bool _qrCodeWorker(void);
@ -154,26 +124,21 @@ private:
void _interruptHandler(void);
public:
inline App(void)
: _driver(nullptr), _parser(nullptr), _identified(nullptr) {
inline App(ui::Context &ctx, file::ZIPProvider &resourceProvider)
: _overlayLayer(util::logger), _ctx(ctx),
_resourceProvider(resourceProvider), _resourceFile(nullptr),
_driver(nullptr), _parser(nullptr), _identified(nullptr) {
_workerStack = new uint8_t[WORKER_STACK_SIZE];
}
inline ~App(void) {
_unloadCartData();
delete[] _workerStack;
}
void run(
ui::Context &ctx, file::Provider &resourceProvider,
file::Provider &fileProvider, file::StringTable &stringTable
);
~App(void);
void run(void);
};
#define APP (reinterpret_cast<App *>(ctx.screenData))
#define STR(id) (APP->_stringTable->get(id ## _h))
#define STRH(id) (APP->_stringTable->get(id))
#define STR(id) (APP->_stringTable.get(id ## _h))
#define STRH(id) (APP->_stringTable.get(id))
#define WAPP (reinterpret_cast<App *>(_ctx->screenData))
#define WSTR(id) (WAPP->_stringTable->get(id ## _h))
#define WSTRH(id) (WAPP->_stringTable->get(id))
#define WAPP (reinterpret_cast<App *>(_ctx.screenData))
#define WSTR(id) (WAPP->_stringTable.get(id ## _h))
#define WSTRH(id) (WAPP->_stringTable.get(id))

564
src/app/appworkers.cpp Normal file
View File

@ -0,0 +1,564 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "app/app.hpp"
#include "ps1/system.h"
#include "cart.hpp"
#include "cartdata.hpp"
#include "cartio.hpp"
#include "file.hpp"
#include "ide.hpp"
#include "io.hpp"
#include "uibase.hpp"
#include "util.hpp"
#include "utilerror.hpp"
static const char _RESOURCE_ZIP_PATH[]{ "cart_tool_resources.zip" };
bool App::_startupWorker(void) {
#ifdef NDEBUG
_workerStatus.setNextScreen(_warningScreen);
#else
// Skip the warning screen in debug builds.
_workerStatus.setNextScreen(_buttonMappingScreen);
#endif
_workerStatus.update(0, 3, WSTR("App.startupWorker.initIDE"));
ide::devices[0].enumerate();
ide::devices[1].enumerate();
_workerStatus.update(1, 3, 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(2, 3, WSTR("App.startupWorker.loadResources"));
_resourceFile = _fileProvider.openFile(_RESOURCE_ZIP_PATH, 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,
"data/x76f041.cartdb",
"data/x76f100.cartdb",
"data/zs01.cartdb"
};
bool App::_cartDetectWorker(void) {
_workerStatus.setNextScreen(_cartInfoScreen);
_workerStatus.update(0, 3, WSTR("App.cartDetectWorker.readCart"));
_unloadCartData();
#ifdef ENABLE_DUMMY_DRIVER
if (!cart::dummyDriverDump.chipType)
_resourceProvider.loadStruct(cart::dummyDriverDump, "data/test.573");
if (cart::dummyDriverDump.chipType) {
LOG("using dummy cart driver");
_driver = new cart::DummyDriver(_dump);
_driver->readSystemID();
} else {
_driver = cart::newCartDriver(_dump);
}
#else
_driver = cart::newCartDriver(_dump);
#endif
if (_dump.chipType) {
LOG("cart dump @ 0x%08x", &_dump);
LOG("cart driver @ 0x%08x", _driver);
auto error = _driver->readCartID();
if (error)
LOG("SID error [%s]", util::getErrorString(error));
error = _driver->readPublicData();
if (error)
LOG("read error [%s]", util::getErrorString(error));
else if (!_dump.isReadableDataEmpty())
_parser = cart::newCartParser(_dump);
LOG("cart parser @ 0x%08x", _parser);
_workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
if (!_db.ptr) {
if (!_resourceProvider.loadData(
_db, _CARTDB_PATHS[_dump.chipType])
) {
LOG("%s not found", _CARTDB_PATHS[_dump.chipType]);
goto _cartInitDone;
}
}
char code[8], region[8];
if (!_parser)
goto _cartInitDone;
if (_parser->getCode(code) && _parser->getRegion(region))
_identified = _db.lookup(code, region);
if (!_identified)
goto _cartInitDone;
// Force the parser to use correct format for the game (to prevent
// ambiguity between different formats).
delete _parser;
_parser = cart::newCartParser(
_dump, _identified->formatType, _identified->flags
);
LOG("new cart parser @ 0x%08x", _parser);
}
_cartInitDone:
_workerStatus.update(2, 3, WSTR("App.cartDetectWorker.readDigitalIO"));
#ifdef ENABLE_DUMMY_DRIVER
if (io::isDigitalIOPresent() && !(_dump.flags & cart::DUMP_SYSTEM_ID_OK)) {
#else
if (io::isDigitalIOPresent()) {
#endif
util::Data bitstream;
bool ready;
if (!_resourceProvider.loadData(bitstream, "data/fpga.bit")) {
LOG("bitstream unavailable");
return true;
}
ready = io::loadBitstream(bitstream.as<uint8_t>(), bitstream.length);
bitstream.destroy();
if (!ready) {
LOG("bitstream upload failed");
return true;
}
delayMicroseconds(5000); // Probably not necessary
io::initKonamiBitstream();
auto error = _driver->readSystemID();
if (error)
LOG("XID error [%s]", util::getErrorString(error));
}
return true;
}
bool App::_cartUnlockWorker(void) {
_workerStatus.setNextScreen(_cartInfoScreen, true);
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
auto error = _driver->readPrivateData();
if (error) {
LOG("read error [%s]", util::getErrorString(error));
/*_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartUnlockWorker.error")
);*/
_workerStatus.setNextScreen(_messageScreen);
return false;
}
if (_parser)
delete _parser;
_parser = cart::newCartParser(_dump);
if (!_parser)
return true;
LOG("cart parser @ 0x%08x", _parser);
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identifyGame"));
char code[8], region[8];
if (_parser->getCode(code) && _parser->getRegion(region))
_identified = _db.lookup(code, region);
// If auto-identification failed (e.g. because the format has no game code),
// use the game whose unlocking key was selected as a hint.
if (!_identified) {
if (_selectedEntry) {
LOG("identify failed, using key as hint");
_identified = _selectedEntry;
} else {
return true;
}
}
delete _parser;
_parser = cart::newCartParser(
_dump, _identified->formatType, _identified->flags
);
LOG("new cart parser @ 0x%08x", _parser);
return true;
}
bool App::_qrCodeWorker(void) {
char qrString[cart::MAX_QR_STRING_LENGTH];
_workerStatus.setNextScreen(_qrCodeScreen);
_workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
_dump.toQRString(qrString);
_workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
_qrCodeScreen.generateCode(qrString);
return true;
}
bool App::_cartDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartDumpWorker.save"));
char code[8], region[8], path[32];
if (_identified && _parser->getCode(code) && _parser->getRegion(region))
snprintf(path, sizeof(path), "%s%s.573", code, region);
else
__builtin_strcpy(path, "unknown.573");
size_t length = _dump.getDumpLength();
LOG("saving %s, length=%d", path, length);
if (_fileProvider.saveData(&_dump, length, path) != length) {
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartDumpWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _cartInfoScreen, WSTR("App.cartDumpWorker.success"),
path
);
_workerStatus.setNextScreen(_messageScreen);
return true;
}
bool App::_cartWriteWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
uint8_t key[8];
auto error = _driver->writeData();
if (!error)
_identified->copyKeyTo(key);
_cartDetectWorker();
if (error) {
LOG("write error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartWriteWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_dump.copyKeyFrom(key);
return _cartUnlockWorker();
}
bool App::_cartReflashWorker(void) {
if (!_cartEraseWorker())
return false;
_workerStatus.update(1, 2, WSTR("App.cartReflashWorker.flash"));
if (_parser)
delete _parser;
_parser = cart::newCartParser(
_dump, _selectedEntry->formatType, _selectedEntry->flags
);
auto pri = _parser->getIdentifiers();
auto pub = _parser->getPublicIdentifiers();
_dump.clearData();
_dump.initConfig(9, _selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION);
if (pri) {
if (_selectedEntry->flags & cart::DATA_HAS_CART_ID)
pri->cartID.copyFrom(_dump.cartID.data);
if (_selectedEntry->flags & cart::DATA_HAS_TRACE_ID)
pri->updateTraceID(
_selectedEntry->traceIDType, _selectedEntry->traceIDParam
);
if (_selectedEntry->flags & cart::DATA_HAS_INSTALL_ID) {
// The private installation ID seems to be unused on carts with a
// public data section.
if (pub)
pub->setInstallID(_selectedEntry->installIDPrefix);
else
pri->setInstallID(_selectedEntry->installIDPrefix);
}
}
_parser->setCode(_selectedEntry->code);
_parser->setRegion(_selectedEntry->region);
_parser->setYear(_selectedEntry->year);
_parser->flush();
auto error = _driver->setDataKey(_selectedEntry->dataKey);
if (error)
LOG("key error [%s]", util::getErrorString(error));
else
error = _driver->writeData();
_cartDetectWorker();
if (error) {
LOG("write error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartReflashWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
return _cartUnlockWorker();
}
bool App::_cartEraseWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
auto error = _driver->erase();
_cartDetectWorker();
if (error) {
LOG("erase error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _cartInfoScreen, WSTR("App.cartEraseWorker.error")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
return _cartUnlockWorker();
}
struct DumpRegion {
public:
util::Hash prompt;
const char *path;
const uint16_t *ptr;
size_t length;
int bank;
uint32_t inputs;
};
enum DumpBank {
BANK_NONE_8BIT = -1,
BANK_NONE_16BIT = -2
};
static constexpr int _NUM_DUMP_REGIONS = 5;
static const DumpRegion _DUMP_REGIONS[_NUM_DUMP_REGIONS]{
{
.prompt = "App.romDumpWorker.dumpBIOS"_h,
.path = "dump_%d/bios.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV2_BASE),
.length = 0x80000,
.bank = BANK_NONE_16BIT,
.inputs = 0
}, {
.prompt = "App.romDumpWorker.dumpRTC"_h,
.path = "dump_%d/rtc.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE | 0x620000),
.length = 0x2000,
.bank = BANK_NONE_8BIT,
.inputs = 0
}, {
.prompt = "App.romDumpWorker.dumpFlash"_h,
.path = "dump_%d/flash.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x1000000,
.bank = SYS573_BANK_FLASH,
.inputs = 0
}, {
.prompt = "App.romDumpWorker.dumpPCMCIA1"_h,
.path = "dump_%d/pcmcia1.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x4000000,
.bank = SYS573_BANK_PCMCIA1,
.inputs = io::JAMMA_PCMCIA_CD1
}, {
.prompt = "App.romDumpWorker.dumpPCMCIA2"_h,
.path = "dump_%d/pcmcia2.bin",
.ptr = reinterpret_cast<const uint16_t *>(DEV0_BASE),
.length = 0x4000000,
.bank = SYS573_BANK_PCMCIA2,
.inputs = io::JAMMA_PCMCIA_CD2
}
};
bool App::_romDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
// Store all dumps in a directory named "dump_N".
int index = 0;
char directoryPath[32], filePath[32];
do {
index++;
snprintf(directoryPath, sizeof(directoryPath), "dump_%d", index);
} while (_fileProvider.fileExists(directoryPath));
LOG("saving dumps to %s", directoryPath);
if (!_fileProvider.createDirectory(directoryPath)) {
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.initError")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
uint32_t inputs = io::getJAMMAInputs();
for (int i = 0; i < _NUM_DUMP_REGIONS; i++) {
auto &region = _DUMP_REGIONS[i];
// Skip PCMCIA slots if a card is not inserted.
if (region.inputs && !(inputs & region.inputs))
continue;
snprintf(filePath, sizeof(filePath), region.path, index);
auto _file = _fileProvider.openFile(
filePath, file::WRITE | file::ALLOW_CREATE
);
if (!_file)
goto _writeError;
// The buffer has to be 8 KB to match the size of RTC RAM.
uint8_t buffer[0x2000];
const uint16_t *ptr = region.ptr;
int count = region.length / sizeof(buffer);
int bank = region.bank;
if (bank >= 0)
io::setFlashBank(bank++);
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)) {
_file->close();
goto _writeError;
}
}
_file->close();
LOG("%s saved", filePath);
}
_messageScreen.setMessage(
MESSAGE_SUCCESS, _mainMenuScreen, WSTR("App.romDumpWorker.success"),
directoryPath
);
_workerStatus.setNextScreen(_messageScreen);
return true;
_writeError:
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen, WSTR("App.romDumpWorker.dumpError")
);
_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) {
LOG("eject error [%s]", util::getErrorString(error));
_messageScreen.setMessage(
MESSAGE_ERROR, _mainMenuScreen,
WSTR("App.atapiEjectWorker.ejectError")
);
_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

@ -195,7 +195,7 @@ void AboutScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("AboutScreen.title");
_prompt = STR("AboutScreen.prompt");
APP->_resourceProvider->loadData(_text, "assets/about.txt");
APP->_resourceProvider.loadData(_text, "assets/about.txt");
auto ptr = reinterpret_cast<char *>(_text.ptr);
_body = ptr;

View File

@ -10,10 +10,12 @@
extern "C" DSTATUS disk_initialize(uint8_t drive) {
auto &dev = ide::devices[drive];
#if 0
if (!(dev.flags & ide::DEVICE_READY)) {
if (dev.enumerate())
return RES_NOTRDY;
}
#endif
return disk_status(drive);
}

View File

@ -7,7 +7,6 @@
#include "defs.hpp"
#include "file.hpp"
#include "gpu.hpp"
#include "ide.hpp"
#include "io.hpp"
#include "spu.hpp"
#include "uibase.hpp"
@ -16,29 +15,15 @@
extern "C" const uint8_t _resources[];
extern "C" const size_t _resourcesSize;
static const char _DEFAULT_RESOURCE_ZIP_PATH[]{ "cart_tool_resources.zip" };
static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
"assets/sounds/startup.vag", // ui::SOUND_STARTUP
"assets/sounds/error.vag", // ui::SOUND_ERROR
"assets/sounds/move.vag", // ui::SOUND_MOVE
"assets/sounds/enter.vag", // ui::SOUND_ENTER
"assets/sounds/exit.vag", // ui::SOUND_EXIT
"assets/sounds/click.vag" // ui::SOUND_CLICK
};
class Settings {
public:
int width, height;
int baudRate;
char resPath[64];
const void *resPtr;
size_t resLength;
inline Settings(void)
: width(320), height(240), baudRate(0), resPtr(nullptr), resLength(0) {
__builtin_strncpy(resPath, _DEFAULT_RESOURCE_ZIP_PATH, sizeof(resPath));
}
: width(320), height(240), baudRate(0), resPtr(nullptr), resLength(0) {}
bool parse(const char *arg);
};
@ -68,12 +53,8 @@ bool Settings::parse(const char *arg) {
height = int(strtol(&arg[14], nullptr, 0));
return true;
// Allow the default assets to be overridden by passing a path or a
// pointer to an in-memory ZIP file as a command-line argument.
case "resources.path"_h:
__builtin_strncpy(resPath, &arg[15], sizeof(resPath));
return true;
// Allow the default assets to be overridden by passing a pointer to an
// in-memory ZIP file as a command-line argument.
case "resources.ptr"_h:
resPtr = reinterpret_cast<const void *>(
strtol(&arg[14], nullptr, 16)
@ -115,40 +96,16 @@ int main(int argc, const char **argv) {
LOG("build " VERSION_STRING " (" __DATE__ " " __TIME__ ")");
LOG("(C) 2022-2023 spicyjpeg");
io::clearWatchdog();
ide::devices[0].enumerate();
ide::devices[1].enumerate();
io::clearWatchdog();
file::FATProvider fileProvider;
// 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:");
io::clearWatchdog();
// Load the resource archive, first from memory if a pointer was given and
// then from the HDD. If both attempts fail, fall back to the archive
// embedded into the executable.
file::ZIPProvider resourceProvider;
file::File *zipFile;
if (settings.resPtr && settings.resLength) {
if (resourceProvider.init(settings.resPtr, settings.resLength))
goto _resourceInitDone;
}
zipFile = fileProvider.openFile(settings.resPath, file::READ);
if (zipFile) {
if (resourceProvider.init(zipFile))
goto _resourceInitDone;
}
resourceProvider.init(_resources, _resourcesSize);
_resourceInitDone:
@ -156,51 +113,12 @@ _resourceInitDone:
gpu::Context gpuCtx(GP1_MODE_NTSC, settings.width, settings.height);
ui::Context uiCtx(gpuCtx);
ui::TiledBackground background;
ui::LogOverlay overlay(util::logger);
file::StringTable strings;
if (
!resourceProvider.loadTIM(
background.tile, "assets/textures/background.tim"
) ||
!resourceProvider.loadTIM(
uiCtx.font.image, "assets/textures/font.tim"
) ||
!resourceProvider.loadStruct(
uiCtx.font.metrics, "assets/textures/font.metrics"
) ||
!resourceProvider.loadStruct(
uiCtx.colors, "assets/app.palette"
) ||
!resourceProvider.loadData(
strings, "assets/app.strings"
)
) {
LOG("required assets not found, exiting");
return 1;
}
io::clearWatchdog();
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
resourceProvider.loadVAG(uiCtx.sounds[i], _UI_SOUND_PATHS[i]);
io::clearWatchdog();
background.text = "v" VERSION_STRING;
uiCtx.setBackgroundLayer(background);
uiCtx.setOverlayLayer(overlay);
App app;
App app(uiCtx, resourceProvider);
gpu::enableDisplay(true);
spu::setVolume(0x3fff);
io::setMiscOutput(io::MISC_SPU_ENABLE, true);
io::clearWatchdog();
app.run(uiCtx, resourceProvider, fileProvider, strings);
app.run();
return 0;
}