mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-02 15:44:35 +01:00
Implement cart data parsing, move things around
This commit is contained in:
parent
4bed3c7f12
commit
bd50057976
@ -17,8 +17,8 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
|||||||
add_executable(
|
add_executable(
|
||||||
cart_tool
|
cart_tool
|
||||||
src/asset.cpp
|
src/asset.cpp
|
||||||
src/cart.cpp
|
src/cartdata.cpp
|
||||||
src/cartdb.cpp
|
src/cartio.cpp
|
||||||
src/gpu.cpp
|
src/gpu.cpp
|
||||||
src/io.cpp
|
src/io.cpp
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
#include "app/app.hpp"
|
#include "app/app.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "asset.hpp"
|
#include "asset.hpp"
|
||||||
#include "cart.hpp"
|
#include "cartdata.hpp"
|
||||||
#include "cartdb.hpp"
|
#include "cartio.hpp"
|
||||||
#include "io.hpp"
|
#include "io.hpp"
|
||||||
#include "uibase.hpp"
|
#include "uibase.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
@ -13,7 +13,7 @@
|
|||||||
/* App class */
|
/* App class */
|
||||||
|
|
||||||
App::App(void)
|
App::App(void)
|
||||||
: _cart(nullptr), _identified(nullptr), _identifyResult(cartdb::UNIDENTIFIED) {
|
: _cart(nullptr), _cartData(nullptr), _identified(nullptr) {
|
||||||
_workerStack = new uint8_t[WORKER_STACK_SIZE];
|
_workerStack = new uint8_t[WORKER_STACK_SIZE];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,31 +59,36 @@ static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
|
|||||||
void App::_cartDetectWorker(void) {
|
void App::_cartDetectWorker(void) {
|
||||||
_workerStatus.update(0, 4, WSTR("App.cartDetectWorker.identifyCart"));
|
_workerStatus.update(0, 4, WSTR("App.cartDetectWorker.identifyCart"));
|
||||||
|
|
||||||
_db.data.unload();
|
_db.unload();
|
||||||
_cart = cart::createCart();
|
if (_cart)
|
||||||
|
delete _cart;
|
||||||
|
if (_cartData)
|
||||||
|
delete _cartData;
|
||||||
|
|
||||||
if (_cart->chipType) {
|
_cart = cart::createCart(_dump);
|
||||||
LOG("cart object @ 0x%08x", _cart);
|
_cartData = nullptr;
|
||||||
|
_identified = nullptr;
|
||||||
|
|
||||||
|
if (_dump.chipType) {
|
||||||
|
LOG("dump @ 0x%08x, cart object @ 0x%08x", &_dump, _cart);
|
||||||
_workerStatus.update(1, 4, WSTR("App.cartDetectWorker.readCart"));
|
_workerStatus.update(1, 4, WSTR("App.cartDetectWorker.readCart"));
|
||||||
|
|
||||||
_cart->readCartID();
|
_cart->readCartID();
|
||||||
_cart->readPublicData();
|
if (!_cart->readPublicData())
|
||||||
|
_cartData = cart::createCartData(_dump);
|
||||||
if (!_loader->loadAsset(_db.data, _CARTDB_PATHS[_cart->chipType])) {
|
if (!_cartData)
|
||||||
LOG("failed to load cartdb, type=%d", _cart->chipType);
|
|
||||||
goto _cartInitDone;
|
goto _cartInitDone;
|
||||||
}
|
|
||||||
if (!_db.init()) {
|
|
||||||
_db.data.unload();
|
|
||||||
goto _cartInitDone;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
LOG("cart data object @ 0x%08x", _cartData);
|
||||||
_workerStatus.update(2, 4, WSTR("App.cartDetectWorker.identifyGame"));
|
_workerStatus.update(2, 4, WSTR("App.cartDetectWorker.identifyGame"));
|
||||||
|
|
||||||
if (_cart->flags & cart::PUBLIC_DATA_OK)
|
if (!_loader->loadAsset(_db, _CARTDB_PATHS[_dump.chipType])) {
|
||||||
_identifyResult = _db.identifyCart(*_cart);
|
LOG("failed to load %s", _CARTDB_PATHS[_dump.chipType]);
|
||||||
else
|
goto _cartInitDone;
|
||||||
LOG("no public data available");
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//_identified = _db.lookupEntry(code, region);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cartInitDone:
|
_cartInitDone:
|
||||||
@ -125,8 +130,8 @@ void App::_cartUnlockWorker(void) {
|
|||||||
|
|
||||||
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identify"));
|
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identify"));
|
||||||
|
|
||||||
if (_cart->flags & cart::PRIVATE_DATA_OK)
|
//if (_dump.flags & cart::DUMP_PRIVATE_DATA_OK)
|
||||||
_identifyResult = _db.identifyCart(*_cart);
|
//_identifyResult = _db.identifyCart(*_cart);
|
||||||
|
|
||||||
_workerStatus.finish(_cartInfoScreen, true);
|
_workerStatus.finish(_cartInfoScreen, true);
|
||||||
_dummyWorker();
|
_dummyWorker();
|
||||||
@ -136,7 +141,7 @@ void App::_qrCodeWorker(void) {
|
|||||||
char qrString[cart::MAX_QR_STRING_LENGTH];
|
char qrString[cart::MAX_QR_STRING_LENGTH];
|
||||||
|
|
||||||
_workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
|
_workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
|
||||||
_cart->toQRString(qrString);
|
_dump.toQRString(qrString);
|
||||||
|
|
||||||
_workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
|
_workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
|
||||||
_qrCodeScreen.generateCode(qrString);
|
_qrCodeScreen.generateCode(qrString);
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
#include "app/unlock.hpp"
|
#include "app/unlock.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "asset.hpp"
|
#include "asset.hpp"
|
||||||
#include "cart.hpp"
|
#include "cartdata.hpp"
|
||||||
#include "cartdb.hpp"
|
#include "cartio.hpp"
|
||||||
#include "uibase.hpp"
|
#include "uibase.hpp"
|
||||||
|
|
||||||
/* Worker status class */
|
/* Worker status class */
|
||||||
@ -85,15 +85,15 @@ private:
|
|||||||
asset::AssetLoader *_loader;
|
asset::AssetLoader *_loader;
|
||||||
asset::StringTable *_strings;
|
asset::StringTable *_strings;
|
||||||
|
|
||||||
cartdb::CartDB _db;
|
cart::Dump _dump;
|
||||||
|
cart::CartDB _db;
|
||||||
Thread _workerThread;
|
Thread _workerThread;
|
||||||
WorkerStatus _workerStatus;
|
WorkerStatus _workerStatus;
|
||||||
|
|
||||||
uint8_t *_workerStack;
|
uint8_t *_workerStack;
|
||||||
cart::Cart *_cart;
|
cart::Cart *_cart;
|
||||||
|
cart::CartData *_cartData;
|
||||||
cartdb::Entry *_identified;
|
cart::DBEntry *_identified;
|
||||||
cartdb::IdentifyResult _identifyResult;
|
|
||||||
|
|
||||||
void _setupWorker(void (App::* func)(void));
|
void _setupWorker(void (App::* func)(void));
|
||||||
void _setupInterrupts(void);
|
void _setupInterrupts(void);
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "app/app.hpp"
|
#include "app/app.hpp"
|
||||||
#include "app/unlock.hpp"
|
#include "app/unlock.hpp"
|
||||||
#include "cart.hpp"
|
#include "cartdata.hpp"
|
||||||
#include "cartdb.hpp"
|
#include "cartio.hpp"
|
||||||
#include "defs.hpp"
|
#include "defs.hpp"
|
||||||
#include "uibase.hpp"
|
#include "uibase.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
@ -36,6 +36,12 @@ static const CartType _CART_TYPES[cart::NUM_CHIP_TYPES]{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum IdentifyState {
|
||||||
|
UNIDENTIFIED = 0,
|
||||||
|
IDENTIFIED = 1,
|
||||||
|
BLANK_CART = 2
|
||||||
|
};
|
||||||
|
|
||||||
static const util::Hash _LOCKED_PROMPTS[]{
|
static const util::Hash _LOCKED_PROMPTS[]{
|
||||||
"CartInfoScreen.description.locked.unidentified"_h,
|
"CartInfoScreen.description.locked.unidentified"_h,
|
||||||
"CartInfoScreen.description.locked.identified"_h,
|
"CartInfoScreen.description.locked.identified"_h,
|
||||||
@ -53,60 +59,59 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) {
|
|||||||
|
|
||||||
TextScreen::show(ctx, goBack);
|
TextScreen::show(ctx, goBack);
|
||||||
|
|
||||||
auto &_cart = *(APP->_cart);
|
auto &dump = APP->_dump;
|
||||||
auto flags = _cart.flags;
|
|
||||||
|
|
||||||
char id1[32], id2[32];
|
char id1[32], id2[32];
|
||||||
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
|
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
|
||||||
|
|
||||||
// Digital I/O board info
|
// Digital I/O board info
|
||||||
if (flags & cart::SYSTEM_ID_OK) {
|
if (dump.flags & cart::DUMP_SYSTEM_ID_OK) {
|
||||||
util::hexToString(id1, _cart.systemID, sizeof(_cart.systemID), '-');
|
dump.systemID.toString(id1);
|
||||||
util::serialNumberToString(id2, &_cart.systemID[1]);
|
dump.systemID.toSerialNumber(id2);
|
||||||
} else if (flags & cart::HAS_DIGITAL_IO) {
|
} else if (dump.flags & cart::DUMP_HAS_SYSTEM_ID) {
|
||||||
strcpy(id1, STR("CartInfoScreen.id.error"));
|
__builtin_strcpy(id1, STR("CartInfoScreen.id.error"));
|
||||||
strcpy(id2, id1);
|
__builtin_strcpy(id2, id1);
|
||||||
} else {
|
} else {
|
||||||
strcpy(id1, STR("CartInfoScreen.id.noSystemID"));
|
__builtin_strcpy(id1, STR("CartInfoScreen.id.noSystemID"));
|
||||||
strcpy(id2, id1);
|
__builtin_strcpy(id2, id1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr += snprintf(ptr, end - ptr, STR("CartInfoScreen.digitalIOInfo"), id1, id2);
|
ptr += snprintf(ptr, end - ptr, STR("CartInfoScreen.digitalIOInfo"), id1, id2);
|
||||||
|
|
||||||
// Cartridge info
|
// Cartridge info
|
||||||
if (!_cart.chipType) {
|
if (!dump.chipType) {
|
||||||
memccpy(ptr, STR("CartInfoScreen.description.noCart"), 0, end - ptr);
|
memccpy(ptr, STR("CartInfoScreen.description.noCart"), 0, end - ptr);
|
||||||
|
|
||||||
_prompt = STR("CartInfoScreen.prompt.error");
|
_prompt = STR("CartInfoScreen.prompt.error");
|
||||||
return;
|
return;
|
||||||
} else if (!(_cart.flags & cart::PUBLIC_DATA_OK)) {
|
} else if (!(dump.flags & cart::DUMP_PUBLIC_DATA_OK)) {
|
||||||
memccpy(ptr, STR("CartInfoScreen.description.initError"), 0, end - ptr);
|
memccpy(ptr, STR("CartInfoScreen.description.initError"), 0, end - ptr);
|
||||||
|
|
||||||
_prompt = STR("CartInfoScreen.prompt.error");
|
_prompt = STR("CartInfoScreen.prompt.error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & cart::CART_ID_OK)
|
if (dump.flags & cart::DUMP_CART_ID_OK)
|
||||||
util::hexToString(id1, _cart.cartID, sizeof(_cart.cartID), '-');
|
dump.cartID.toString(id1);
|
||||||
else if (flags & cart::HAS_DS2401)
|
else if (dump.flags & cart::DUMP_HAS_CART_ID)
|
||||||
strcpy(id1, STR("CartInfoScreen.id.error"));
|
__builtin_strcpy(id1, STR("CartInfoScreen.id.error"));
|
||||||
else
|
else
|
||||||
strcpy(id1, STR("CartInfoScreen.id.noCartID"));
|
__builtin_strcpy(id1, STR("CartInfoScreen.id.noCartID"));
|
||||||
|
|
||||||
if (flags & cart::ZS_ID_OK)
|
if (dump.flags & cart::DUMP_ZS_ID_OK)
|
||||||
util::hexToString(id2, _cart.zsID, sizeof(_cart.zsID), '-');
|
dump.zsID.toString(id2);
|
||||||
else if (_cart.chipType == cart::TYPE_ZS01)
|
else if (dump.chipType == cart::ZS01)
|
||||||
strcpy(id2, STR("CartInfoScreen.id.error"));
|
__builtin_strcpy(id2, STR("CartInfoScreen.id.error"));
|
||||||
else
|
else
|
||||||
strcpy(id2, STR("CartInfoScreen.id.noZSID"));
|
__builtin_strcpy(id2, STR("CartInfoScreen.id.noZSID"));
|
||||||
|
|
||||||
auto unlockStatus = (flags & cart::PRIVATE_DATA_OK) ?
|
auto unlockStatus = (dump.flags & cart::DUMP_PRIVATE_DATA_OK) ?
|
||||||
STR("CartInfoScreen.unlockStatus.unlocked") :
|
STR("CartInfoScreen.unlockStatus.unlocked") :
|
||||||
STR("CartInfoScreen.unlockStatus.locked");
|
STR("CartInfoScreen.unlockStatus.locked");
|
||||||
|
|
||||||
ptr += snprintf(
|
ptr += snprintf(
|
||||||
ptr, end - ptr, STR("CartInfoScreen.cartInfo"),
|
ptr, end - ptr, STR("CartInfoScreen.cartInfo"),
|
||||||
STRH(_CART_TYPES[_cart.chipType].name), unlockStatus, id1, id2
|
STRH(_CART_TYPES[dump.chipType].name), unlockStatus, id1, id2
|
||||||
);
|
);
|
||||||
|
|
||||||
// At this point the cartridge can be in one of 6 states:
|
// At this point the cartridge can be in one of 6 states:
|
||||||
@ -122,34 +127,38 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) {
|
|||||||
// => only dumping/flashing available
|
// => only dumping/flashing available
|
||||||
// - unlocked, no private data, blank
|
// - unlocked, no private data, blank
|
||||||
// => only dumping/flashing available
|
// => only dumping/flashing available
|
||||||
auto result = APP->_identifyResult;
|
IdentifyState state;
|
||||||
char name[96];
|
char name[96];
|
||||||
|
|
||||||
if (result == cartdb::IDENTIFIED)
|
if (APP->_identified) {
|
||||||
|
state = IDENTIFIED;
|
||||||
APP->_identified->getDisplayName(name, sizeof(name));
|
APP->_identified->getDisplayName(name, sizeof(name));
|
||||||
|
} else {
|
||||||
|
state = APP->_dump.isDataEmpty() ? BLANK_CART : UNIDENTIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
if (flags & cart::PRIVATE_DATA_OK) {
|
if (dump.flags & cart::DUMP_PRIVATE_DATA_OK) {
|
||||||
ptr += snprintf(ptr, end - ptr, STRH(_UNLOCKED_PROMPTS[result]), name);
|
ptr += snprintf(ptr, end - ptr, STRH(_UNLOCKED_PROMPTS[state]), name);
|
||||||
|
|
||||||
_prompt = STR("CartInfoScreen.prompt.unlocked");
|
_prompt = STR("CartInfoScreen.prompt.unlocked");
|
||||||
} else {
|
} else {
|
||||||
ptr += snprintf(ptr, end - ptr, STRH(_LOCKED_PROMPTS[result]), name);
|
ptr += snprintf(ptr, end - ptr, STRH(_LOCKED_PROMPTS[state]), name);
|
||||||
|
|
||||||
_prompt = STR("CartInfoScreen.prompt.locked");
|
_prompt = STR("CartInfoScreen.prompt.locked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CartInfoScreen::update(ui::Context &ctx) {
|
void CartInfoScreen::update(ui::Context &ctx) {
|
||||||
auto &_cart = *(APP->_cart);
|
auto &dump = APP->_dump;
|
||||||
|
|
||||||
if (!_cart.chipType)
|
if (!dump.chipType)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ctx.buttons.pressed(ui::BTN_START)) {
|
if (ctx.buttons.pressed(ui::BTN_START)) {
|
||||||
/*if (_cart.flags & cart::PRIVATE_DATA_OK)
|
if (dump.flags & cart::DUMP_PRIVATE_DATA_OK)
|
||||||
ctx.show(APP->_cartActionsScreen, false, true);
|
ctx.show(APP->_cartActionsScreen, false, true);
|
||||||
else
|
else
|
||||||
ctx.show(APP->_unlockKeyScreen, false, true);*/
|
ctx.show(APP->_unlockKeyScreen, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +169,7 @@ enum SpecialUnlockKeyEntry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
int UnlockKeyScreen::_getSpecialEntryOffset(ui::Context &ctx) const {
|
int UnlockKeyScreen::_getSpecialEntryOffset(ui::Context &ctx) const {
|
||||||
return (APP->_identifyResult == cartdb::IDENTIFIED)
|
return APP->_identified ? ENTRY_AUTO_UNLOCK : ENTRY_CUSTOM_KEY;
|
||||||
? ENTRY_AUTO_UNLOCK : ENTRY_CUSTOM_KEY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *UnlockKeyScreen::_getItemName(ui::Context &ctx, int index) const {
|
const char *UnlockKeyScreen::_getItemName(ui::Context &ctx, int index) const {
|
||||||
@ -180,7 +188,7 @@ const char *UnlockKeyScreen::_getItemName(ui::Context &ctx, int index) const {
|
|||||||
return _nullKeyItem;
|
return _nullKeyItem;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
APP->_db.getEntry(index).getDisplayName(name, sizeof(name));
|
APP->_db.get(index)->getDisplayName(name, sizeof(name));
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +201,7 @@ void UnlockKeyScreen::show(ui::Context &ctx, bool goBack) {
|
|||||||
_customKeyItem = STR("UnlockKeyScreen.customKey");
|
_customKeyItem = STR("UnlockKeyScreen.customKey");
|
||||||
_nullKeyItem = STR("UnlockKeyScreen.nullKey");
|
_nullKeyItem = STR("UnlockKeyScreen.nullKey");
|
||||||
|
|
||||||
_listLength = APP->_db.numEntries - _getSpecialEntryOffset(ctx);
|
_listLength = APP->_db.getNumEntries() - _getSpecialEntryOffset(ctx);
|
||||||
|
|
||||||
ListScreen::show(ctx, goBack);
|
ListScreen::show(ctx, goBack);
|
||||||
}
|
}
|
||||||
@ -206,9 +214,9 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
|
|||||||
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case ENTRY_AUTO_UNLOCK:
|
case ENTRY_AUTO_UNLOCK:
|
||||||
memcpy(
|
__builtin_memcpy(
|
||||||
APP->_cart->dataKey, APP->_identified->dataKey,
|
APP->_dump.dataKey, APP->_identified->dataKey,
|
||||||
sizeof(APP->_cart->dataKey)
|
sizeof(APP->_dump.dataKey)
|
||||||
);
|
);
|
||||||
ctx.show(APP->_unlockConfirmScreen, false, true);
|
ctx.show(APP->_unlockConfirmScreen, false, true);
|
||||||
break;
|
break;
|
||||||
@ -218,14 +226,16 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ENTRY_NULL_KEY:
|
case ENTRY_NULL_KEY:
|
||||||
memset(APP->_cart->dataKey, 0, sizeof(APP->_cart->dataKey));
|
__builtin_memset(
|
||||||
|
APP->_dump.dataKey, 0, sizeof(APP->_dump.dataKey)
|
||||||
|
);
|
||||||
ctx.show(APP->_unlockConfirmScreen, false, true);
|
ctx.show(APP->_unlockConfirmScreen, false, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
memcpy(
|
__builtin_memcpy(
|
||||||
APP->_cart->dataKey, APP->_db.getEntry(index).dataKey,
|
APP->_dump.dataKey, APP->_db.get(index)->dataKey,
|
||||||
sizeof(APP->_cart->dataKey)
|
sizeof(APP->_dump.dataKey)
|
||||||
);
|
);
|
||||||
ctx.show(APP->_unlockConfirmScreen, false, true);
|
ctx.show(APP->_unlockConfirmScreen, false, true);
|
||||||
}
|
}
|
||||||
@ -236,7 +246,7 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
|
|||||||
|
|
||||||
void UnlockConfirmScreen::show(ui::Context &ctx, bool goBack) {
|
void UnlockConfirmScreen::show(ui::Context &ctx, bool goBack) {
|
||||||
_title = STR("UnlockConfirmScreen.title");
|
_title = STR("UnlockConfirmScreen.title");
|
||||||
_body = STRH(_CART_TYPES[APP->_cart->chipType].warning);
|
_body = STRH(_CART_TYPES[APP->_dump.chipType].warning);
|
||||||
_buttons[0] = STR("UnlockConfirmScreen.no");
|
_buttons[0] = STR("UnlockConfirmScreen.no");
|
||||||
_buttons[1] = STR("UnlockConfirmScreen.yes");
|
_buttons[1] = STR("UnlockConfirmScreen.yes");
|
||||||
|
|
||||||
@ -260,7 +270,7 @@ void UnlockConfirmScreen::update(ui::Context &ctx) {
|
|||||||
|
|
||||||
void UnlockErrorScreen::show(ui::Context &ctx, bool goBack) {
|
void UnlockErrorScreen::show(ui::Context &ctx, bool goBack) {
|
||||||
_title = STR("UnlockErrorScreen.title");
|
_title = STR("UnlockErrorScreen.title");
|
||||||
_body = STRH(_CART_TYPES[APP->_cart->chipType].error);
|
_body = STRH(_CART_TYPES[APP->_dump.chipType].error);
|
||||||
_buttons[0] = STR("UnlockErrorScreen.ok");
|
_buttons[0] = STR("UnlockErrorScreen.ok");
|
||||||
|
|
||||||
_numButtons = 1;
|
_numButtons = 1;
|
||||||
|
@ -159,12 +159,12 @@ size_t AssetLoader::loadFontMetrics(gpu::Font &output, const char *path) {
|
|||||||
|
|
||||||
/* String table manager */
|
/* String table manager */
|
||||||
|
|
||||||
const char *StringTable::get(util::Hash id) {
|
const char *StringTable::get(util::Hash id) const {
|
||||||
if (!data.ptr)
|
if (!ptr)
|
||||||
return "missingno";
|
return "missingno";
|
||||||
|
|
||||||
auto blob = reinterpret_cast<const char *>(data.ptr);
|
auto blob = reinterpret_cast<const char *>(ptr);
|
||||||
auto table = reinterpret_cast<const StringTableEntry *>(data.ptr);
|
auto table = reinterpret_cast<const StringTableEntry *>(ptr);
|
||||||
|
|
||||||
auto entry = &table[id % TABLE_BUCKET_COUNT];
|
auto entry = &table[id % TABLE_BUCKET_COUNT];
|
||||||
|
|
||||||
@ -181,7 +181,9 @@ const char *StringTable::get(util::Hash id) {
|
|||||||
return "missingno";
|
return "missingno";
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t StringTable::format(char *buffer, size_t length, util::Hash id, ...) {
|
size_t StringTable::format(
|
||||||
|
char *buffer, size_t length, util::Hash id, ...
|
||||||
|
) const {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, id);
|
va_start(ap, id);
|
||||||
|
@ -66,16 +66,14 @@ public:
|
|||||||
uint16_t offset, chained;
|
uint16_t offset, chained;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringTable {
|
class StringTable : public Asset {
|
||||||
public:
|
public:
|
||||||
Asset data;
|
inline const char *operator[](util::Hash id) const {
|
||||||
|
|
||||||
inline const char *operator[](util::Hash id) {
|
|
||||||
return get(id);
|
return get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *get(util::Hash id);
|
const char *get(util::Hash id) const;
|
||||||
size_t format(char *buffer, size_t length, util::Hash id, ...);
|
size_t format(char *buffer, size_t length, util::Hash id, ...) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* QR code encoder */
|
/* QR code encoder */
|
||||||
|
123
src/cart.hpp
123
src/cart.hpp
@ -1,123 +0,0 @@
|
|||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "zs01.hpp"
|
|
||||||
|
|
||||||
namespace cart {
|
|
||||||
|
|
||||||
enum Error {
|
|
||||||
NO_ERROR = 0,
|
|
||||||
UNSUPPORTED_OP = 1,
|
|
||||||
DS2401_NO_RESP = 2,
|
|
||||||
DS2401_ID_ERROR = 3,
|
|
||||||
X76_NACK = 4,
|
|
||||||
X76_POLL_FAIL = 5,
|
|
||||||
X76_VERIFY_FAIL = 6,
|
|
||||||
ZS01_NACK = 7,
|
|
||||||
ZS01_ERROR = 8,
|
|
||||||
ZS01_CRC_MISMATCH = 9
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ChipType : uint8_t {
|
|
||||||
TYPE_NONE = 0,
|
|
||||||
TYPE_X76F041 = 1,
|
|
||||||
TYPE_X76F100 = 2,
|
|
||||||
TYPE_ZS01 = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CartFlag : uint8_t {
|
|
||||||
HAS_DIGITAL_IO = 1 << 0,
|
|
||||||
HAS_DS2401 = 1 << 1,
|
|
||||||
CONFIG_OK = 1 << 2,
|
|
||||||
SYSTEM_ID_OK = 1 << 3,
|
|
||||||
CART_ID_OK = 1 << 4,
|
|
||||||
ZS_ID_OK = 1 << 5,
|
|
||||||
PUBLIC_DATA_OK = 1 << 6,
|
|
||||||
PRIVATE_DATA_OK = 1 << 7
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr int DUMP_VERSION = 1;
|
|
||||||
static constexpr int NUM_CHIP_TYPES = 4;
|
|
||||||
static constexpr int MAX_QR_STRING_LENGTH = 0x600;
|
|
||||||
|
|
||||||
class Cart;
|
|
||||||
|
|
||||||
size_t getDataLength(ChipType type);
|
|
||||||
Cart *createCart(void);
|
|
||||||
|
|
||||||
class [[gnu::packed]] Cart {
|
|
||||||
public:
|
|
||||||
uint8_t version;
|
|
||||||
ChipType chipType;
|
|
||||||
|
|
||||||
uint8_t flags, _state;
|
|
||||||
uint8_t dataKey[8], config[8];
|
|
||||||
uint8_t systemID[8], cartID[8], zsID[8];
|
|
||||||
|
|
||||||
inline size_t getDumpLength(void) {
|
|
||||||
return getDataLength(chipType) + 44;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cart(void);
|
|
||||||
size_t toQRString(char *output);
|
|
||||||
virtual Error readSystemID(void);
|
|
||||||
virtual Error readCartID(void) { return UNSUPPORTED_OP; }
|
|
||||||
virtual Error readPublicData(void) { return UNSUPPORTED_OP; }
|
|
||||||
virtual Error readPrivateData(void) { return UNSUPPORTED_OP; }
|
|
||||||
virtual Error writeData(void) { return UNSUPPORTED_OP; }
|
|
||||||
virtual Error erase(void) { return UNSUPPORTED_OP; }
|
|
||||||
virtual Error setDataKey(const uint8_t *newKey) { return UNSUPPORTED_OP; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] X76Cart : public Cart {
|
|
||||||
protected:
|
|
||||||
Error _readDS2401(void);
|
|
||||||
Error _x76Command(uint8_t command, uint8_t param, uint8_t pollByte) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
uint8_t data[512];
|
|
||||||
|
|
||||||
Error readCartID(void);
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] X76F041Cart : public X76Cart {
|
|
||||||
public:
|
|
||||||
uint8_t data[512];
|
|
||||||
|
|
||||||
X76F041Cart(void);
|
|
||||||
Error readPrivateData(void);
|
|
||||||
Error writeData(void);
|
|
||||||
Error erase(void);
|
|
||||||
Error setDataKey(const uint8_t *newKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] X76F100Cart : public X76Cart {
|
|
||||||
public:
|
|
||||||
uint8_t data[112];
|
|
||||||
|
|
||||||
X76F100Cart(void);
|
|
||||||
//Error readPrivateData(void);
|
|
||||||
//Error writeData(void);
|
|
||||||
//Error erase(void);
|
|
||||||
//Error setDataKey(const uint8_t *newKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] ZS01Cart : public Cart {
|
|
||||||
private:
|
|
||||||
Error _transact(zs01::Packet &request, zs01::Packet &response);
|
|
||||||
|
|
||||||
public:
|
|
||||||
uint8_t data[112];
|
|
||||||
|
|
||||||
ZS01Cart(void);
|
|
||||||
Error readCartID(void);
|
|
||||||
Error readPublicData(void);
|
|
||||||
Error readPrivateData(void);
|
|
||||||
Error writeData(void);
|
|
||||||
Error erase(void);
|
|
||||||
Error setDataKey(const uint8_t *newKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
292
src/cartdata.cpp
Normal file
292
src/cartdata.cpp
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "asset.hpp"
|
||||||
|
#include "cartdata.hpp"
|
||||||
|
#include "cartio.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
|
||||||
|
bool isValidRegion(const char *region) {
|
||||||
|
// Character 0: region (A=Asia?, E=Europe, J=Japan, K=Korea, S=?, U=US)
|
||||||
|
// Character 1: type/variant (A-F=regular, R-W=e-Amusement, X-Z=?)
|
||||||
|
// Characters 2-4: game revision (A-D or Z00-Z99, optional)
|
||||||
|
if (!region[0] || !__builtin_strchr("AEJKSU", region[0]))
|
||||||
|
return false;
|
||||||
|
if (!region[1] || !__builtin_strchr("ABCDEFRSTUVWXYZ", region[1]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (region[2]) {
|
||||||
|
if (!__builtin_strchr("ABCDZ", region[2]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (region[2] == 'Z') {
|
||||||
|
if (!__builtin_isdigit(region[3]) || !__builtin_isdigit(region[4]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
region += 2;
|
||||||
|
}
|
||||||
|
if (region[3])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common data structures */
|
||||||
|
|
||||||
|
void BasicHeader::updateChecksum(void) {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
||||||
|
|
||||||
|
checksum = uint8_t((value & 0xff) ^ 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BasicHeader::validateChecksum(void) const {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
||||||
|
|
||||||
|
return (checksum == ((value & 0xff) ^ 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtendedHeader::updateChecksum(void) {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint16_t *>(this), 14);
|
||||||
|
|
||||||
|
checksum = uint16_t(value & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtendedHeader::validateChecksum(void) const {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint16_t *>(this), 14);
|
||||||
|
|
||||||
|
return (checksum == (value & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t IdentifierSet::getFlags(void) const {
|
||||||
|
uint8_t flags = 0;
|
||||||
|
|
||||||
|
if (!traceID.isEmpty())
|
||||||
|
flags |= DATA_HAS_TRACE_ID;
|
||||||
|
if (!cartID.isEmpty())
|
||||||
|
flags |= DATA_HAS_CART_ID;
|
||||||
|
if (!installID.isEmpty())
|
||||||
|
flags |= DATA_HAS_INSTALL_ID;
|
||||||
|
if (!systemID.isEmpty())
|
||||||
|
flags |= DATA_HAS_SYSTEM_ID;
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentifierSet::setInstallID(uint8_t prefix) {
|
||||||
|
installID.clear();
|
||||||
|
|
||||||
|
installID.data[0] = prefix;
|
||||||
|
installID.updateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentifierSet::updateTraceID(uint8_t prefix, int param) {
|
||||||
|
traceID.clear();
|
||||||
|
|
||||||
|
uint8_t *input = &cartID.data[1];
|
||||||
|
uint16_t checksum = 0;
|
||||||
|
|
||||||
|
switch (prefix) {
|
||||||
|
case 0x81:
|
||||||
|
// TODO: reverse engineer this TID format
|
||||||
|
traceID.data[2] = 0;
|
||||||
|
traceID.data[5] = 0;
|
||||||
|
traceID.data[6] = 0;
|
||||||
|
|
||||||
|
LOG("prefix=0x81");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x82:
|
||||||
|
for (size_t i = 0; i < (sizeof(cartID.data) - 2); i++) {
|
||||||
|
uint8_t value = *(input++);
|
||||||
|
|
||||||
|
for (int j = 0; j < 8; j++, value >>= 1) {
|
||||||
|
if (value % 2)
|
||||||
|
checksum ^= 1 << (i % param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traceID.data[1] = checksum >> 8;
|
||||||
|
traceID.data[2] = checksum & 0xff;
|
||||||
|
|
||||||
|
LOG("prefix=0x82, checksum=%04x", checksum);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG("unknown prefix 0x%02x", prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceID.data[0] = prefix;
|
||||||
|
traceID.updateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data formats */
|
||||||
|
|
||||||
|
bool CartData::validate(void) {
|
||||||
|
char region[8];
|
||||||
|
|
||||||
|
if (getRegion(region) < REGION_MIN_LENGTH)
|
||||||
|
return false;
|
||||||
|
if (!isValidRegion(region))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto id = getIdentifiers();
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
uint8_t idFlags = flags & (
|
||||||
|
DATA_HAS_TRACE_ID | DATA_HAS_CART_ID | DATA_HAS_INSTALL_ID |
|
||||||
|
DATA_HAS_SYSTEM_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (id->getFlags() != idFlags)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SimpleCartData::getRegion(char *output) const {
|
||||||
|
auto header = _getHeader();
|
||||||
|
|
||||||
|
__builtin_memcpy(output, header->region, 4);
|
||||||
|
output[4] = 0;
|
||||||
|
|
||||||
|
return __builtin_strlen(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BasicCartData::getRegion(char *output) const {
|
||||||
|
auto header = _getHeader();
|
||||||
|
|
||||||
|
output[0] = header->region[0];
|
||||||
|
output[1] = header->region[1];
|
||||||
|
output[2] = 0;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentifierSet *BasicCartData::getIdentifiers(void) {
|
||||||
|
return reinterpret_cast<IdentifierSet *>(&_dump.data[sizeof(BasicHeader)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BasicCartData::validate(void) {
|
||||||
|
return (CartData::validate() && _getHeader()->validateChecksum());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ExtendedCartData::getRegion(char *output) const {
|
||||||
|
auto header = _getHeader();
|
||||||
|
|
||||||
|
__builtin_memcpy(output, header->region, 4);
|
||||||
|
output[4] = 0;
|
||||||
|
|
||||||
|
return __builtin_strlen(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentifierSet *ExtendedCartData::getIdentifiers(void) {
|
||||||
|
if (!(flags & DATA_HAS_PUBLIC_SECTION))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return reinterpret_cast<IdentifierSet *>(
|
||||||
|
&_dump.data[sizeof(ExtendedHeader) + sizeof(PublicIdentifierSet)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtendedCartData::flush(void) {
|
||||||
|
// Copy over the private identifiers to the public data area. On X76F041
|
||||||
|
// carts this area is in the last sector, while on ZS01 carts it is placed
|
||||||
|
// in the first 32 bytes.
|
||||||
|
auto pri = getIdentifiers();
|
||||||
|
auto pub = reinterpret_cast<PublicIdentifierSet *>(
|
||||||
|
&_getPublicData()[sizeof(ExtendedHeader)]
|
||||||
|
);
|
||||||
|
|
||||||
|
pub->traceID.copyFrom(pri->traceID.data);
|
||||||
|
pub->cartID.copyFrom(pri->cartID.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtendedCartData::validate(void) {
|
||||||
|
return (CartData::validate() && _getHeader()->validateChecksum());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data format identification */
|
||||||
|
|
||||||
|
struct KnownFormat {
|
||||||
|
public:
|
||||||
|
FormatType formatType;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int _NUM_KNOWN_FORMATS = 8;
|
||||||
|
|
||||||
|
// More variants may exist.
|
||||||
|
static const KnownFormat _KNOWN_FORMATS[_NUM_KNOWN_FORMATS]{
|
||||||
|
{ SIMPLE, DATA_HAS_PUBLIC_SECTION },
|
||||||
|
{ BASIC, 0 },
|
||||||
|
{ BASIC, DATA_HAS_TRACE_ID },
|
||||||
|
{ BASIC, DATA_HAS_TRACE_ID | DATA_HAS_CART_ID },
|
||||||
|
{ BASIC, DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID },
|
||||||
|
{ BASIC, DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
||||||
|
| DATA_HAS_INSTALL_ID | DATA_HAS_SYSTEM_ID },
|
||||||
|
{ EXTENDED, 0 },
|
||||||
|
{ EXTENDED, DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
||||||
|
| DATA_HAS_INSTALL_ID | DATA_HAS_SYSTEM_ID | DATA_HAS_PUBLIC_SECTION },
|
||||||
|
};
|
||||||
|
|
||||||
|
CartData *createCartData(Dump &dump, FormatType formatType, uint8_t flags) {
|
||||||
|
switch (formatType) {
|
||||||
|
case SIMPLE:
|
||||||
|
return new SimpleCartData(dump, flags);
|
||||||
|
|
||||||
|
case BASIC:
|
||||||
|
return new BasicCartData(dump, flags);
|
||||||
|
|
||||||
|
case EXTENDED:
|
||||||
|
return new ExtendedCartData(dump, flags);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return new CartData(dump, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CartData *createCartData(Dump &dump) {
|
||||||
|
for (int i = 0; i < _NUM_KNOWN_FORMATS; i++) {
|
||||||
|
auto &fmt = _KNOWN_FORMATS[i];
|
||||||
|
CartData *data = createCartData(dump, fmt.formatType, fmt.flags);
|
||||||
|
|
||||||
|
if (data->validate()) {
|
||||||
|
LOG("found known format, index=%d", i);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("unrecognized dump format");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cartridge database */
|
||||||
|
|
||||||
|
const DBEntry *CartDB::lookupEntry(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);
|
||||||
|
|
||||||
|
for (size_t step = getNumEntries() / 2; step; step /= 2) {
|
||||||
|
auto entry = &offset[step];
|
||||||
|
int diff = entry->compare(code, region);
|
||||||
|
|
||||||
|
if (!diff)
|
||||||
|
return entry;
|
||||||
|
else if (diff > 0) // TODO: could be diff < 0
|
||||||
|
offset = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("%s %s not found", code, region);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
213
src/cartdata.hpp
Normal file
213
src/cartdata.hpp
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "asset.hpp"
|
||||||
|
#include "cartio.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* Definitions */
|
||||||
|
|
||||||
|
enum FormatType : uint8_t {
|
||||||
|
BLANK = 0,
|
||||||
|
SIMPLE = 1,
|
||||||
|
BASIC = 2,
|
||||||
|
EXTENDED = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
// | | Simple | Basic | Extended |
|
||||||
|
// | :---------------------- | :-------- | :------- | :-------- |
|
||||||
|
// | DATA_HAS_CODE_PREFIX | | Optional | Mandatory |
|
||||||
|
// | DATA_HAS_*_ID | | Optional | Optional |
|
||||||
|
// | DATA_HAS_PUBLIC_SECTION | Mandatory | | Optional |
|
||||||
|
|
||||||
|
enum FormatFlags : uint8_t {
|
||||||
|
DATA_HAS_CODE_PREFIX = 1 << 0,
|
||||||
|
DATA_HAS_TRACE_ID = 1 << 1,
|
||||||
|
DATA_HAS_CART_ID = 1 << 2,
|
||||||
|
DATA_HAS_INSTALL_ID = 1 << 3,
|
||||||
|
DATA_HAS_SYSTEM_ID = 1 << 4,
|
||||||
|
DATA_HAS_PUBLIC_SECTION = 1 << 5
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr size_t CODE_LENGTH = 5;
|
||||||
|
static constexpr size_t REGION_MIN_LENGTH = 2;
|
||||||
|
static constexpr size_t REGION_MAX_LENGTH = 5;
|
||||||
|
|
||||||
|
bool isValidRegion(const char *region);
|
||||||
|
|
||||||
|
/* Common data structures */
|
||||||
|
|
||||||
|
class [[gnu::packed]] SimpleHeader {
|
||||||
|
public:
|
||||||
|
char region[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] BasicHeader {
|
||||||
|
public:
|
||||||
|
char region[2], codePrefix[2];
|
||||||
|
uint8_t checksum, _pad[3];
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] ExtendedHeader {
|
||||||
|
public:
|
||||||
|
char code[8];
|
||||||
|
uint16_t year; // BCD, can be little endian, big endian or zero
|
||||||
|
char region[4];
|
||||||
|
uint16_t checksum;
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] IdentifierSet {
|
||||||
|
public:
|
||||||
|
Identifier traceID, cartID, installID, systemID; // aka TID, SID, MID, XID
|
||||||
|
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(this, 0, sizeof(IdentifierSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getFlags(void) const;
|
||||||
|
void setInstallID(uint8_t prefix);
|
||||||
|
void updateTraceID(uint8_t prefix, int param);
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] PublicIdentifierSet {
|
||||||
|
public:
|
||||||
|
Identifier traceID, cartID; // aka TID, SID
|
||||||
|
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(this, 0, sizeof(PublicIdentifierSet));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Data formats */
|
||||||
|
|
||||||
|
class CartData {
|
||||||
|
protected:
|
||||||
|
Dump &_dump;
|
||||||
|
|
||||||
|
inline uint8_t *_getPublicData(void) const {
|
||||||
|
if (flags & DATA_HAS_PUBLIC_SECTION)
|
||||||
|
return &_dump.data[_dump.getChipSize().publicDataOffset];
|
||||||
|
else
|
||||||
|
return _dump.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
inline CartData(Dump &dump, uint8_t flags = 0)
|
||||||
|
: _dump(dump), flags(flags) {}
|
||||||
|
|
||||||
|
virtual size_t getRegion(char *output) const { return 0; }
|
||||||
|
virtual IdentifierSet *getIdentifiers(void) { return nullptr; }
|
||||||
|
virtual void flush(void) {}
|
||||||
|
virtual bool validate(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SimpleCartData : public CartData {
|
||||||
|
private:
|
||||||
|
inline SimpleHeader *_getHeader(void) const {
|
||||||
|
return reinterpret_cast<SimpleHeader *>(_getPublicData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline SimpleCartData(Dump &dump, uint8_t flags = 0)
|
||||||
|
: CartData(dump, flags | DATA_HAS_PUBLIC_SECTION) {}
|
||||||
|
|
||||||
|
size_t getRegion(char *output) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BasicCartData : public CartData {
|
||||||
|
private:
|
||||||
|
inline BasicHeader *_getHeader(void) const {
|
||||||
|
return reinterpret_cast<BasicHeader *>(_getPublicData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline BasicCartData(Dump &dump, uint8_t flags = 0)
|
||||||
|
: CartData(dump, flags) {}
|
||||||
|
|
||||||
|
size_t getRegion(char *output) const;
|
||||||
|
IdentifierSet *getIdentifiers(void);
|
||||||
|
bool validate(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExtendedCartData : public CartData {
|
||||||
|
private:
|
||||||
|
inline ExtendedHeader *_getHeader(void) const {
|
||||||
|
return reinterpret_cast<ExtendedHeader *>(_getPublicData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline ExtendedCartData(Dump &dump, uint8_t flags = 0)
|
||||||
|
: CartData(dump, flags | DATA_HAS_CODE_PREFIX) {}
|
||||||
|
|
||||||
|
size_t getRegion(char *output) const;
|
||||||
|
IdentifierSet *getIdentifiers(void);
|
||||||
|
void flush(void);
|
||||||
|
bool validate(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
CartData *createCartData(Dump &dump, FormatType formatType, uint8_t flags = 0);
|
||||||
|
CartData *createCartData(Dump &dump);
|
||||||
|
|
||||||
|
/* Cartridge database */
|
||||||
|
|
||||||
|
class [[gnu::packed]] DBEntry {
|
||||||
|
public:
|
||||||
|
ChipType chipType;
|
||||||
|
FormatType formatType;
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
uint8_t traceIDPrefix, traceIDParam, installIDPrefix, _reserved[2];
|
||||||
|
uint8_t dataKey[8];
|
||||||
|
char code[8], region[8], name[64];
|
||||||
|
|
||||||
|
inline int compare(const char *_code, const char *_region) const {
|
||||||
|
int diff = __builtin_strncmp(code, _code, CODE_LENGTH + 1);
|
||||||
|
if (diff)
|
||||||
|
return diff;
|
||||||
|
|
||||||
|
// If the provided region string is longer than this entry's region but
|
||||||
|
// the first few characters match, return 0. Do not however match
|
||||||
|
// strings shorter than this entry's region.
|
||||||
|
return __builtin_strncmp(region, _region, __builtin_strlen(region));
|
||||||
|
}
|
||||||
|
inline int getDisplayName(char *output, size_t length) const {
|
||||||
|
return snprintf(output, length, "%s %s\t%s", code, region, name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CartDB : public asset::Asset {
|
||||||
|
public:
|
||||||
|
inline const DBEntry *operator[](int index) const {
|
||||||
|
return get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const DBEntry *get(int index) const {
|
||||||
|
auto entries = reinterpret_cast<const DBEntry *>(ptr);
|
||||||
|
|
||||||
|
if (!entries)
|
||||||
|
return nullptr;
|
||||||
|
if ((index * sizeof(DBEntry)) >= length)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &entries[index];
|
||||||
|
}
|
||||||
|
inline size_t getNumEntries(void) const {
|
||||||
|
return length / sizeof(DBEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DBEntry *lookupEntry(const char *code, const char *region) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "asset.hpp"
|
|
||||||
#include "cart.hpp"
|
|
||||||
#include "cartdb.hpp"
|
|
||||||
#include "util.hpp"
|
|
||||||
|
|
||||||
namespace cartdb {
|
|
||||||
|
|
||||||
bool CartDB::init(void) {
|
|
||||||
if (!data.ptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto &firstEntry = *reinterpret_cast<const Entry *>(data.ptr);
|
|
||||||
|
|
||||||
if (firstEntry.version != ENTRY_VERSION) {
|
|
||||||
LOG("unsupported DB version %d", firstEntry.version);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_chipType = firstEntry.chipType;
|
|
||||||
_entryLength = sizeof(Entry) + cart::getDataLength(_chipType);
|
|
||||||
numEntries = data.length / _entryLength;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentifyResult CartDB::identifyCart(cart::Cart &cart) const {
|
|
||||||
// TODO: implement this
|
|
||||||
|
|
||||||
LOG("no matching game found");
|
|
||||||
return UNIDENTIFIED;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "asset.hpp"
|
|
||||||
#include "cart.hpp"
|
|
||||||
|
|
||||||
namespace cartdb {
|
|
||||||
|
|
||||||
enum IdentifyResult {
|
|
||||||
UNIDENTIFIED = 0,
|
|
||||||
IDENTIFIED = 1,
|
|
||||||
BLANK = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
enum EntryFlag : uint8_t {
|
|
||||||
HAS_SYSTEM_ID = 1 << 0,
|
|
||||||
HAS_CART_ID = 1 << 1,
|
|
||||||
HAS_ZS_ID = 1 << 2,
|
|
||||||
HAS_CHECKSUM = 1 << 3
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr int ENTRY_VERSION = 1;
|
|
||||||
|
|
||||||
class [[gnu::packed]] Entry {
|
|
||||||
public:
|
|
||||||
uint8_t version;
|
|
||||||
cart::ChipType chipType;
|
|
||||||
|
|
||||||
uint8_t flags, _reserved;
|
|
||||||
uint8_t systemIDOffset, cartIDOffset, zsIDOffset, checksumOffset;
|
|
||||||
char code[8], region[8], name[64];
|
|
||||||
uint8_t dataKey[8], config[8];
|
|
||||||
|
|
||||||
inline int getDisplayName(char *output, size_t length) const {
|
|
||||||
return snprintf(output, length, "%s %s\t%s", code, region, name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] X76F041Entry : public Entry {
|
|
||||||
public:
|
|
||||||
uint8_t data[512];
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] X76F100Entry : public Entry {
|
|
||||||
public:
|
|
||||||
uint8_t data[112];
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] ZS01Entry : public Entry {
|
|
||||||
public:
|
|
||||||
uint8_t data[112];
|
|
||||||
};
|
|
||||||
|
|
||||||
class CartDB {
|
|
||||||
private:
|
|
||||||
cart::ChipType _chipType;
|
|
||||||
size_t _entryLength;
|
|
||||||
|
|
||||||
public:
|
|
||||||
asset::Asset data;
|
|
||||||
size_t numEntries;
|
|
||||||
|
|
||||||
inline CartDB(void)
|
|
||||||
: _entryLength(0), numEntries(0) {}
|
|
||||||
inline const Entry &getEntry(int index) const {
|
|
||||||
auto _data = reinterpret_cast<const uint8_t *>(data.ptr);
|
|
||||||
//assert(data);
|
|
||||||
|
|
||||||
return *reinterpret_cast<const Entry *>(&_data[index * _entryLength]);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool init(void);
|
|
||||||
IdentifyResult identifyCart(cart::Cart &cart) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,96 +1,64 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "vendor/miniz.h"
|
#include "vendor/miniz.h"
|
||||||
#include "cart.hpp"
|
#include "cartio.hpp"
|
||||||
#include "io.hpp"
|
#include "io.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "zs01.hpp"
|
#include "zs01.hpp"
|
||||||
|
|
||||||
namespace cart {
|
namespace cart {
|
||||||
|
|
||||||
/* Common functions */
|
/* Common data structures */
|
||||||
|
|
||||||
enum ChipID : uint32_t {
|
void Identifier::updateChecksum(void) {
|
||||||
_ID_X76F041 = 0x55aa5519,
|
data[7] = (util::sum(data, 7) & 0xff) ^ 0xff;
|
||||||
_ID_X76F100 = 0x55aa0019,
|
|
||||||
_ID_ZS01 = 0x5a530001
|
|
||||||
};
|
|
||||||
|
|
||||||
static const size_t _DATA_LENGTHS[NUM_CHIP_TYPES]{ 0, 512, 112, 112 };
|
|
||||||
|
|
||||||
static constexpr int _X76_MAX_ACK_POLLS = 5;
|
|
||||||
static constexpr int _X76_WRITE_DELAY = 10000;
|
|
||||||
static constexpr int _ZS01_PACKET_DELAY = 30000;
|
|
||||||
|
|
||||||
static bool _validateID(const uint8_t *id) {
|
|
||||||
if (!id[0] || (id[0] == 0xff)) {
|
|
||||||
LOG("invalid device type 0x%02x", id[0]);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t crc = util::dsCRC8(id, 7);
|
bool Identifier::validateChecksum(void) const {
|
||||||
if (crc != id[7]) {
|
uint8_t value = (util::sum(data, 7) & 0xff) ^ 0xff;
|
||||||
LOG("CRC mismatch, exp=0x%02x, got=0x%02x", crc, id[7]);
|
|
||||||
|
if (value != data[7]) {
|
||||||
|
LOG("checksum mismatch, exp=0x%02x, got=0x%02x", value, data[7]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t getDataLength(ChipType type) {
|
void Identifier::updateDSCRC(void) {
|
||||||
return _DATA_LENGTHS[type];
|
data[7] = util::dsCRC8(data, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cart *createCart(void) {
|
bool Identifier::validateDSCRC(void) const {
|
||||||
if (!io::getCartInsertionStatus()) {
|
uint8_t value = util::dsCRC8(data, 7);
|
||||||
LOG("DSR not asserted");
|
|
||||||
return new Cart();
|
if (value != data[7]) {
|
||||||
|
LOG("CRC mismatch, exp=0x%02x, got=0x%02x", value, data[7]);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t id1 = io::i2cResetX76();
|
return true;
|
||||||
LOG("id1=0x%08x", id1);
|
|
||||||
|
|
||||||
switch (id1) {
|
|
||||||
case _ID_X76F041:
|
|
||||||
return new X76F041Cart();
|
|
||||||
|
|
||||||
//case _ID_X76F100:
|
|
||||||
//return new X76F100Cart();
|
|
||||||
|
|
||||||
default:
|
|
||||||
uint32_t id2 = io::i2cResetZS01();
|
|
||||||
LOG("id2=0x%08x", id2);
|
|
||||||
|
|
||||||
if (id2 == _ID_ZS01)
|
|
||||||
return new ZS01Cart();
|
|
||||||
|
|
||||||
return new Cart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base cart class */
|
/* Dump structure and utilities */
|
||||||
|
|
||||||
Cart::Cart(void) {
|
const ChipSize CHIP_SIZES[NUM_CHIP_TYPES]{
|
||||||
memset(&version, 0, 44);
|
{ .dataLength = 0, .publicDataOffset = 0, .publicDataLength = 0 },
|
||||||
|
{ .dataLength = 512, .publicDataOffset = 384, .publicDataLength = 128 },
|
||||||
|
{ .dataLength = 112, .publicDataOffset = 0, .publicDataLength = 0 },
|
||||||
|
{ .dataLength = 112, .publicDataOffset = 0, .publicDataLength = 32 }
|
||||||
|
};
|
||||||
|
|
||||||
version = DUMP_VERSION;
|
size_t Dump::toQRString(char *output) const {
|
||||||
chipType = TYPE_NONE;
|
|
||||||
flags = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Cart::toQRString(char *output) {
|
|
||||||
uint8_t compressed[MAX_QR_STRING_LENGTH];
|
uint8_t compressed[MAX_QR_STRING_LENGTH];
|
||||||
size_t uncompLength = getDumpLength();
|
size_t uncompLength = getDumpLength();
|
||||||
size_t compLength = MAX_QR_STRING_LENGTH;
|
size_t compLength = MAX_QR_STRING_LENGTH;
|
||||||
|
|
||||||
int error = mz_compress2(
|
int error = mz_compress2(
|
||||||
compressed,
|
compressed, reinterpret_cast<mz_ulong *>(&compLength),
|
||||||
reinterpret_cast<mz_ulong *>(&compLength),
|
reinterpret_cast<const uint8_t *>(this), uncompLength,
|
||||||
&version,
|
|
||||||
uncompLength,
|
|
||||||
MZ_BEST_COMPRESSION
|
MZ_BEST_COMPRESSION
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -104,14 +72,19 @@ size_t Cart::toQRString(char *output) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
compLength = util::encodeBase45(&output[5], compressed, compLength);
|
compLength = util::encodeBase45(&output[5], compressed, compLength);
|
||||||
memcpy(&output[0], "573::", 5);
|
__builtin_memcpy(&output[0], "573::", 5);
|
||||||
memcpy(&output[compLength + 5], "::", 3);
|
__builtin_memcpy(&output[compLength + 5], "::", 3);
|
||||||
|
|
||||||
return compLength + 7;
|
return compLength + 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error Cart::readSystemID(void) {
|
/* Functions common to all cartridge drivers */
|
||||||
uint8_t id[8];
|
|
||||||
|
static constexpr int _X76_MAX_ACK_POLLS = 5;
|
||||||
|
static constexpr int _X76_WRITE_DELAY = 10000;
|
||||||
|
static constexpr int _ZS01_PACKET_DELAY = 30000;
|
||||||
|
|
||||||
|
CartError Cart::readSystemID(void) {
|
||||||
auto mask = setInterruptMask(0);
|
auto mask = setInterruptMask(0);
|
||||||
|
|
||||||
if (!io::dsDIOReset()) {
|
if (!io::dsDIOReset()) {
|
||||||
@ -122,25 +95,22 @@ Error Cart::readSystemID(void) {
|
|||||||
return DS2401_NO_RESP;
|
return DS2401_NO_RESP;
|
||||||
}
|
}
|
||||||
|
|
||||||
flags |= HAS_DIGITAL_IO;
|
_dump.flags |= DUMP_HAS_SYSTEM_ID;
|
||||||
|
|
||||||
io::dsDIOWriteByte(0x33);
|
io::dsDIOWriteByte(0x33);
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
id[i] = io::dsDIOReadByte();
|
_dump.systemID.data[i] = io::dsDIOReadByte();
|
||||||
|
|
||||||
if (mask)
|
if (mask)
|
||||||
setInterruptMask(mask);
|
setInterruptMask(mask);
|
||||||
if (!_validateID(id))
|
if (!_dump.systemID.validateDSCRC())
|
||||||
return DS2401_ID_ERROR;
|
return DS2401_ID_ERROR;
|
||||||
|
|
||||||
memcpy(systemID, id, sizeof(id));
|
_dump.flags |= DUMP_SYSTEM_ID_OK;
|
||||||
|
|
||||||
flags |= SYSTEM_ID_OK;
|
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error X76Cart::readCartID(void) {
|
CartError X76Cart::readCartID(void) {
|
||||||
uint8_t id[8];
|
|
||||||
auto mask = setInterruptMask(0);
|
auto mask = setInterruptMask(0);
|
||||||
|
|
||||||
if (!io::dsCartReset()) {
|
if (!io::dsCartReset()) {
|
||||||
@ -151,29 +121,27 @@ Error X76Cart::readCartID(void) {
|
|||||||
return DS2401_NO_RESP;
|
return DS2401_NO_RESP;
|
||||||
}
|
}
|
||||||
|
|
||||||
flags |= HAS_DS2401;
|
_dump.flags |= DUMP_HAS_CART_ID;
|
||||||
|
|
||||||
io::dsCartWriteByte(0x33);
|
io::dsCartWriteByte(0x33);
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
id[i] = io::dsCartReadByte();
|
_dump.cartID.data[i] = io::dsCartReadByte();
|
||||||
|
|
||||||
if (mask)
|
if (mask)
|
||||||
setInterruptMask(mask);
|
setInterruptMask(mask);
|
||||||
if (!_validateID(id))
|
if (!_dump.cartID.validateDSCRC())
|
||||||
return DS2401_ID_ERROR;
|
return DS2401_ID_ERROR;
|
||||||
|
|
||||||
memcpy(cartID, id, sizeof(id));
|
_dump.flags |= DUMP_CART_ID_OK;
|
||||||
|
|
||||||
flags |= CART_ID_OK;
|
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error X76Cart::_x76Command(
|
CartError X76Cart::_x76Command(
|
||||||
uint8_t command, uint8_t param, uint8_t pollByte
|
uint8_t cmd, uint8_t param, uint8_t pollByte
|
||||||
) const {
|
) const {
|
||||||
io::i2cStartWithCS();
|
io::i2cStartWithCS();
|
||||||
|
|
||||||
io::i2cWriteByte(command);
|
io::i2cWriteByte(cmd);
|
||||||
if (!io::i2cGetACK()) {
|
if (!io::i2cGetACK()) {
|
||||||
io::i2cStopWithCS();
|
io::i2cStopWithCS();
|
||||||
LOG("NACK while sending command");
|
LOG("NACK while sending command");
|
||||||
@ -187,7 +155,7 @@ Error X76Cart::_x76Command(
|
|||||||
return X76_NACK;
|
return X76_NACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!io::i2cWriteBytes(dataKey, sizeof(dataKey))) {
|
if (!io::i2cWriteBytes(_dump.dataKey, sizeof(_dump.dataKey))) {
|
||||||
io::i2cStopWithCS();
|
io::i2cStopWithCS();
|
||||||
LOG("NACK while sending data key");
|
LOG("NACK while sending data key");
|
||||||
return X76_NACK;
|
return X76_NACK;
|
||||||
@ -222,17 +190,11 @@ enum X76F041ConfigOp : uint8_t {
|
|||||||
_X76F041_CFG_ERASE = 0x70
|
_X76F041_CFG_ERASE = 0x70
|
||||||
};
|
};
|
||||||
|
|
||||||
X76F041Cart::X76F041Cart(void) {
|
CartError X76F041Cart::readPrivateData(void) {
|
||||||
Cart();
|
|
||||||
|
|
||||||
chipType = TYPE_X76F041;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error X76F041Cart::readPrivateData(void) {
|
|
||||||
// Reads can be done with any block size, but a single read operation can't
|
// Reads can be done with any block size, but a single read operation can't
|
||||||
// cross 128-byte block boundaries.
|
// cross 128-byte block boundaries.
|
||||||
for (int i = 0; i < 512; i += 128) {
|
for (int i = 0; i < 512; i += 128) {
|
||||||
Error error = _x76Command(
|
auto error = _x76Command(
|
||||||
_X76F041_READ | (i >> 8), i & 0xff, _X76F041_ACK_POLL
|
_X76F041_READ | (i >> 8), i & 0xff, _X76F041_ACK_POLL
|
||||||
);
|
);
|
||||||
if (error)
|
if (error)
|
||||||
@ -247,23 +209,23 @@ Error X76F041Cart::readPrivateData(void) {
|
|||||||
return X76_NACK;
|
return X76_NACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
io::i2cReadBytes(&data[i], 128);
|
io::i2cReadBytes(&_dump.data[i], 128);
|
||||||
io::i2cStopWithCS();
|
io::i2cStopWithCS();
|
||||||
}
|
}
|
||||||
|
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error X76F041Cart::writeData(void) {
|
CartError X76F041Cart::writeData(void) {
|
||||||
// Writes can only be done in 8-byte blocks.
|
// Writes can only be done in 8-byte blocks.
|
||||||
for (int i = 0; i < 512; i += 8) {
|
for (int i = 0; i < 512; i += 8) {
|
||||||
Error error = _x76Command(
|
auto error = _x76Command(
|
||||||
_X76F041_WRITE | (i >> 8), i & 0xff, _X76F041_ACK_POLL
|
_X76F041_WRITE | (i >> 8), i & 0xff, _X76F041_ACK_POLL
|
||||||
);
|
);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
if (!io::i2cWriteBytes(&data[i], 8)) {
|
if (!io::i2cWriteBytes(&_dump.data[i], 8)) {
|
||||||
LOG("NACK while sending data bytes");
|
LOG("NACK while sending data bytes");
|
||||||
return X76_NACK;
|
return X76_NACK;
|
||||||
}
|
}
|
||||||
@ -274,8 +236,8 @@ Error X76F041Cart::writeData(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error X76F041Cart::erase(void) {
|
CartError X76F041Cart::erase(void) {
|
||||||
Error error = _x76Command(
|
auto error = _x76Command(
|
||||||
_X76F041_CONFIG, _X76F041_CFG_ERASE, _X76F041_ACK_POLL
|
_X76F041_CONFIG, _X76F041_CFG_ERASE, _X76F041_ACK_POLL
|
||||||
);
|
);
|
||||||
if (error)
|
if (error)
|
||||||
@ -285,8 +247,8 @@ Error X76F041Cart::erase(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error X76F041Cart::setDataKey(const uint8_t *newKey) {
|
CartError X76F041Cart::setDataKey(const uint8_t *key) {
|
||||||
Error error = _x76Command(
|
auto error = _x76Command(
|
||||||
_X76F041_CONFIG, _X76F041_CFG_SET_DATA_KEY, _X76F041_ACK_POLL
|
_X76F041_CONFIG, _X76F041_CFG_SET_DATA_KEY, _X76F041_ACK_POLL
|
||||||
);
|
);
|
||||||
if (error)
|
if (error)
|
||||||
@ -295,7 +257,7 @@ Error X76F041Cart::setDataKey(const uint8_t *newKey) {
|
|||||||
// The X76F041 requires the key to be sent twice as a way of ensuring it
|
// The X76F041 requires the key to be sent twice as a way of ensuring it
|
||||||
// gets received correctly.
|
// gets received correctly.
|
||||||
for (int i = 2; i; i--) {
|
for (int i = 2; i; i--) {
|
||||||
if (!io::i2cWriteBytes(newKey, sizeof(dataKey))) {
|
if (!io::i2cWriteBytes(key, sizeof(_dump.dataKey))) {
|
||||||
io::i2cStopWithCS();
|
io::i2cStopWithCS();
|
||||||
LOG("NACK while setting new data key");
|
LOG("NACK while setting new data key");
|
||||||
return X76_NACK;
|
return X76_NACK;
|
||||||
@ -304,8 +266,8 @@ Error X76F041Cart::setDataKey(const uint8_t *newKey) {
|
|||||||
|
|
||||||
io::i2cStopWithCS(_X76_WRITE_DELAY);
|
io::i2cStopWithCS(_X76_WRITE_DELAY);
|
||||||
|
|
||||||
// Update the data key stored in the class.
|
// Update the data key stored in the dump.
|
||||||
memcpy(dataKey, newKey, sizeof(dataKey));
|
__builtin_memcpy(_dump.dataKey, key, sizeof(_dump.dataKey));
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,25 +281,29 @@ enum X76F100Command : uint8_t {
|
|||||||
_X76F100_ACK_POLL = 0x55
|
_X76F100_ACK_POLL = 0x55
|
||||||
};
|
};
|
||||||
|
|
||||||
X76F100Cart::X76F100Cart(void) {
|
// TODO: actually implement this (even though no X76F100 carts were ever made)
|
||||||
Cart();
|
|
||||||
|
|
||||||
chipType = TYPE_X76F100;
|
CartError X76F100Cart::readPrivateData(void) {
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: actually implement this (even though no X76F100 carts were ever made)
|
CartError X76F100Cart::writeData(void) {
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F100Cart::erase(void) {
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F100Cart::setDataKey(const uint8_t *key) {
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
|
}
|
||||||
|
|
||||||
/* ZS01 driver */
|
/* ZS01 driver */
|
||||||
|
|
||||||
ZS01Cart::ZS01Cart(void) {
|
CartError ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) {
|
||||||
Cart();
|
|
||||||
|
|
||||||
chipType = TYPE_ZS01;
|
|
||||||
flags = HAS_DS2401;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) {
|
|
||||||
io::i2cStart();
|
io::i2cStart();
|
||||||
|
|
||||||
if (!io::i2cWriteBytes(
|
if (!io::i2cWriteBytes(
|
||||||
&request.command, sizeof(zs01::Packet), _ZS01_PACKET_DELAY
|
&request.command, sizeof(zs01::Packet), _ZS01_PACKET_DELAY
|
||||||
)) {
|
)) {
|
||||||
@ -352,7 +318,8 @@ Error ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) {
|
|||||||
if (!response.decodeResponse())
|
if (!response.decodeResponse())
|
||||||
return ZS01_CRC_MISMATCH;
|
return ZS01_CRC_MISMATCH;
|
||||||
|
|
||||||
_state = response.address;
|
_encoderState = response.address;
|
||||||
|
|
||||||
if (response.command != zs01::RESP_NO_ERROR) {
|
if (response.command != zs01::RESP_NO_ERROR) {
|
||||||
LOG("ZS01 error, code=0x%02x", response.command);
|
LOG("ZS01 error, code=0x%02x", response.command);
|
||||||
return ZS01_ERROR;
|
return ZS01_ERROR;
|
||||||
@ -361,9 +328,9 @@ Error ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error ZS01Cart::readCartID(void) {
|
CartError ZS01Cart::readCartID(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
Error error;
|
CartError error;
|
||||||
|
|
||||||
request.address = zs01::ADDR_ZS01_ID;
|
request.address = zs01::ADDR_ZS01_ID;
|
||||||
request.encodeReadRequest();
|
request.encodeReadRequest();
|
||||||
@ -371,12 +338,12 @@ Error ZS01Cart::readCartID(void) {
|
|||||||
error = _transact(request, response);
|
error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
if (!_validateID(response.data))
|
|
||||||
|
response.copyTo(_dump.zsID.data);
|
||||||
|
if (!_dump.zsID.validateDSCRC())
|
||||||
return DS2401_ID_ERROR;
|
return DS2401_ID_ERROR;
|
||||||
|
|
||||||
response.copyDataTo(zsID);
|
_dump.flags |= DUMP_ZS_ID_OK;
|
||||||
|
|
||||||
flags |= ZS_ID_OK;
|
|
||||||
|
|
||||||
request.address = zs01::ADDR_DS2401_ID;
|
request.address = zs01::ADDR_DS2401_ID;
|
||||||
request.encodeReadRequest();
|
request.encodeReadRequest();
|
||||||
@ -384,114 +351,153 @@ Error ZS01Cart::readCartID(void) {
|
|||||||
error = _transact(request, response);
|
error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
if (!_validateID(response.data))
|
|
||||||
|
response.copyTo(_dump.cartID.data);
|
||||||
|
if (!_dump.cartID.validateDSCRC())
|
||||||
return DS2401_ID_ERROR;
|
return DS2401_ID_ERROR;
|
||||||
|
|
||||||
response.copyDataTo(cartID);
|
_dump.flags |= DUMP_CART_ID_OK;
|
||||||
|
|
||||||
flags |= CART_ID_OK;
|
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error ZS01Cart::readPublicData(void) {
|
CartError ZS01Cart::readPublicData(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
|
|
||||||
for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PUBLIC_END; i++) {
|
for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PUBLIC_END; i++) {
|
||||||
request.address = i;
|
request.address = i;
|
||||||
request.encodeReadRequest();
|
request.encodeReadRequest();
|
||||||
|
|
||||||
Error error = _transact(request, response);
|
CartError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
response.copyDataTo(&data[i * sizeof(response.data)]);
|
response.copyTo(&_dump.data[i * sizeof(response.data)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
flags |= PUBLIC_DATA_OK;
|
_dump.flags |= DUMP_PUBLIC_DATA_OK;
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error ZS01Cart::readPrivateData(void) {
|
CartError ZS01Cart::readPrivateData(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key key;
|
||||||
key.unpackFrom(dataKey);
|
|
||||||
|
key.unpackFrom(_dump.dataKey);
|
||||||
|
|
||||||
for (int i = zs01::ADDR_PRIVATE; i < zs01::ADDR_PRIVATE_END; i++) {
|
for (int i = zs01::ADDR_PRIVATE; i < zs01::ADDR_PRIVATE_END; i++) {
|
||||||
request.address = i;
|
request.address = i;
|
||||||
request.encodeReadRequest(key, _state);
|
request.encodeReadRequest(key, _encoderState);
|
||||||
|
|
||||||
Error error = _transact(request, response);
|
CartError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
response.copyDataTo(&data[i * sizeof(response.data)]);
|
response.copyTo(&_dump.data[i * sizeof(response.data)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
flags |= PRIVATE_DATA_OK;
|
_dump.flags |= DUMP_PRIVATE_DATA_OK;
|
||||||
|
|
||||||
request.address = zs01::ADDR_CONFIG;
|
request.address = zs01::ADDR_CONFIG;
|
||||||
request.encodeReadRequest(key, _state);
|
request.encodeReadRequest(key, _encoderState);
|
||||||
|
|
||||||
Error error = _transact(request, response);
|
CartError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
response.copyDataTo(config);
|
response.copyTo(_dump.config);
|
||||||
|
|
||||||
flags |= CONFIG_OK;
|
_dump.flags |= DUMP_CONFIG_OK;
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error ZS01Cart::writeData(void) {
|
CartError ZS01Cart::writeData(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key key;
|
||||||
key.unpackFrom(dataKey);
|
|
||||||
|
key.unpackFrom(_dump.dataKey);
|
||||||
|
|
||||||
for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PRIVATE_END; i++) {
|
for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PRIVATE_END; i++) {
|
||||||
request.address = i;
|
request.address = i;
|
||||||
request.copyDataFrom(&data[i * sizeof(request.data)]);
|
request.copyFrom(&_dump.data[i * sizeof(request.data)]);
|
||||||
request.encodeWriteRequest(key, _state);
|
request.encodeWriteRequest(key, _encoderState);
|
||||||
|
|
||||||
Error error = _transact(request, response);
|
CartError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.address = zs01::ADDR_CONFIG;
|
request.address = zs01::ADDR_CONFIG;
|
||||||
request.copyDataFrom(config);
|
request.copyFrom(_dump.config);
|
||||||
request.encodeWriteRequest(key, _state);
|
request.encodeWriteRequest(key, _encoderState);
|
||||||
|
|
||||||
return _transact(request, response);
|
return _transact(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
Error ZS01Cart::erase(void) {
|
CartError ZS01Cart::erase(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key key;
|
||||||
key.unpackFrom(dataKey);
|
|
||||||
|
|
||||||
memset(request.data, 0, sizeof(request.data));
|
key.unpackFrom(_dump.dataKey);
|
||||||
|
|
||||||
|
__builtin_memset(request.data, 0, sizeof(request.data));
|
||||||
request.address = zs01::ADDR_ERASE;
|
request.address = zs01::ADDR_ERASE;
|
||||||
request.encodeWriteRequest(key, _state);
|
request.encodeWriteRequest(key, _encoderState);
|
||||||
|
|
||||||
return _transact(request, response);
|
return _transact(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
Error ZS01Cart::setDataKey(const uint8_t *newKey) {
|
CartError ZS01Cart::setDataKey(const uint8_t *key) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key newKey;
|
||||||
key.unpackFrom(dataKey);
|
|
||||||
|
newKey.unpackFrom(_dump.dataKey);
|
||||||
|
|
||||||
request.address = zs01::ADDR_DATA_KEY;
|
request.address = zs01::ADDR_DATA_KEY;
|
||||||
request.copyDataFrom(newKey);
|
request.copyFrom(key);
|
||||||
request.encodeWriteRequest(key, _state);
|
request.encodeWriteRequest(newKey, _encoderState);
|
||||||
|
|
||||||
Error error = _transact(request, response);
|
CartError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
// Update the data key stored in the class.
|
// Update the data key stored in the dump.
|
||||||
memcpy(dataKey, newKey, sizeof(dataKey));
|
__builtin_memcpy(_dump.dataKey, key, sizeof(_dump.dataKey));
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cartridge identification */
|
||||||
|
|
||||||
|
enum ChipIdentifier : uint32_t {
|
||||||
|
_ID_X76F041 = 0x55aa5519,
|
||||||
|
_ID_X76F100 = 0x55aa0019,
|
||||||
|
_ID_ZS01 = 0x5a530001
|
||||||
|
};
|
||||||
|
|
||||||
|
Cart *createCart(Dump &dump) {
|
||||||
|
if (!io::getCartInsertionStatus()) {
|
||||||
|
LOG("DSR not asserted");
|
||||||
|
return new Cart(dump);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t id1 = io::i2cResetZS01();
|
||||||
|
LOG("id1=0x%08x", id1);
|
||||||
|
|
||||||
|
if (id1 == _ID_ZS01)
|
||||||
|
return new ZS01Cart(dump);
|
||||||
|
|
||||||
|
uint32_t id2 = io::i2cResetX76();
|
||||||
|
LOG("id2=0x%08x", id2);
|
||||||
|
|
||||||
|
switch (id2) {
|
||||||
|
case _ID_X76F041:
|
||||||
|
return new X76F041Cart(dump);
|
||||||
|
|
||||||
|
//case _ID_X76F100:
|
||||||
|
//return new X76F100Cart(dump);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return new Cart(dump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
198
src/cartio.hpp
Normal file
198
src/cartio.hpp
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "zs01.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* Definitions */
|
||||||
|
|
||||||
|
enum CartError {
|
||||||
|
NO_ERROR = 0,
|
||||||
|
UNSUPPORTED_OP = 1,
|
||||||
|
DS2401_NO_RESP = 2,
|
||||||
|
DS2401_ID_ERROR = 3,
|
||||||
|
X76_NACK = 4,
|
||||||
|
X76_POLL_FAIL = 5,
|
||||||
|
X76_VERIFY_FAIL = 6,
|
||||||
|
ZS01_NACK = 7,
|
||||||
|
ZS01_ERROR = 8,
|
||||||
|
ZS01_CRC_MISMATCH = 9
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ChipType : uint8_t {
|
||||||
|
NONE = 0,
|
||||||
|
X76F041 = 1,
|
||||||
|
X76F100 = 2,
|
||||||
|
ZS01 = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DumpFlag : uint8_t {
|
||||||
|
DUMP_HAS_SYSTEM_ID = 1 << 0,
|
||||||
|
DUMP_HAS_CART_ID = 1 << 1,
|
||||||
|
DUMP_CONFIG_OK = 1 << 2,
|
||||||
|
DUMP_SYSTEM_ID_OK = 1 << 3,
|
||||||
|
DUMP_CART_ID_OK = 1 << 4,
|
||||||
|
DUMP_ZS_ID_OK = 1 << 5,
|
||||||
|
DUMP_PUBLIC_DATA_OK = 1 << 6,
|
||||||
|
DUMP_PRIVATE_DATA_OK = 1 << 7
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int NUM_CHIP_TYPES = 4;
|
||||||
|
static constexpr size_t MAX_QR_STRING_LENGTH = 0x600;
|
||||||
|
|
||||||
|
/* Common data structures */
|
||||||
|
|
||||||
|
class [[gnu::packed]] Identifier {
|
||||||
|
public:
|
||||||
|
uint8_t data[8];
|
||||||
|
|
||||||
|
inline void copyFrom(const uint8_t *source) {
|
||||||
|
__builtin_memcpy(data, source, sizeof(data));
|
||||||
|
}
|
||||||
|
inline void copyTo(uint8_t *dest) const {
|
||||||
|
__builtin_memcpy(dest, data, sizeof(data));
|
||||||
|
}
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(data, 0, sizeof(data));
|
||||||
|
}
|
||||||
|
inline bool isEmpty(void) const {
|
||||||
|
return (util::sum(data, sizeof(data)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t toString(char *output) const {
|
||||||
|
return util::hexToString(output, data, sizeof(data), '-');
|
||||||
|
}
|
||||||
|
inline size_t toSerialNumber(char *output) const {
|
||||||
|
return util::serialNumberToString(output, &data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
void updateDSCRC(void);
|
||||||
|
bool validateDSCRC(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Dump structure and utilities */
|
||||||
|
|
||||||
|
struct ChipSize {
|
||||||
|
public:
|
||||||
|
size_t dataLength, publicDataOffset, publicDataLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const ChipSize CHIP_SIZES[NUM_CHIP_TYPES];
|
||||||
|
|
||||||
|
class [[gnu::packed]] Dump {
|
||||||
|
public:
|
||||||
|
ChipType chipType;
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
Identifier systemID, cartID, zsID;
|
||||||
|
|
||||||
|
uint8_t dataKey[8], config[8];
|
||||||
|
uint8_t data[512];
|
||||||
|
|
||||||
|
inline const ChipSize &getChipSize(void) const {
|
||||||
|
return CHIP_SIZES[chipType];
|
||||||
|
}
|
||||||
|
inline size_t getDumpLength(void) const {
|
||||||
|
return (sizeof(Dump) - sizeof(data)) + getChipSize().dataLength;
|
||||||
|
}
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(this, 0, sizeof(Dump));
|
||||||
|
}
|
||||||
|
inline void clearData(void) {
|
||||||
|
__builtin_memset(data, 0, getChipSize().dataLength);
|
||||||
|
}
|
||||||
|
inline bool isDataEmpty(void) const {
|
||||||
|
size_t length = getChipSize().dataLength;
|
||||||
|
auto sum = util::sum(data, length);
|
||||||
|
|
||||||
|
return (!sum || (sum == (0xff * length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t toQRString(char *output) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Cartridge driver classes */
|
||||||
|
|
||||||
|
class Cart {
|
||||||
|
protected:
|
||||||
|
Dump &_dump;
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline Cart(Dump &dump, ChipType chipType = NONE, uint8_t flags = 0)
|
||||||
|
: _dump(dump) {
|
||||||
|
dump.clear();
|
||||||
|
|
||||||
|
dump.chipType = chipType;
|
||||||
|
dump.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual CartError readSystemID(void);
|
||||||
|
virtual CartError readCartID(void) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError readPublicData(void) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError readPrivateData(void) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError writeData(void) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError erase(void) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError setDataKey(const uint8_t *key) { return UNSUPPORTED_OP; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] X76Cart : public Cart {
|
||||||
|
protected:
|
||||||
|
CartError _readDS2401(void);
|
||||||
|
CartError _x76Command(uint8_t cmd, uint8_t param, uint8_t pollByte) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline X76Cart(Dump &dump, ChipType chipType)
|
||||||
|
: Cart(dump, chipType) {}
|
||||||
|
|
||||||
|
CartError readCartID(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] X76F041Cart : public X76Cart {
|
||||||
|
public:
|
||||||
|
inline X76F041Cart(Dump &dump)
|
||||||
|
: X76Cart(dump, X76F041) {}
|
||||||
|
|
||||||
|
CartError readPrivateData(void);
|
||||||
|
CartError writeData(void);
|
||||||
|
CartError erase(void);
|
||||||
|
CartError setDataKey(const uint8_t *key);
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] X76F100Cart : public X76Cart {
|
||||||
|
public:
|
||||||
|
inline X76F100Cart(Dump &dump)
|
||||||
|
: X76Cart(dump, X76F100) {}
|
||||||
|
|
||||||
|
CartError readPrivateData(void);
|
||||||
|
CartError writeData(void);
|
||||||
|
CartError erase(void);
|
||||||
|
CartError setDataKey(const uint8_t *key);
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] ZS01Cart : public Cart {
|
||||||
|
private:
|
||||||
|
uint8_t _encoderState;
|
||||||
|
|
||||||
|
CartError _transact(zs01::Packet &request, zs01::Packet &response);
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline ZS01Cart(Dump &dump)
|
||||||
|
: Cart(dump, ZS01, DUMP_HAS_CART_ID), _encoderState(0) {}
|
||||||
|
|
||||||
|
CartError readCartID(void);
|
||||||
|
CartError readPublicData(void);
|
||||||
|
CartError readPrivateData(void);
|
||||||
|
CartError writeData(void);
|
||||||
|
CartError erase(void);
|
||||||
|
CartError setDataKey(const uint8_t *key);
|
||||||
|
};
|
||||||
|
|
||||||
|
Cart *createCart(Dump &dump);
|
||||||
|
|
||||||
|
}
|
@ -99,7 +99,7 @@ int main(int argc, const char **argv) {
|
|||||||
!loader.loadTIM(background.tile, "assets/textures/background.tim") ||
|
!loader.loadTIM(background.tile, "assets/textures/background.tim") ||
|
||||||
!loader.loadTIM(uiCtx.font.image, "assets/textures/font.tim") ||
|
!loader.loadTIM(uiCtx.font.image, "assets/textures/font.tim") ||
|
||||||
!loader.loadFontMetrics(uiCtx.font, "assets/textures/font.metrics") ||
|
!loader.loadFontMetrics(uiCtx.font, "assets/textures/font.metrics") ||
|
||||||
!loader.loadAsset(strings.data, "assets/app.strings")
|
!loader.loadAsset(strings, "assets/app.strings")
|
||||||
) {
|
) {
|
||||||
LOG("required assets not found, exiting");
|
LOG("required assets not found, exiting");
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -9,6 +9,15 @@ namespace util {
|
|||||||
|
|
||||||
/* Misc. template utilities */
|
/* Misc. template utilities */
|
||||||
|
|
||||||
|
template<typename T> static inline uint32_t sum(const T *data, size_t length) {
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
for (; length; length--)
|
||||||
|
value += uint32_t(*(data++));
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T> static inline T min(T a, T b) {
|
template<typename T> static inline T min(T a, T b) {
|
||||||
return (a < b) ? a : b;
|
return (a < b) ? a : b;
|
||||||
}
|
}
|
||||||
|
13
src/zs01.hpp
13
src/zs01.hpp
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace zs01 {
|
namespace zs01 {
|
||||||
|
|
||||||
@ -59,14 +58,14 @@ class Packet {
|
|||||||
public:
|
public:
|
||||||
uint8_t command, address, data[8], crc[2];
|
uint8_t command, address, data[8], crc[2];
|
||||||
|
|
||||||
inline void copyDataFrom(const uint8_t *source) {
|
inline void copyFrom(const uint8_t *source) {
|
||||||
memcpy(data, source, sizeof(data));
|
__builtin_memcpy(data, source, sizeof(data));
|
||||||
}
|
}
|
||||||
inline void copyDataTo(uint8_t *dest) const {
|
inline void copyTo(uint8_t *dest) const {
|
||||||
memcpy(dest, data, sizeof(data));
|
__builtin_memcpy(dest, data, sizeof(data));
|
||||||
}
|
}
|
||||||
inline void clearData(void) {
|
inline void clear(void) {
|
||||||
memset(data, 0, sizeof(data));
|
__builtin_memset(data, 0, sizeof(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateCRC(void);
|
void updateCRC(void);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user