mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 19:52:05 +01:00
Rearrange app class structure and clean up main()
This commit is contained in:
parent
9cf2908921
commit
d0767b962d
@ -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
|
||||
|
@ -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...",
|
||||
|
631
src/app/app.cpp
631
src/app/app.cpp
@ -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 ®ion = _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();
|
||||
}
|
||||
}
|
||||
|
@ -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
564
src/app/appworkers.cpp
Normal 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 ®ion = _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;
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
92
src/main.cpp
92
src/main.cpp
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user