mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 19:52:05 +01:00
Add reflashing, fix bugs and traceid generation
This commit is contained in:
parent
63651e7114
commit
51ac032198
@ -8,7 +8,11 @@
|
||||
},
|
||||
"cartEraseWorker": {
|
||||
"erase": "Performing cartridge erase...",
|
||||
"error": "An error occurred while erasing the cartridge's EEPROM.\n\nPress the Test button to view debug logs."
|
||||
"error": "An error occurred while erasing the cartridge's EEPROM. The unlocking key may or may not have been successfully changed.\n\nPress the Test button to view debug logs."
|
||||
},
|
||||
"cartReflashWorker": {
|
||||
"flash": "Performing cartridge reflash...",
|
||||
"error": "An error occurred while reflashing the cartridge. The unlocking key may or may not have been successfully changed.\n\nPress the Test button to view debug logs."
|
||||
},
|
||||
"cartUnlockWorker": {
|
||||
"read": "Dumping cartridge...",
|
||||
@ -56,22 +60,31 @@
|
||||
"prompt": "Save the contents of the cartridge's EEPROM to a file on the IDE hard drive or CF card connected as secondary drive (if any)."
|
||||
},
|
||||
"hexdump": {
|
||||
"name": "[UNIMPLEMENTED] View cartridge hexdump",
|
||||
"prompt": "Display the raw contents of the cartridge's EEPROM."
|
||||
"name": "View cartridge hexdump",
|
||||
"prompt": "Display the raw contents of the cartridge's EEPROM in hexadecimal format."
|
||||
},
|
||||
"resetSystemID": {
|
||||
"name": "Reset system identifier (unpair cartridge)",
|
||||
"prompt": "Delete any previously saved system identifier, allowing the cartridge to be used to reinstall the game on any system.",
|
||||
"confirm": "The system identifier will be cleared, allowing the cartridge to be used on a system with any digital I/O board. The game will have to be reinstalled in order to pair the cartridge to the new board.\n\nDo you wish to proceed?"
|
||||
"confirm": "The system identifier will be cleared, allowing the cartridge to be used on a system with any digital I/O board. The game will have to be reinstalled in order to pair the cartridge to the new board.\n\nDo you wish to proceed?",
|
||||
"error": "The system identifier is not present or has already been cleared on this cartridge."
|
||||
},
|
||||
"matchSystemID": {
|
||||
"name": "Copy system identifier (pair to this system)",
|
||||
"prompt": "Set the saved system identifier to allow the cartridge to be used on this system without having to reinstall the game first.",
|
||||
"confirm": "The system identifier will be changed, allowing the cartridge to be used on this system. If already installed here, the game will not have to be reinstalled.\n\nDo you wish to proceed?",
|
||||
"error": "No digital I/O board found. Copying the system identifier requires a digital I/O board to be installed in this system."
|
||||
},
|
||||
"editSystemID": {
|
||||
"name": "Edit system identifier (pair to another I/O board)",
|
||||
"prompt": "Edit the saved system identifier to allow the cartridge to be used on a specific system without having to reinstall the game first.",
|
||||
"confirm": "The system identifier will be changed, allowing the cartridge to be used on the respective system. If already installed on the new system, the game will not have to be reinstalled.\n\nDo you wish to proceed?"
|
||||
"confirm": "The system identifier will be changed, allowing the cartridge to be used on the respective system. If already installed on the new system, the game will not have to be reinstalled.\n\nDo you wish to proceed?",
|
||||
"error": "The system identifier entered is invalid. Make sure all digits are correct and try again."
|
||||
},
|
||||
"reflash": {
|
||||
"name": "[UNIMPLEMENTED] Erase and convert to another game",
|
||||
"prompt": "Wipe all data and flash the cartridge with another game's identifiers. All cartridges can be converted for use with any other game that uses the same cartridge type."
|
||||
"name": "Erase and convert cartridge to another game",
|
||||
"prompt": "Wipe all data and flash the cartridge with another game's identifiers. All cartridges can be converted for use with any other game that uses the same cartridge type.",
|
||||
"confirm": "The contents of the cartridge's EEPROM will be replaced, the system identifier (if any) will be cleared and the unlocking key will be updated to match the new game.\n\nDo you wish to proceed?"
|
||||
},
|
||||
"erase": {
|
||||
"name": "Erase cartridge",
|
||||
@ -151,6 +164,11 @@
|
||||
"ok": "Continue"
|
||||
},
|
||||
|
||||
"HexdumpScreen": {
|
||||
"title": "{CART_ICON} Cartridge dump",
|
||||
"prompt": "Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back."
|
||||
},
|
||||
|
||||
"KeyEntryScreen": {
|
||||
"title": "Enter unlocking key",
|
||||
"body": "Enter the 8-byte key this cartridge was last locked with.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move between digits, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted digit.",
|
||||
@ -163,8 +181,14 @@
|
||||
"prompt": "Scan this code and paste the resulting string into the decodeDump.py script provided alongside this tool to obtain a dump of the cartridge. Press {START_BUTTON} to go back."
|
||||
},
|
||||
|
||||
"ReflashGameScreen": {
|
||||
"title": "{CART_ICON} Select game to convert cartridge to",
|
||||
"prompt": "Make sure you select the correct region. Note that cartridges can only be converted for use with games that accept the same cartridge type.",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to confirm, hold {LEFT_BUTTON} + {RIGHT_BUTTON} to go back"
|
||||
},
|
||||
|
||||
"SystemIDEntryScreen": {
|
||||
"title": "Enter system ID",
|
||||
"title": "Edit system identifier",
|
||||
"body": "Enter the new digital I/O board's identifier. To obtain the ID of another board, run this tool on its respective system.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move between digits, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted digit.",
|
||||
"cancel": "Cancel",
|
||||
"ok": "Confirm"
|
||||
|
Binary file not shown.
@ -13,7 +13,7 @@ public:
|
||||
void (CartActionsScreen::*target)(ui::Context &ctx);
|
||||
};
|
||||
|
||||
static constexpr int _NUM_SYSTEM_ID_ACTIONS = 7;
|
||||
static constexpr int _NUM_SYSTEM_ID_ACTIONS = 8;
|
||||
static constexpr int _NUM_NO_SYSTEM_ID_ACTIONS = 5;
|
||||
|
||||
static const Action _ACTIONS[]{
|
||||
@ -41,6 +41,10 @@ static const Action _ACTIONS[]{
|
||||
.name = "CartActionsScreen.resetSystemID.name"_h,
|
||||
.prompt = "CartActionsScreen.resetSystemID.prompt"_h,
|
||||
.target = &CartActionsScreen::resetSystemID
|
||||
}, {
|
||||
.name = "CartActionsScreen.matchSystemID.name"_h,
|
||||
.prompt = "CartActionsScreen.matchSystemID.prompt"_h,
|
||||
.target = &CartActionsScreen::matchSystemID
|
||||
}, {
|
||||
.name = "CartActionsScreen.editSystemID.name"_h,
|
||||
.prompt = "CartActionsScreen.editSystemID.prompt"_h,
|
||||
@ -63,9 +67,11 @@ void CartActionsScreen::hddDump(ui::Context &ctx) {
|
||||
}
|
||||
|
||||
void CartActionsScreen::hexdump(ui::Context &ctx) {
|
||||
ctx.show(APP->_hexdumpScreen, false, true);
|
||||
}
|
||||
|
||||
void CartActionsScreen::reflash(ui::Context &ctx) {
|
||||
ctx.show(APP->_reflashGameScreen, false, true);
|
||||
}
|
||||
|
||||
void CartActionsScreen::erase(ui::Context &ctx) {
|
||||
@ -82,24 +88,58 @@ void CartActionsScreen::erase(ui::Context &ctx) {
|
||||
}
|
||||
|
||||
void CartActionsScreen::resetSystemID(ui::Context &ctx) {
|
||||
APP->_confirmScreen.setMessage(
|
||||
*this,
|
||||
[](ui::Context &ctx) {
|
||||
APP->_parser->getIdentifiers()->systemID.clear();
|
||||
APP->_parser->flush();
|
||||
if (!(APP->_parser->getIdentifiers()->systemID.isEmpty())) {
|
||||
APP->_confirmScreen.setMessage(
|
||||
*this,
|
||||
[](ui::Context &ctx) {
|
||||
APP->_parser->getIdentifiers()->systemID.clear();
|
||||
APP->_parser->flush();
|
||||
|
||||
APP->_setupWorker(&App::_cartWriteWorker);
|
||||
ctx.show(APP->_workerStatusScreen, false, true);
|
||||
},
|
||||
STR("CartActionsScreen.resetSystemID.confirm")
|
||||
);
|
||||
APP->_setupWorker(&App::_cartWriteWorker);
|
||||
ctx.show(APP->_workerStatusScreen, false, true);
|
||||
},
|
||||
STR("CartActionsScreen.resetSystemID.confirm")
|
||||
);
|
||||
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
} else {
|
||||
APP->_errorScreen.setMessage(
|
||||
*this, STR("CartActionsScreen.resetSystemID.error")
|
||||
);
|
||||
|
||||
ctx.show(APP->_errorScreen, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CartActionsScreen::matchSystemID(ui::Context &ctx) {
|
||||
if (APP->_dump.flags & cart::DUMP_SYSTEM_ID_OK) {
|
||||
APP->_confirmScreen.setMessage(
|
||||
*this,
|
||||
[](ui::Context &ctx) {
|
||||
APP->_parser->getIdentifiers()->systemID.copyFrom(
|
||||
APP->_dump.systemID.data
|
||||
);
|
||||
APP->_parser->flush();
|
||||
|
||||
APP->_setupWorker(&App::_cartWriteWorker);
|
||||
ctx.show(APP->_workerStatusScreen, false, true);
|
||||
},
|
||||
STR("CartActionsScreen.matchSystemID.confirm")
|
||||
);
|
||||
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
} else {
|
||||
APP->_errorScreen.setMessage(
|
||||
*this, STR("CartActionsScreen.matchSystemID.error")
|
||||
);
|
||||
|
||||
ctx.show(APP->_errorScreen, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CartActionsScreen::editSystemID(ui::Context &ctx) {
|
||||
APP->_confirmScreen.setMessage(
|
||||
*this,
|
||||
APP->_systemIDEntryScreen,
|
||||
[](ui::Context &ctx) {
|
||||
APP->_systemIDEntryScreen.setSystemID(*(APP->_parser));
|
||||
|
||||
@ -109,6 +149,10 @@ void CartActionsScreen::editSystemID(ui::Context &ctx) {
|
||||
STR("CartActionsScreen.editSystemID.confirm")
|
||||
);
|
||||
|
||||
APP->_errorScreen.setMessage(
|
||||
APP->_systemIDEntryScreen, STR("CartActionsScreen.editSystemID.error")
|
||||
);
|
||||
|
||||
ctx.show(APP->_systemIDEntryScreen, false, true);
|
||||
}
|
||||
|
||||
@ -135,7 +179,10 @@ void CartActionsScreen::update(ui::Context &ctx) {
|
||||
|
||||
if (ctx.buttons.pressed(ui::BTN_START))
|
||||
(this->*action.target)(ctx);
|
||||
if (ctx.buttons.held(ui::BTN_LEFT) && ctx.buttons.held(ui::BTN_RIGHT))
|
||||
if (
|
||||
(ctx.buttons.held(ui::BTN_LEFT) && ctx.buttons.pressed(ui::BTN_RIGHT)) ||
|
||||
(ctx.buttons.pressed(ui::BTN_LEFT) && ctx.buttons.held(ui::BTN_RIGHT))
|
||||
)
|
||||
ctx.show(APP->_cartInfoScreen, true, true);
|
||||
}
|
||||
|
||||
@ -158,6 +205,69 @@ void QRCodeScreen::update(ui::Context &ctx) {
|
||||
ctx.show(APP->_cartInfoScreen, true, true);
|
||||
}
|
||||
|
||||
void HexdumpScreen::show(ui::Context &ctx, bool goBack) {
|
||||
_title = STR("HexdumpScreen.title");
|
||||
_body = _bodyText;
|
||||
_prompt = STR("HexdumpScreen.prompt");
|
||||
|
||||
size_t length = APP->_dump.getChipSize().dataLength;
|
||||
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
|
||||
|
||||
for (size_t i = 0; i < length; i += 16) {
|
||||
ptr += snprintf(ptr, end - ptr, "%04x: ", i);
|
||||
ptr += util::hexToString(ptr, &APP->_dump.data[i], 16, ' ');
|
||||
|
||||
*(ptr++) = '\n';
|
||||
}
|
||||
|
||||
TextScreen::show(ctx, goBack);
|
||||
}
|
||||
|
||||
void HexdumpScreen::update(ui::Context &ctx) {
|
||||
if (ctx.buttons.pressed(ui::BTN_START))
|
||||
ctx.show(APP->_cartInfoScreen, true, true);
|
||||
}
|
||||
|
||||
const char *ReflashGameScreen::_getItemName(ui::Context &ctx, int index) const {
|
||||
static char name[96]; // TODO: get rid of this ugly crap
|
||||
|
||||
APP->_db.get(index)->getDisplayName(name, sizeof(name));
|
||||
return name;
|
||||
}
|
||||
|
||||
void ReflashGameScreen::show(ui::Context &ctx, bool goBack) {
|
||||
_title = STR("ReflashGameScreen.title");
|
||||
_prompt = STR("ReflashGameScreen.prompt");
|
||||
_itemPrompt = STR("ReflashGameScreen.itemPrompt");
|
||||
|
||||
_listLength = APP->_db.getNumEntries();
|
||||
|
||||
ListScreen::show(ctx, goBack);
|
||||
}
|
||||
|
||||
void ReflashGameScreen::update(ui::Context &ctx) {
|
||||
ListScreen::update(ctx);
|
||||
|
||||
if (ctx.buttons.pressed(ui::BTN_START)) {
|
||||
APP->_confirmScreen.setMessage(
|
||||
*this,
|
||||
[](ui::Context &ctx) {
|
||||
APP->_setupWorker(&App::_cartReflashWorker);
|
||||
ctx.show(APP->_workerStatusScreen, false, true);
|
||||
},
|
||||
STR("CartActionsScreen.reflash.confirm")
|
||||
);
|
||||
|
||||
APP->_reflashEntry = APP->_db.get(_activeItem);
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
} else if (
|
||||
(ctx.buttons.held(ui::BTN_LEFT) && ctx.buttons.pressed(ui::BTN_RIGHT)) ||
|
||||
(ctx.buttons.pressed(ui::BTN_LEFT) && ctx.buttons.held(ui::BTN_RIGHT))
|
||||
) {
|
||||
ctx.show(APP->_cartActionsScreen, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemIDEntryScreen::show(ui::Context &ctx, bool goBack) {
|
||||
_title = STR("SystemIDEntryScreen.title");
|
||||
_body = STR("SystemIDEntryScreen.body");
|
||||
@ -171,15 +281,21 @@ void SystemIDEntryScreen::show(ui::Context &ctx, bool goBack) {
|
||||
_separator = '-';
|
||||
|
||||
HexEntryScreen::show(ctx, goBack);
|
||||
|
||||
APP->_parser->getIdentifiers()->systemID.copyTo(_buffer);
|
||||
}
|
||||
|
||||
void SystemIDEntryScreen::update(ui::Context &ctx) {
|
||||
HexEntryScreen::update(ctx);
|
||||
|
||||
if (ctx.buttons.pressed(ui::BTN_START)) {
|
||||
if (_activeButton == _buttonIndexOffset)
|
||||
if (_activeButton == _buttonIndexOffset) {
|
||||
ctx.show(APP->_cartActionsScreen, true, true);
|
||||
else if (_activeButton == (_buttonIndexOffset + 1))
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
} else if (_activeButton == (_buttonIndexOffset + 1)) {
|
||||
if (util::dsCRC8(_buffer, 7) == _buffer[7])
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
else
|
||||
ctx.show(APP->_errorScreen, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ public:
|
||||
void reflash(ui::Context &ctx);
|
||||
void erase(ui::Context &ctx);
|
||||
void resetSystemID(ui::Context &ctx);
|
||||
void matchSystemID(ui::Context &ctx);
|
||||
void editSystemID(ui::Context &ctx);
|
||||
|
||||
void show(ui::Context &ctx, bool goBack = false);
|
||||
@ -40,6 +41,24 @@ public:
|
||||
void update(ui::Context &ctx);
|
||||
};
|
||||
|
||||
class HexdumpScreen : public ui::TextScreen {
|
||||
private:
|
||||
char _bodyText[2048];
|
||||
|
||||
public:
|
||||
void show(ui::Context &ctx, bool goBack = false);
|
||||
void update(ui::Context &ctx);
|
||||
};
|
||||
|
||||
class ReflashGameScreen : public ui::ListScreen {
|
||||
protected:
|
||||
const char *_getItemName(ui::Context &ctx, int index) const;
|
||||
|
||||
public:
|
||||
void show(ui::Context &ctx, bool goBack = false);
|
||||
void update(ui::Context &ctx);
|
||||
};
|
||||
|
||||
class SystemIDEntryScreen : public ui::HexEntryScreen {
|
||||
public:
|
||||
inline void setSystemID(cart::Parser &parser) const {
|
||||
|
134
src/app/app.cpp
134
src/app/app.cpp
@ -90,8 +90,16 @@ void App::_cartDetectWorker(void) {
|
||||
LOG("cart driver @ 0x%08x", _driver);
|
||||
_workerStatus.update(1, 4, WSTR("App.cartDetectWorker.readCart"));
|
||||
|
||||
_driver->readCartID();
|
||||
if (!_driver->readPublicData())
|
||||
auto error = _driver->readCartID();
|
||||
|
||||
if (error)
|
||||
LOG("cart ID error, code=%d", error);
|
||||
|
||||
error = _driver->readPublicData();
|
||||
|
||||
if (error)
|
||||
LOG("public data error, code=%d", error);
|
||||
else
|
||||
_parser = cart::newCartParser(_dump);
|
||||
|
||||
LOG("cart parser @ 0x%08x", _parser);
|
||||
@ -136,14 +144,21 @@ _cartInitDone:
|
||||
|
||||
// This must be outside of the if block above to make sure the system ID
|
||||
// gets read with the dummy driver.
|
||||
_driver->readSystemID();
|
||||
auto error = _driver->readSystemID();
|
||||
|
||||
if (error)
|
||||
LOG("system ID error, code=%d", error);
|
||||
}
|
||||
|
||||
void App::_cartUnlockWorker(void) {
|
||||
_workerStatus.setNextScreen(_cartInfoScreen, true);
|
||||
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
|
||||
|
||||
if (_driver->readPrivateData()) {
|
||||
auto error = _driver->readPrivateData();
|
||||
|
||||
if (error) {
|
||||
LOG("private data error, code=%d", error);
|
||||
|
||||
/*_errorScreen.setMessage(
|
||||
_cartInfoScreen, WSTR("App.cartUnlockWorker.error")
|
||||
);*/
|
||||
@ -155,6 +170,7 @@ void App::_cartUnlockWorker(void) {
|
||||
delete _parser;
|
||||
|
||||
_parser = cart::newCartParser(_dump);
|
||||
|
||||
if (!_parser)
|
||||
return;
|
||||
|
||||
@ -204,51 +220,102 @@ void App::_hddDumpWorker(void) {
|
||||
void App::_cartWriteWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
|
||||
|
||||
if (_driver->writeData()) {
|
||||
auto error = _driver->writeData();
|
||||
|
||||
_cartDetectWorker();
|
||||
|
||||
if (!error && _identified) {
|
||||
_identified->copyKeyTo(_dump.dataKey);
|
||||
_cartUnlockWorker();
|
||||
}
|
||||
|
||||
if (error) {
|
||||
LOG("write error, code=%d", error);
|
||||
|
||||
_errorScreen.setMessage(
|
||||
_cartInfoScreen, WSTR("App.cartWriteWorker.error")
|
||||
);
|
||||
_workerStatus.setNextScreen(_errorScreen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void App::_cartReflashWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.cartWriteWorker.flash"));
|
||||
|
||||
if (_parser)
|
||||
delete _parser;
|
||||
|
||||
_parser = cart::newCartParser(
|
||||
_dump, _reflashEntry->formatType, _reflashEntry->flags
|
||||
);
|
||||
auto pri = _parser->getIdentifiers();
|
||||
auto pub = _parser->getPublicIdentifiers();
|
||||
|
||||
_dump.clearData();
|
||||
|
||||
pri->clear();
|
||||
pri->cartID.copyFrom(_dump.cartID.data);
|
||||
pri->updateTraceID(_reflashEntry->traceIDType, _reflashEntry->traceIDParam);
|
||||
|
||||
// The private installation ID seems to be unused on carts with a public
|
||||
// data section.
|
||||
if (pub) {
|
||||
pri->installID.clear();
|
||||
pub->setInstallID(_reflashEntry->installIDPrefix);
|
||||
} else {
|
||||
pri->setInstallID(_reflashEntry->installIDPrefix);
|
||||
}
|
||||
|
||||
_parser->setCode(_reflashEntry->code);
|
||||
_parser->setRegion(_reflashEntry->region);
|
||||
_parser->setYear(_reflashEntry->year);
|
||||
_parser->flush();
|
||||
|
||||
uint8_t key[8];
|
||||
auto error = _driver->writeData();
|
||||
|
||||
if (!error) {
|
||||
_reflashEntry->copyKeyTo(key);
|
||||
error = _driver->setDataKey(key);
|
||||
}
|
||||
|
||||
_cartDetectWorker();
|
||||
_cartUnlockWorker();
|
||||
|
||||
if (!error) {
|
||||
_dump.copyKeyFrom(key);
|
||||
_cartUnlockWorker();
|
||||
}
|
||||
|
||||
if (error) {
|
||||
LOG("write error, code=%d", error);
|
||||
|
||||
_errorScreen.setMessage(
|
||||
_cartInfoScreen, WSTR("App.cartReflashWorker.error")
|
||||
);
|
||||
_workerStatus.setNextScreen(_errorScreen);
|
||||
}
|
||||
}
|
||||
|
||||
void App::_cartEraseWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
|
||||
|
||||
if (_driver->erase()) {
|
||||
auto error = _driver->erase();
|
||||
|
||||
_cartDetectWorker();
|
||||
|
||||
if (!error) {
|
||||
_dump.clearKey();
|
||||
_cartUnlockWorker();
|
||||
}
|
||||
|
||||
if (error) {
|
||||
LOG("erase error, code=%d", error);
|
||||
|
||||
_errorScreen.setMessage(
|
||||
_cartInfoScreen, WSTR("App.cartEraseWorker.error")
|
||||
);
|
||||
_workerStatus.setNextScreen(_errorScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
_cartDetectWorker();
|
||||
_cartUnlockWorker();
|
||||
}
|
||||
|
||||
void App::_rebootWorker(void) {
|
||||
int startTime = _ctx->time;
|
||||
int duration = _ctx->gpuCtx.refreshRate * 3;
|
||||
int elapsed;
|
||||
|
||||
// Stop clearing the watchdog for a few seconds.
|
||||
_allowWatchdogClear = false;
|
||||
|
||||
do {
|
||||
elapsed = _ctx->time - startTime;
|
||||
|
||||
_workerStatus.update(elapsed, duration, WSTR("App.rebootWorker.reboot"));
|
||||
delayMicroseconds(10000);
|
||||
} while (elapsed < duration);
|
||||
|
||||
// If for some reason the watchdog fails to reboot the system, fall back to
|
||||
// a soft reboot.
|
||||
softReset();
|
||||
}
|
||||
|
||||
/* Misc. functions */
|
||||
@ -267,9 +334,8 @@ void App::_worker(void) {
|
||||
void App::_interruptHandler(void) {
|
||||
if (acknowledgeInterrupt(IRQ_VBLANK)) {
|
||||
_ctx->tick();
|
||||
io::clearWatchdog();
|
||||
|
||||
if (_allowWatchdogClear)
|
||||
io::clearWatchdog();
|
||||
if (gpu::isIdle() && (_workerStatus.status != WORKER_BUSY_SUSPEND))
|
||||
switchThread(nullptr);
|
||||
}
|
||||
|
@ -95,6 +95,8 @@ class App {
|
||||
friend class KeyEntryScreen;
|
||||
friend class CartActionsScreen;
|
||||
friend class QRCodeScreen;
|
||||
friend class HexdumpScreen;
|
||||
friend class ReflashGameScreen;
|
||||
friend class SystemIDEntryScreen;
|
||||
|
||||
private:
|
||||
@ -108,6 +110,8 @@ private:
|
||||
KeyEntryScreen _keyEntryScreen;
|
||||
CartActionsScreen _cartActionsScreen;
|
||||
QRCodeScreen _qrCodeScreen;
|
||||
HexdumpScreen _hexdumpScreen;
|
||||
ReflashGameScreen _reflashGameScreen;
|
||||
SystemIDEntryScreen _systemIDEntryScreen;
|
||||
|
||||
ui::Context *_ctx;
|
||||
@ -123,9 +127,7 @@ private:
|
||||
uint8_t *_workerStack;
|
||||
cart::Driver *_driver;
|
||||
cart::Parser *_parser;
|
||||
const cart::DBEntry *_identified;
|
||||
|
||||
bool _allowWatchdogClear;
|
||||
const cart::DBEntry *_identified, *_reflashEntry;
|
||||
|
||||
void _unloadCartData(void);
|
||||
void _setupWorker(void (App::*func)(void));
|
||||
@ -136,16 +138,15 @@ private:
|
||||
void _qrCodeWorker(void);
|
||||
void _hddDumpWorker(void);
|
||||
void _cartWriteWorker(void);
|
||||
void _cartReflashWorker(void);
|
||||
void _cartEraseWorker(void);
|
||||
void _rebootWorker(void);
|
||||
|
||||
void _worker(void);
|
||||
void _interruptHandler(void);
|
||||
|
||||
public:
|
||||
inline App(void)
|
||||
: _driver(nullptr), _parser(nullptr), _identified(nullptr),
|
||||
_allowWatchdogClear(true) {
|
||||
: _driver(nullptr), _parser(nullptr), _identified(nullptr) {
|
||||
_workerStack = new uint8_t[WORKER_STACK_SIZE];
|
||||
}
|
||||
inline ~App(void) {
|
||||
|
@ -142,8 +142,6 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) {
|
||||
state = IDENTIFIED;
|
||||
APP->_identified->getDisplayName(name, sizeof(name));
|
||||
|
||||
//
|
||||
|
||||
auto ids = APP->_parser->getIdentifiers();
|
||||
|
||||
if (!(APP->_identified->flags & cart::DATA_HAS_SYSTEM_ID)) {
|
||||
@ -320,7 +318,10 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
|
||||
);
|
||||
ctx.show(APP->_confirmScreen, false, true);
|
||||
}
|
||||
} else if (ctx.buttons.held(ui::BTN_LEFT) && ctx.buttons.held(ui::BTN_RIGHT)) {
|
||||
} else if (
|
||||
(ctx.buttons.held(ui::BTN_LEFT) && ctx.buttons.pressed(ui::BTN_RIGHT)) ||
|
||||
(ctx.buttons.pressed(ui::BTN_LEFT) && ctx.buttons.held(ui::BTN_RIGHT))
|
||||
) {
|
||||
ctx.show(APP->_cartInfoScreen, true, true);
|
||||
}
|
||||
}
|
||||
|
37
src/cart.cpp
37
src/cart.cpp
@ -68,25 +68,28 @@ void IdentifierSet::updateTraceID(TraceIDType type, int param) {
|
||||
uint16_t checksum = 0;
|
||||
|
||||
if (type == TID_81) {
|
||||
// TODO: reverse engineer this TID format
|
||||
// This format seems to be an arbitrary unique identifier not tied to
|
||||
// anything in particular (perhaps RTC RAM?), ignored by the game.
|
||||
traceID.data[0] = 0x81;
|
||||
traceID.data[2] = 0;
|
||||
traceID.data[5] = 0;
|
||||
traceID.data[6] = 0;
|
||||
traceID.data[2] = 5;
|
||||
traceID.data[5] = 7;
|
||||
traceID.data[6] = 3;
|
||||
|
||||
LOG("prefix=0x81");
|
||||
goto _done;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (sizeof(cartID.data) - 2); i++) {
|
||||
for (size_t i = 0; i < ((sizeof(cartID.data) - 2) * 8); i += 8) {
|
||||
uint8_t value = *(input++);
|
||||
|
||||
for (int j = 0; j < 8; j++, value >>= 1) {
|
||||
if (value % 2)
|
||||
checksum ^= 1 << (i % param);
|
||||
for (size_t j = i; j < (i + 8); j++, value >>= 1) {
|
||||
if (value & 1)
|
||||
checksum ^= 1 << (j % param);
|
||||
}
|
||||
}
|
||||
|
||||
traceID.data[0] = 0x82;
|
||||
|
||||
if (type == TID_82_BIG_ENDIAN) {
|
||||
traceID.data[1] = checksum >> 8;
|
||||
traceID.data[2] = checksum & 0xff;
|
||||
@ -101,6 +104,24 @@ _done:
|
||||
traceID.updateChecksum();
|
||||
}
|
||||
|
||||
uint8_t PublicIdentifierSet::getFlags(void) const {
|
||||
uint8_t flags = 0;
|
||||
|
||||
if (!installID.isEmpty())
|
||||
flags |= DATA_HAS_INSTALL_ID;
|
||||
if (!systemID.isEmpty())
|
||||
flags |= DATA_HAS_SYSTEM_ID;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
void PublicIdentifierSet::setInstallID(uint8_t prefix) {
|
||||
installID.clear();
|
||||
|
||||
installID.data[0] = prefix;
|
||||
installID.updateChecksum();
|
||||
}
|
||||
|
||||
void BasicHeader::updateChecksum(bool invert) {
|
||||
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
||||
uint8_t mask = invert ? 0xff : 0x00;
|
||||
|
@ -109,11 +109,14 @@ public:
|
||||
|
||||
class [[gnu::packed]] PublicIdentifierSet {
|
||||
public:
|
||||
Identifier traceID, cartID; // aka TID, SID
|
||||
Identifier installID, systemID; // aka MID, XID
|
||||
|
||||
inline void clear(void) {
|
||||
__builtin_memset(this, 0, sizeof(PublicIdentifierSet));
|
||||
}
|
||||
|
||||
uint8_t getFlags(void) const;
|
||||
void setInstallID(uint8_t prefix);
|
||||
};
|
||||
|
||||
class [[gnu::packed]] SimpleHeader {
|
||||
|
@ -51,6 +51,22 @@ size_t SimpleParser::getRegion(char *output) const {
|
||||
return __builtin_strlen(output);
|
||||
}
|
||||
|
||||
void SimpleParser::setRegion(const char *input) {
|
||||
auto header = _getHeader();
|
||||
|
||||
__builtin_strncpy(header->region, input, sizeof(header->region));
|
||||
}
|
||||
|
||||
void BasicParser::setCode(const char *input) {
|
||||
if (!(flags & DATA_HAS_CODE_PREFIX))
|
||||
return;
|
||||
|
||||
auto header = _getHeader();
|
||||
|
||||
header->region[2] = input[0];
|
||||
header->region[3] = input[1];
|
||||
}
|
||||
|
||||
size_t BasicParser::getRegion(char *output) const {
|
||||
auto header = _getHeader();
|
||||
|
||||
@ -61,10 +77,21 @@ size_t BasicParser::getRegion(char *output) const {
|
||||
return 2;
|
||||
}
|
||||
|
||||
void BasicParser::setRegion(const char *input) {
|
||||
auto header = _getHeader();
|
||||
|
||||
header->region[0] = input[0];
|
||||
header->region[1] = input[1];
|
||||
}
|
||||
|
||||
IdentifierSet *BasicParser::getIdentifiers(void) {
|
||||
return reinterpret_cast<IdentifierSet *>(&_dump.data[sizeof(BasicHeader)]);
|
||||
}
|
||||
|
||||
void BasicParser::flush(void) {
|
||||
_getHeader()->updateChecksum(flags & DATA_CHECKSUM_INVERTED);
|
||||
}
|
||||
|
||||
bool BasicParser::validate(void) {
|
||||
if (!Parser::validate())
|
||||
return false;
|
||||
@ -84,6 +111,15 @@ size_t ExtendedParser::getCode(char *output) const {
|
||||
return __builtin_strlen(output);
|
||||
}
|
||||
|
||||
void ExtendedParser::setCode(const char *input) {
|
||||
auto header = _getHeader();
|
||||
|
||||
__builtin_strncpy(header->code, input, sizeof(header->code));
|
||||
|
||||
if (flags & DATA_GX706_WORKAROUND)
|
||||
header->region[1] = 'E';
|
||||
}
|
||||
|
||||
size_t ExtendedParser::getRegion(char *output) const {
|
||||
auto header = _getHeader();
|
||||
|
||||
@ -93,6 +129,20 @@ size_t ExtendedParser::getRegion(char *output) const {
|
||||
return __builtin_strlen(output);
|
||||
}
|
||||
|
||||
void ExtendedParser::setRegion(const char *input) {
|
||||
auto header = _getHeader();
|
||||
|
||||
__builtin_strncpy(header->region, input, sizeof(header->region));
|
||||
}
|
||||
|
||||
uint16_t ExtendedParser::getYear(void) const {
|
||||
return _getHeader()->year;
|
||||
}
|
||||
|
||||
void ExtendedParser::setYear(uint16_t value) {
|
||||
_getHeader()->year = value;
|
||||
}
|
||||
|
||||
IdentifierSet *ExtendedParser::getIdentifiers(void) {
|
||||
if (!(flags & DATA_HAS_PUBLIC_SECTION))
|
||||
return nullptr;
|
||||
@ -118,8 +168,11 @@ void ExtendedParser::flush(void) {
|
||||
auto pri = getIdentifiers();
|
||||
auto pub = getPublicIdentifiers();
|
||||
|
||||
pub->traceID.copyFrom(pri->traceID.data);
|
||||
pub->cartID.copyFrom(pri->cartID.data);
|
||||
// The private installation ID seems to always go unused and zeroed out...
|
||||
//pub->installID.copyFrom(pri->installID.data);
|
||||
pub->systemID.copyFrom(pri->systemID.data);
|
||||
|
||||
_getHeader()->updateChecksum(flags & DATA_CHECKSUM_INVERTED);
|
||||
}
|
||||
|
||||
bool ExtendedParser::validate(void) {
|
||||
@ -272,18 +325,22 @@ Parser *newCartParser(Dump &dump) {
|
||||
const DBEntry *CartDB::lookup(const char *code, const char *region) const {
|
||||
// Perform a binary search. This assumes all entries in the DB are sorted by
|
||||
// their code and region.
|
||||
auto offset = reinterpret_cast<const DBEntry *>(ptr);
|
||||
auto low = reinterpret_cast<const DBEntry *>(ptr);
|
||||
auto high = &low[getNumEntries() - 1];
|
||||
|
||||
for (size_t step = getNumEntries() / 2; step; step /= 2) {
|
||||
auto entry = &offset[step];
|
||||
while (low <= high) {
|
||||
auto entry = &low[(high - low) / 2];
|
||||
int diff = entry->compare(code, region);
|
||||
|
||||
if (!diff) {
|
||||
LOG("%s %s found, entry=0x%08x", code, region, entry);
|
||||
return entry;
|
||||
} else if (diff < 0) {
|
||||
offset = entry;
|
||||
}
|
||||
|
||||
if (diff < 0)
|
||||
low = &entry[1];
|
||||
else
|
||||
high = &entry[-1];
|
||||
}
|
||||
|
||||
LOG("%s %s not found", code, region);
|
||||
|
@ -34,7 +34,11 @@ public:
|
||||
|
||||
virtual ~Parser(void) {}
|
||||
virtual size_t getCode(char *output) const { return 0; }
|
||||
virtual void setCode(const char *input) {}
|
||||
virtual size_t getRegion(char *output) const { return 0; }
|
||||
virtual void setRegion(const char *input) {}
|
||||
virtual uint16_t getYear(void) const { return 0; }
|
||||
virtual void setYear(uint16_t value) {}
|
||||
virtual IdentifierSet *getIdentifiers(void) { return nullptr; }
|
||||
virtual PublicIdentifierSet *getPublicIdentifiers(void) { return nullptr; }
|
||||
virtual void flush(void) {}
|
||||
@ -52,6 +56,7 @@ public:
|
||||
: Parser(dump, flags | DATA_HAS_PUBLIC_SECTION) {}
|
||||
|
||||
size_t getRegion(char *output) const;
|
||||
void setRegion(const char *input);
|
||||
};
|
||||
|
||||
class BasicParser : public Parser {
|
||||
@ -64,8 +69,11 @@ public:
|
||||
inline BasicParser(Dump &dump, uint8_t flags = 0)
|
||||
: Parser(dump, flags) {}
|
||||
|
||||
void setCode(const char *input);
|
||||
size_t getRegion(char *output) const;
|
||||
void setRegion(const char *input);
|
||||
IdentifierSet *getIdentifiers(void);
|
||||
void flush(void);
|
||||
bool validate(void);
|
||||
};
|
||||
|
||||
@ -80,7 +88,11 @@ public:
|
||||
: Parser(dump, flags | DATA_HAS_CODE_PREFIX) {}
|
||||
|
||||
size_t getCode(char *output) const;
|
||||
void setCode(const char *input);
|
||||
size_t getRegion(char *output) const;
|
||||
void setRegion(const char *input);
|
||||
uint16_t getYear(void) const;
|
||||
void setYear(uint16_t value);
|
||||
IdentifierSet *getIdentifiers(void);
|
||||
PublicIdentifierSet *getPublicIdentifiers(void);
|
||||
void flush(void);
|
||||
@ -118,6 +130,9 @@ public:
|
||||
inline int getDisplayName(char *output, size_t length) const {
|
||||
return snprintf(output, length, "%s %s\t%s", code, region, name);
|
||||
}
|
||||
inline void copyKeyTo(uint8_t *dest) const {
|
||||
__builtin_memcpy(dest, dataKey, sizeof(dataKey));
|
||||
}
|
||||
};
|
||||
|
||||
class CartDB : public util::Data {
|
||||
|
@ -110,6 +110,7 @@ DriverError DummyDriver::setDataKey(const uint8_t *key) {
|
||||
static constexpr int _X76_MAX_ACK_POLLS = 5;
|
||||
static constexpr int _X76_WRITE_DELAY = 12000;
|
||||
static constexpr int _X76_PACKET_DELAY = 12000;
|
||||
static constexpr int _ZS01_SEND_DELAY = 30000;
|
||||
static constexpr int _ZS01_PACKET_DELAY = 30000;
|
||||
|
||||
DriverError CartDriver::readSystemID(void) {
|
||||
@ -377,6 +378,7 @@ DriverError X76F100Driver::setDataKey(const uint8_t *key) {
|
||||
DriverError ZS01Driver::_transact(
|
||||
zs01::Packet &request, zs01::Packet &response
|
||||
) {
|
||||
delayMicroseconds(_ZS01_PACKET_DELAY);
|
||||
io::i2cStart();
|
||||
|
||||
#ifdef ENABLE_I2C_LOGGING
|
||||
@ -387,7 +389,7 @@ DriverError ZS01Driver::_transact(
|
||||
#endif
|
||||
|
||||
if (!io::i2cWriteBytes(
|
||||
&request.command, sizeof(zs01::Packet), _ZS01_PACKET_DELAY
|
||||
&request.command, sizeof(zs01::Packet), _ZS01_SEND_DELAY
|
||||
)) {
|
||||
io::i2cStop();
|
||||
LOG("NACK while sending request packet");
|
||||
@ -402,14 +404,16 @@ DriverError ZS01Driver::_transact(
|
||||
LOG("R: %s", buffer);
|
||||
#endif
|
||||
|
||||
if (!response.decodeResponse())
|
||||
return ZS01_CRC_MISMATCH;
|
||||
bool ok = response.decodeResponse();
|
||||
|
||||
#ifdef ENABLE_I2C_LOGGING
|
||||
util::hexToString(buffer, &response.command, sizeof(zs01::Packet), ' ');
|
||||
LOG("D: %s", buffer);
|
||||
#endif
|
||||
|
||||
//if (!ok)
|
||||
//return ZS01_CRC_MISMATCH;
|
||||
|
||||
_encoderState = response.address;
|
||||
|
||||
if (response.command != zs01::RESP_NO_ERROR) {
|
||||
|
@ -232,7 +232,7 @@ DeviceError Device::enumerate(void) {
|
||||
_copyString(revision, block.revision, sizeof(revision));
|
||||
_copyString(serialNumber, block.serialNumber, sizeof(serialNumber));
|
||||
|
||||
LOG("%s: %s", (flags & DEVICE_PRIMARY) ? "primary" : "secondary", model);
|
||||
LOG("%s: %s", (flags & DEVICE_SECONDARY) ? "secondary" : "primary", model);
|
||||
|
||||
// Find out the fastest PIO transfer mode supported and enable it.
|
||||
int mode = 1;
|
||||
|
26
src/io.cpp
26
src/io.cpp
@ -171,15 +171,18 @@ void initKonamiBitstream(void) {
|
||||
|
||||
/* I2C driver */
|
||||
|
||||
static constexpr int _I2C_BUS_DELAY = 50;
|
||||
static constexpr int _I2C_RESET_DELAY = 500;
|
||||
|
||||
// SDA is open-drain so it is toggled by changing pin direction.
|
||||
#define _SDA(value) setCartSDADir(!(value))
|
||||
#define SDA(value) _SDA(value), delayMicroseconds(20)
|
||||
#define SDA(value) _SDA(value), delayMicroseconds(_I2C_BUS_DELAY)
|
||||
#define _SCL(value) setCartOutput(OUT_SCL, value)
|
||||
#define SCL(value) _SCL(value), delayMicroseconds(20)
|
||||
#define SCL(value) _SCL(value), delayMicroseconds(_I2C_BUS_DELAY)
|
||||
#define _CS(value) setCartOutput(OUT_CS, value)
|
||||
#define CS(value) _CS(value), delayMicroseconds(20)
|
||||
#define CS(value) _CS(value), delayMicroseconds(_I2C_BUS_DELAY)
|
||||
#define _RESET(value) setCartOutput(OUT_RESET, value)
|
||||
#define RESET(value) _RESET(value), delayMicroseconds(20)
|
||||
#define RESET(value) _RESET(value), delayMicroseconds(_I2C_BUS_DELAY)
|
||||
|
||||
void i2cStart(void) {
|
||||
_SDA(true);
|
||||
@ -226,7 +229,7 @@ uint8_t i2cReadByte(void) {
|
||||
SCL(false);
|
||||
}
|
||||
|
||||
delayMicroseconds(20);
|
||||
delayMicroseconds(_I2C_BUS_DELAY);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -248,12 +251,12 @@ void i2cSendACK(bool ack) {
|
||||
}
|
||||
|
||||
bool i2cGetACK(void) {
|
||||
delayMicroseconds(20); // Required for ZS01
|
||||
delayMicroseconds(_I2C_BUS_DELAY); // Required for ZS01
|
||||
SCL(true);
|
||||
bool ack = !getCartSDA();
|
||||
SCL(false);
|
||||
|
||||
delayMicroseconds(20);
|
||||
delayMicroseconds(_I2C_BUS_DELAY);
|
||||
return ack;
|
||||
}
|
||||
|
||||
@ -291,6 +294,7 @@ uint32_t i2cResetX76(void) {
|
||||
SCL(true);
|
||||
SCL(false);
|
||||
RESET(false);
|
||||
delayMicroseconds(_I2C_RESET_DELAY);
|
||||
|
||||
for (int bit = 0; bit < 32; bit++) { // LSB first
|
||||
SCL(true);
|
||||
@ -317,7 +321,7 @@ uint32_t i2cResetZS01(void) {
|
||||
|
||||
RESET(false);
|
||||
RESET(true);
|
||||
delayMicroseconds(100);
|
||||
delayMicroseconds(_I2C_RESET_DELAY);
|
||||
|
||||
SCL(true);
|
||||
SCL(false);
|
||||
@ -335,12 +339,14 @@ uint32_t i2cResetZS01(void) {
|
||||
|
||||
/* 1-wire driver */
|
||||
|
||||
static constexpr int _DS_RESET_DELAY = 480;
|
||||
|
||||
#define _CART1WIRE(value) setCartOutput(OUT_1WIRE, !(value))
|
||||
#define _DIO1WIRE(value) setDIO1Wire(value)
|
||||
|
||||
bool dsCartReset(void) {
|
||||
_CART1WIRE(false);
|
||||
delayMicroseconds(480);
|
||||
delayMicroseconds(_DS_RESET_DELAY);
|
||||
_CART1WIRE(true);
|
||||
|
||||
delayMicroseconds(60);
|
||||
@ -353,7 +359,7 @@ bool dsCartReset(void) {
|
||||
|
||||
bool dsDIOReset(void) {
|
||||
_DIO1WIRE(false);
|
||||
delayMicroseconds(480);
|
||||
delayMicroseconds(_DS_RESET_DELAY);
|
||||
_DIO1WIRE(true);
|
||||
|
||||
delayMicroseconds(60);
|
||||
|
@ -79,25 +79,35 @@ void MessageScreen::update(Context &ctx) {
|
||||
if (_locked)
|
||||
return;
|
||||
|
||||
if (ctx.buttons.pressed(ui::BTN_LEFT)) {
|
||||
if (_activeButton > 0) {
|
||||
_activeButton--;
|
||||
int numButtons = _buttonIndexOffset + _numButtons;
|
||||
|
||||
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
|
||||
ctx.sounds[SOUND_MOVE].play();
|
||||
} else {
|
||||
if (
|
||||
ctx.buttons.pressed(ui::BTN_LEFT) ||
|
||||
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeButton > 0))
|
||||
) {
|
||||
_activeButton--;
|
||||
if (_activeButton < 0) {
|
||||
_activeButton += numButtons;
|
||||
ctx.sounds[SOUND_CLICK].play();
|
||||
} else {
|
||||
ctx.sounds[SOUND_MOVE].play();
|
||||
}
|
||||
|
||||
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
|
||||
}
|
||||
if (ctx.buttons.pressed(ui::BTN_RIGHT)) {
|
||||
if (_activeButton < (_buttonIndexOffset + _numButtons - 1)) {
|
||||
_activeButton++;
|
||||
|
||||
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
|
||||
ctx.sounds[SOUND_MOVE].play();
|
||||
} else {
|
||||
if (
|
||||
ctx.buttons.pressed(ui::BTN_RIGHT) ||
|
||||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeButton < (numButtons - 1)))
|
||||
) {
|
||||
_activeButton++;
|
||||
if (_activeButton >= numButtons) {
|
||||
_activeButton -= numButtons;
|
||||
ctx.sounds[SOUND_CLICK].play();
|
||||
} else {
|
||||
ctx.sounds[SOUND_MOVE].play();
|
||||
}
|
||||
|
||||
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,8 +151,9 @@ void HexEntryScreen::draw(Context &ctx, bool active) const {
|
||||
// Cursor
|
||||
if (_activeButton < _buttonIndexOffset)
|
||||
ctx.gpuCtx.drawGradientRectV(
|
||||
textOffset + _cursorAnim.getValue(ctx.time), boxY, digitWidth,
|
||||
BUTTON_HEIGHT, COLOR_BOX1, COLOR_HIGHLIGHT1
|
||||
textOffset + _cursorAnim.getValue(ctx.time),
|
||||
boxY + BUTTON_HEIGHT / 2, digitWidth, BUTTON_HEIGHT / 2, COLOR_BOX1,
|
||||
COLOR_HIGHLIGHT1
|
||||
);
|
||||
|
||||
// Text
|
||||
|
@ -18,8 +18,10 @@ static const Key _COMMAND_KEY{
|
||||
// Konami's driver generates a pseudorandom key for each transaction, but it can
|
||||
// be a fixed key as well.
|
||||
static const Key _RESPONSE_KEY{
|
||||
.add = { 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
.shift = { 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
.add = { 237, 8, 16, 11, 6, 4, 8, 30 },
|
||||
.shift = { 0, 3, 2, 2, 6, 2, 2, 1 }
|
||||
//.add = { 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
//.shift = { 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
/* Packet encoding/decoding */
|
||||
|
14
src/zs01.hpp
14
src/zs01.hpp
@ -30,15 +30,15 @@ enum RequestFlag : uint8_t {
|
||||
enum ResponseCode : uint8_t {
|
||||
// The meaning of these codes is currently unknown. Presumably:
|
||||
// - one of the "security errors" is a CRC validation failure, the other
|
||||
// could be data key related
|
||||
// could be data key related, the third one could be DS2401 related
|
||||
// - one of the unknown errors is for invalid commands or addresses
|
||||
// - one or two of the unknown errors are for actual read/write failures
|
||||
// - one of the unknown errors is for actual read/write failures
|
||||
RESP_NO_ERROR = 0x00,
|
||||
RESP_SECURITY_ERROR1 = 0x01,
|
||||
RESP_UNKNOWN_ERROR1 = 0x02,
|
||||
RESP_UNKNOWN_ERROR2 = 0x03,
|
||||
RESP_SECURITY_ERROR2 = 0x04,
|
||||
RESP_UNKNOWN_ERROR3 = 0x05
|
||||
RESP_UNKNOWN_ERROR1 = 0x01,
|
||||
RESP_SECURITY_ERROR1 = 0x02,
|
||||
RESP_SECURITY_ERROR2 = 0x03,
|
||||
RESP_UNKNOWN_ERROR2 = 0x04,
|
||||
RESP_SECURITY_ERROR3 = 0x05
|
||||
};
|
||||
|
||||
/* Packet encoding/decoding */
|
||||
|
@ -6,7 +6,7 @@ __author__ = "spicyjpeg"
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum, IntFlag
|
||||
from struct import Struct
|
||||
from struct import Struct, unpack
|
||||
from typing import Any, Iterable, Iterator, Mapping, Sequence
|
||||
|
||||
## Definitions
|
||||
@ -100,7 +100,24 @@ class IdentifierSet:
|
||||
|
||||
return flags
|
||||
|
||||
def getTraceIDType(self) -> TraceIDType:
|
||||
def getCartIDChecksum(self, param: int) -> int:
|
||||
if self.cartID is None:
|
||||
return 0
|
||||
|
||||
checksum: int = 0
|
||||
|
||||
for i in range(6):
|
||||
value: int = self.cartID[i + 1]
|
||||
|
||||
for j in range(i * 8, (i + 1) * 8):
|
||||
if value & 1:
|
||||
checksum ^= 1 << (j % param)
|
||||
|
||||
value >>= 1
|
||||
|
||||
return checksum & 0xffff
|
||||
|
||||
def getTraceIDType(self, param: int) -> TraceIDType:
|
||||
if self.traceID is None:
|
||||
return TraceIDType.TID_NONE
|
||||
|
||||
@ -109,7 +126,17 @@ class IdentifierSet:
|
||||
return TraceIDType.TID_81
|
||||
|
||||
case 0x82:
|
||||
return TraceIDType.TID_82_BIG_ENDIAN # TODO
|
||||
print(self.cartID,self.traceID)
|
||||
checksum: int = self.getCartIDChecksum(param)
|
||||
big: int = unpack("> H", self.traceID[1:3])[0]
|
||||
little: int = unpack("< H", self.traceID[1:3])[0]
|
||||
|
||||
if checksum == big:
|
||||
return TraceIDType.TID_82_BIG_ENDIAN
|
||||
elif checksum == little:
|
||||
return TraceIDType.TID_82_LITTLE_ENDIAN
|
||||
|
||||
raise ValueError(f"trace ID mismatch, exp=0x{checksum:04x}, big=0x{big:04x}, little=0x{little:04x}")
|
||||
|
||||
case prefix:
|
||||
raise ValueError(f"unknown trace ID prefix: 0x{prefix:02x}")
|
||||
@ -272,6 +299,7 @@ class ExtendedParser(Parser):
|
||||
## Cartridge database
|
||||
|
||||
DB_ENTRY_STRUCT: Struct = Struct("< 6B H 8s 8s 8s 96s")
|
||||
TRACE_ID_PARAMS: Sequence[int] = 16, 14
|
||||
|
||||
@dataclass
|
||||
class GameEntry:
|
||||
@ -312,21 +340,32 @@ class DBEntry:
|
||||
year: int = 0
|
||||
|
||||
def __init__(self, game: GameEntry, dump: Dump, parser: Parser):
|
||||
# Find the correct parameters for the trace ID heuristically.
|
||||
_type: TraceIDType | None = None
|
||||
|
||||
for self.traceIDParam in TRACE_ID_PARAMS:
|
||||
try:
|
||||
_type = parser.identifiers.getTraceIDType(self.traceIDParam)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
if _type is None:
|
||||
raise RuntimeError("failed to determine trace ID parameters")
|
||||
|
||||
self.game = game
|
||||
self.dataKey = dump.dataKey
|
||||
self.chipType = dump.chipType
|
||||
self.formatType = parser.formatType
|
||||
self.traceIDType = parser.identifiers.getTraceIDType()
|
||||
self.traceIDType = _type
|
||||
self.flags = parser.flags
|
||||
self.year = parser.year or 0
|
||||
|
||||
# TODO: implement this properly
|
||||
self.traceIDParam = 16
|
||||
|
||||
if parser.identifiers.installID:
|
||||
self.installIDPrefix = parser.identifiers.installID[0]
|
||||
else:
|
||||
if parser.identifiers.installID is None:
|
||||
self.installIDPrefix = 0
|
||||
else:
|
||||
self.installIDPrefix = parser.identifiers.installID[0]
|
||||
|
||||
def __lt__(self, entry: Any) -> bool:
|
||||
return (self.game < entry.game)
|
||||
|
Loading…
x
Reference in New Issue
Block a user