diff --git a/CMakeLists.txt b/CMakeLists.txt index 280bd3f..9664736 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,8 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter) add_executable( cart_tool src/asset.cpp - src/cart.cpp - src/cartdb.cpp + src/cartdata.cpp + src/cartio.cpp src/gpu.cpp src/io.cpp src/main.cpp diff --git a/src/app/app.cpp b/src/app/app.cpp index c344fba..89f94dc 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -4,8 +4,8 @@ #include "app/app.hpp" #include "ps1/system.h" #include "asset.hpp" -#include "cart.hpp" -#include "cartdb.hpp" +#include "cartdata.hpp" +#include "cartio.hpp" #include "io.hpp" #include "uibase.hpp" #include "util.hpp" @@ -13,7 +13,7 @@ /* App class */ App::App(void) -: _cart(nullptr), _identified(nullptr), _identifyResult(cartdb::UNIDENTIFIED) { +: _cart(nullptr), _cartData(nullptr), _identified(nullptr) { _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) { _workerStatus.update(0, 4, WSTR("App.cartDetectWorker.identifyCart")); - _db.data.unload(); - _cart = cart::createCart(); + _db.unload(); + if (_cart) + delete _cart; + if (_cartData) + delete _cartData; - if (_cart->chipType) { - LOG("cart object @ 0x%08x", _cart); + _cart = cart::createCart(_dump); + _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")); _cart->readCartID(); - _cart->readPublicData(); - - if (!_loader->loadAsset(_db.data, _CARTDB_PATHS[_cart->chipType])) { - LOG("failed to load cartdb, type=%d", _cart->chipType); + if (!_cart->readPublicData()) + _cartData = cart::createCartData(_dump); + if (!_cartData) 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")); - if (_cart->flags & cart::PUBLIC_DATA_OK) - _identifyResult = _db.identifyCart(*_cart); - else - LOG("no public data available"); + if (!_loader->loadAsset(_db, _CARTDB_PATHS[_dump.chipType])) { + LOG("failed to load %s", _CARTDB_PATHS[_dump.chipType]); + goto _cartInitDone; + } + + // TODO + //_identified = _db.lookupEntry(code, region); } _cartInitDone: @@ -125,8 +130,8 @@ void App::_cartUnlockWorker(void) { _workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identify")); - if (_cart->flags & cart::PRIVATE_DATA_OK) - _identifyResult = _db.identifyCart(*_cart); + //if (_dump.flags & cart::DUMP_PRIVATE_DATA_OK) + //_identifyResult = _db.identifyCart(*_cart); _workerStatus.finish(_cartInfoScreen, true); _dummyWorker(); @@ -136,7 +141,7 @@ void App::_qrCodeWorker(void) { char qrString[cart::MAX_QR_STRING_LENGTH]; _workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress")); - _cart->toQRString(qrString); + _dump.toQRString(qrString); _workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate")); _qrCodeScreen.generateCode(qrString); diff --git a/src/app/app.hpp b/src/app/app.hpp index 86388d0..5accfb6 100644 --- a/src/app/app.hpp +++ b/src/app/app.hpp @@ -7,8 +7,8 @@ #include "app/unlock.hpp" #include "ps1/system.h" #include "asset.hpp" -#include "cart.hpp" -#include "cartdb.hpp" +#include "cartdata.hpp" +#include "cartio.hpp" #include "uibase.hpp" /* Worker status class */ @@ -85,15 +85,15 @@ private: asset::AssetLoader *_loader; asset::StringTable *_strings; - cartdb::CartDB _db; - Thread _workerThread; - WorkerStatus _workerStatus; + cart::Dump _dump; + cart::CartDB _db; + Thread _workerThread; + WorkerStatus _workerStatus; - uint8_t *_workerStack; - cart::Cart *_cart; - - cartdb::Entry *_identified; - cartdb::IdentifyResult _identifyResult; + uint8_t *_workerStack; + cart::Cart *_cart; + cart::CartData *_cartData; + cart::DBEntry *_identified; void _setupWorker(void (App::* func)(void)); void _setupInterrupts(void); diff --git a/src/app/unlock.cpp b/src/app/unlock.cpp index 04889cb..d731862 100644 --- a/src/app/unlock.cpp +++ b/src/app/unlock.cpp @@ -3,8 +3,8 @@ #include #include "app/app.hpp" #include "app/unlock.hpp" -#include "cart.hpp" -#include "cartdb.hpp" +#include "cartdata.hpp" +#include "cartio.hpp" #include "defs.hpp" #include "uibase.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[]{ "CartInfoScreen.description.locked.unidentified"_h, "CartInfoScreen.description.locked.identified"_h, @@ -53,60 +59,59 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) { TextScreen::show(ctx, goBack); - auto &_cart = *(APP->_cart); - auto flags = _cart.flags; + auto &dump = APP->_dump; char id1[32], id2[32]; char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)]; // Digital I/O board info - if (flags & cart::SYSTEM_ID_OK) { - util::hexToString(id1, _cart.systemID, sizeof(_cart.systemID), '-'); - util::serialNumberToString(id2, &_cart.systemID[1]); - } else if (flags & cart::HAS_DIGITAL_IO) { - strcpy(id1, STR("CartInfoScreen.id.error")); - strcpy(id2, id1); + if (dump.flags & cart::DUMP_SYSTEM_ID_OK) { + dump.systemID.toString(id1); + dump.systemID.toSerialNumber(id2); + } else if (dump.flags & cart::DUMP_HAS_SYSTEM_ID) { + __builtin_strcpy(id1, STR("CartInfoScreen.id.error")); + __builtin_strcpy(id2, id1); } else { - strcpy(id1, STR("CartInfoScreen.id.noSystemID")); - strcpy(id2, id1); + __builtin_strcpy(id1, STR("CartInfoScreen.id.noSystemID")); + __builtin_strcpy(id2, id1); } ptr += snprintf(ptr, end - ptr, STR("CartInfoScreen.digitalIOInfo"), id1, id2); // Cartridge info - if (!_cart.chipType) { + if (!dump.chipType) { memccpy(ptr, STR("CartInfoScreen.description.noCart"), 0, end - ptr); _prompt = STR("CartInfoScreen.prompt.error"); 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); _prompt = STR("CartInfoScreen.prompt.error"); return; } - if (flags & cart::CART_ID_OK) - util::hexToString(id1, _cart.cartID, sizeof(_cart.cartID), '-'); - else if (flags & cart::HAS_DS2401) - strcpy(id1, STR("CartInfoScreen.id.error")); + if (dump.flags & cart::DUMP_CART_ID_OK) + dump.cartID.toString(id1); + else if (dump.flags & cart::DUMP_HAS_CART_ID) + __builtin_strcpy(id1, STR("CartInfoScreen.id.error")); else - strcpy(id1, STR("CartInfoScreen.id.noCartID")); + __builtin_strcpy(id1, STR("CartInfoScreen.id.noCartID")); - if (flags & cart::ZS_ID_OK) - util::hexToString(id2, _cart.zsID, sizeof(_cart.zsID), '-'); - else if (_cart.chipType == cart::TYPE_ZS01) - strcpy(id2, STR("CartInfoScreen.id.error")); + if (dump.flags & cart::DUMP_ZS_ID_OK) + dump.zsID.toString(id2); + else if (dump.chipType == cart::ZS01) + __builtin_strcpy(id2, STR("CartInfoScreen.id.error")); else - 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.locked"); ptr += snprintf( 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: @@ -122,34 +127,38 @@ void CartInfoScreen::show(ui::Context &ctx, bool goBack) { // => only dumping/flashing available // - unlocked, no private data, blank // => only dumping/flashing available - auto result = APP->_identifyResult; - char name[96]; + IdentifyState state; + char name[96]; - if (result == cartdb::IDENTIFIED) + if (APP->_identified) { + state = IDENTIFIED; APP->_identified->getDisplayName(name, sizeof(name)); + } else { + state = APP->_dump.isDataEmpty() ? BLANK_CART : UNIDENTIFIED; + } - if (flags & cart::PRIVATE_DATA_OK) { - ptr += snprintf(ptr, end - ptr, STRH(_UNLOCKED_PROMPTS[result]), name); + if (dump.flags & cart::DUMP_PRIVATE_DATA_OK) { + ptr += snprintf(ptr, end - ptr, STRH(_UNLOCKED_PROMPTS[state]), name); _prompt = STR("CartInfoScreen.prompt.unlocked"); } 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"); } } void CartInfoScreen::update(ui::Context &ctx) { - auto &_cart = *(APP->_cart); + auto &dump = APP->_dump; - if (!_cart.chipType) + if (!dump.chipType) return; 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); 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 { - return (APP->_identifyResult == cartdb::IDENTIFIED) - ? ENTRY_AUTO_UNLOCK : ENTRY_CUSTOM_KEY; + return APP->_identified ? ENTRY_AUTO_UNLOCK : ENTRY_CUSTOM_KEY; } 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; default: - APP->_db.getEntry(index).getDisplayName(name, sizeof(name)); + APP->_db.get(index)->getDisplayName(name, sizeof(name)); return name; } } @@ -193,7 +201,7 @@ void UnlockKeyScreen::show(ui::Context &ctx, bool goBack) { _customKeyItem = STR("UnlockKeyScreen.customKey"); _nullKeyItem = STR("UnlockKeyScreen.nullKey"); - _listLength = APP->_db.numEntries - _getSpecialEntryOffset(ctx); + _listLength = APP->_db.getNumEntries() - _getSpecialEntryOffset(ctx); ListScreen::show(ctx, goBack); } @@ -206,9 +214,9 @@ void UnlockKeyScreen::update(ui::Context &ctx) { switch (index) { case ENTRY_AUTO_UNLOCK: - memcpy( - APP->_cart->dataKey, APP->_identified->dataKey, - sizeof(APP->_cart->dataKey) + __builtin_memcpy( + APP->_dump.dataKey, APP->_identified->dataKey, + sizeof(APP->_dump.dataKey) ); ctx.show(APP->_unlockConfirmScreen, false, true); break; @@ -218,14 +226,16 @@ void UnlockKeyScreen::update(ui::Context &ctx) { break; 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); break; default: - memcpy( - APP->_cart->dataKey, APP->_db.getEntry(index).dataKey, - sizeof(APP->_cart->dataKey) + __builtin_memcpy( + APP->_dump.dataKey, APP->_db.get(index)->dataKey, + sizeof(APP->_dump.dataKey) ); ctx.show(APP->_unlockConfirmScreen, false, true); } @@ -236,7 +246,7 @@ void UnlockKeyScreen::update(ui::Context &ctx) { void UnlockConfirmScreen::show(ui::Context &ctx, bool goBack) { _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[1] = STR("UnlockConfirmScreen.yes"); @@ -260,7 +270,7 @@ void UnlockConfirmScreen::update(ui::Context &ctx) { void UnlockErrorScreen::show(ui::Context &ctx, bool goBack) { _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"); _numButtons = 1; diff --git a/src/asset.cpp b/src/asset.cpp index 17e50b8..c62b695 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -159,12 +159,12 @@ size_t AssetLoader::loadFontMetrics(gpu::Font &output, const char *path) { /* String table manager */ -const char *StringTable::get(util::Hash id) { - if (!data.ptr) +const char *StringTable::get(util::Hash id) const { + if (!ptr) return "missingno"; - auto blob = reinterpret_cast(data.ptr); - auto table = reinterpret_cast(data.ptr); + auto blob = reinterpret_cast(ptr); + auto table = reinterpret_cast(ptr); auto entry = &table[id % TABLE_BUCKET_COUNT]; @@ -181,7 +181,9 @@ const char *StringTable::get(util::Hash id) { 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_start(ap, id); diff --git a/src/asset.hpp b/src/asset.hpp index d209282..94f0742 100644 --- a/src/asset.hpp +++ b/src/asset.hpp @@ -66,16 +66,14 @@ public: uint16_t offset, chained; }; -class StringTable { +class StringTable : public Asset { public: - Asset data; - - inline const char *operator[](util::Hash id) { + inline const char *operator[](util::Hash id) const { return get(id); } - const char *get(util::Hash id); - size_t format(char *buffer, size_t length, util::Hash id, ...); + const char *get(util::Hash id) const; + size_t format(char *buffer, size_t length, util::Hash id, ...) const; }; /* QR code encoder */ diff --git a/src/cart.hpp b/src/cart.hpp deleted file mode 100644 index d163e95..0000000 --- a/src/cart.hpp +++ /dev/null @@ -1,123 +0,0 @@ - -#pragma once - -#include -#include -#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); -}; - -} diff --git a/src/cartdata.cpp b/src/cartdata.cpp new file mode 100644 index 0000000..6ea7e52 --- /dev/null +++ b/src/cartdata.cpp @@ -0,0 +1,292 @@ + + +#include +#include +#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(this), 4); + + checksum = uint8_t((value & 0xff) ^ 0xff); +} + +bool BasicHeader::validateChecksum(void) const { + auto value = util::sum(reinterpret_cast(this), 4); + + return (checksum == ((value & 0xff) ^ 0xff)); +} + +void ExtendedHeader::updateChecksum(void) { + auto value = util::sum(reinterpret_cast(this), 14); + + checksum = uint16_t(value & 0xffff); +} + +bool ExtendedHeader::validateChecksum(void) const { + auto value = util::sum(reinterpret_cast(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(&_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( + &_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( + &_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(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; +} + +} diff --git a/src/cartdata.hpp b/src/cartdata.hpp new file mode 100644 index 0000000..53d8b5c --- /dev/null +++ b/src/cartdata.hpp @@ -0,0 +1,213 @@ + +#pragma once + +#include +#include +#include +#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(_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(_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(_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(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; +}; + +} diff --git a/src/cartdb.cpp b/src/cartdb.cpp deleted file mode 100644 index 2e9ede6..0000000 --- a/src/cartdb.cpp +++ /dev/null @@ -1,37 +0,0 @@ - - -#include -#include -#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(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; -} - -} diff --git a/src/cartdb.hpp b/src/cartdb.hpp deleted file mode 100644 index 3538dab..0000000 --- a/src/cartdb.hpp +++ /dev/null @@ -1,79 +0,0 @@ - -#pragma once - -#include -#include -#include -#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(data.ptr); - //assert(data); - - return *reinterpret_cast(&_data[index * _entryLength]); - } - - bool init(void); - IdentifyResult identifyCart(cart::Cart &cart) const; -}; - -} diff --git a/src/cart.cpp b/src/cartio.cpp similarity index 55% rename from src/cart.cpp rename to src/cartio.cpp index 32d4833..9f37acd 100644 --- a/src/cart.cpp +++ b/src/cartio.cpp @@ -1,96 +1,64 @@ #include #include -#include #include "ps1/system.h" #include "vendor/miniz.h" -#include "cart.hpp" +#include "cartio.hpp" #include "io.hpp" #include "util.hpp" #include "zs01.hpp" namespace cart { -/* Common functions */ +/* Common data structures */ -enum ChipID : uint32_t { - _ID_X76F041 = 0x55aa5519, - _ID_X76F100 = 0x55aa0019, - _ID_ZS01 = 0x5a530001 -}; +void Identifier::updateChecksum(void) { + data[7] = (util::sum(data, 7) & 0xff) ^ 0xff; +} -static const size_t _DATA_LENGTHS[NUM_CHIP_TYPES]{ 0, 512, 112, 112 }; +bool Identifier::validateChecksum(void) const { + uint8_t value = (util::sum(data, 7) & 0xff) ^ 0xff; -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); - if (crc != id[7]) { - 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 true; } -size_t getDataLength(ChipType type) { - return _DATA_LENGTHS[type]; +void Identifier::updateDSCRC(void) { + data[7] = util::dsCRC8(data, 7); } -Cart *createCart(void) { - if (!io::getCartInsertionStatus()) { - LOG("DSR not asserted"); - return new Cart(); +bool Identifier::validateDSCRC(void) const { + uint8_t value = util::dsCRC8(data, 7); + + if (value != data[7]) { + LOG("CRC mismatch, exp=0x%02x, got=0x%02x", value, data[7]); + return false; } - uint32_t id1 = io::i2cResetX76(); - 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(); - } + return true; } -/* Base cart class */ +/* Dump structure and utilities */ -Cart::Cart(void) { - memset(&version, 0, 44); +const ChipSize CHIP_SIZES[NUM_CHIP_TYPES]{ + { .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; - chipType = TYPE_NONE; - flags = 0; -} - -size_t Cart::toQRString(char *output) { +size_t Dump::toQRString(char *output) const { uint8_t compressed[MAX_QR_STRING_LENGTH]; size_t uncompLength = getDumpLength(); size_t compLength = MAX_QR_STRING_LENGTH; int error = mz_compress2( - compressed, - reinterpret_cast(&compLength), - &version, - uncompLength, + compressed, reinterpret_cast(&compLength), + reinterpret_cast(this), uncompLength, MZ_BEST_COMPRESSION ); @@ -104,15 +72,20 @@ size_t Cart::toQRString(char *output) { ); compLength = util::encodeBase45(&output[5], compressed, compLength); - memcpy(&output[0], "573::", 5); - memcpy(&output[compLength + 5], "::", 3); + __builtin_memcpy(&output[0], "573::", 5); + __builtin_memcpy(&output[compLength + 5], "::", 3); return compLength + 7; } -Error Cart::readSystemID(void) { - uint8_t id[8]; - auto mask = setInterruptMask(0); +/* Functions common to all cartridge drivers */ + +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); if (!io::dsDIOReset()) { if (mask) @@ -122,26 +95,23 @@ Error Cart::readSystemID(void) { return DS2401_NO_RESP; } - flags |= HAS_DIGITAL_IO; + _dump.flags |= DUMP_HAS_SYSTEM_ID; io::dsDIOWriteByte(0x33); for (int i = 0; i < 8; i++) - id[i] = io::dsDIOReadByte(); + _dump.systemID.data[i] = io::dsDIOReadByte(); if (mask) setInterruptMask(mask); - if (!_validateID(id)) + if (!_dump.systemID.validateDSCRC()) return DS2401_ID_ERROR; - memcpy(systemID, id, sizeof(id)); - - flags |= SYSTEM_ID_OK; + _dump.flags |= DUMP_SYSTEM_ID_OK; return NO_ERROR; } -Error X76Cart::readCartID(void) { - uint8_t id[8]; - auto mask = setInterruptMask(0); +CartError X76Cart::readCartID(void) { + auto mask = setInterruptMask(0); if (!io::dsCartReset()) { if (mask) @@ -151,29 +121,27 @@ Error X76Cart::readCartID(void) { return DS2401_NO_RESP; } - flags |= HAS_DS2401; + _dump.flags |= DUMP_HAS_CART_ID; io::dsCartWriteByte(0x33); for (int i = 0; i < 8; i++) - id[i] = io::dsCartReadByte(); + _dump.cartID.data[i] = io::dsCartReadByte(); if (mask) setInterruptMask(mask); - if (!_validateID(id)) + if (!_dump.cartID.validateDSCRC()) return DS2401_ID_ERROR; - memcpy(cartID, id, sizeof(id)); - - flags |= CART_ID_OK; + _dump.flags |= DUMP_CART_ID_OK; return NO_ERROR; } -Error X76Cart::_x76Command( - uint8_t command, uint8_t param, uint8_t pollByte +CartError X76Cart::_x76Command( + uint8_t cmd, uint8_t param, uint8_t pollByte ) const { io::i2cStartWithCS(); - io::i2cWriteByte(command); + io::i2cWriteByte(cmd); if (!io::i2cGetACK()) { io::i2cStopWithCS(); LOG("NACK while sending command"); @@ -187,7 +155,7 @@ Error X76Cart::_x76Command( return X76_NACK; } - if (!io::i2cWriteBytes(dataKey, sizeof(dataKey))) { + if (!io::i2cWriteBytes(_dump.dataKey, sizeof(_dump.dataKey))) { io::i2cStopWithCS(); LOG("NACK while sending data key"); return X76_NACK; @@ -222,17 +190,11 @@ enum X76F041ConfigOp : uint8_t { _X76F041_CFG_ERASE = 0x70 }; -X76F041Cart::X76F041Cart(void) { - Cart(); - - chipType = TYPE_X76F041; -} - -Error X76F041Cart::readPrivateData(void) { +CartError X76F041Cart::readPrivateData(void) { // Reads can be done with any block size, but a single read operation can't // cross 128-byte block boundaries. for (int i = 0; i < 512; i += 128) { - Error error = _x76Command( + auto error = _x76Command( _X76F041_READ | (i >> 8), i & 0xff, _X76F041_ACK_POLL ); if (error) @@ -247,23 +209,23 @@ Error X76F041Cart::readPrivateData(void) { return X76_NACK; } - io::i2cReadBytes(&data[i], 128); + io::i2cReadBytes(&_dump.data[i], 128); io::i2cStopWithCS(); } return NO_ERROR; } -Error X76F041Cart::writeData(void) { +CartError X76F041Cart::writeData(void) { // Writes can only be done in 8-byte blocks. for (int i = 0; i < 512; i += 8) { - Error error = _x76Command( + auto error = _x76Command( _X76F041_WRITE | (i >> 8), i & 0xff, _X76F041_ACK_POLL ); if (error) return error; - if (!io::i2cWriteBytes(&data[i], 8)) { + if (!io::i2cWriteBytes(&_dump.data[i], 8)) { LOG("NACK while sending data bytes"); return X76_NACK; } @@ -274,8 +236,8 @@ Error X76F041Cart::writeData(void) { return NO_ERROR; } -Error X76F041Cart::erase(void) { - Error error = _x76Command( +CartError X76F041Cart::erase(void) { + auto error = _x76Command( _X76F041_CONFIG, _X76F041_CFG_ERASE, _X76F041_ACK_POLL ); if (error) @@ -285,8 +247,8 @@ Error X76F041Cart::erase(void) { return NO_ERROR; } -Error X76F041Cart::setDataKey(const uint8_t *newKey) { - Error error = _x76Command( +CartError X76F041Cart::setDataKey(const uint8_t *key) { + auto error = _x76Command( _X76F041_CONFIG, _X76F041_CFG_SET_DATA_KEY, _X76F041_ACK_POLL ); 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 // gets received correctly. for (int i = 2; i; i--) { - if (!io::i2cWriteBytes(newKey, sizeof(dataKey))) { + if (!io::i2cWriteBytes(key, sizeof(_dump.dataKey))) { io::i2cStopWithCS(); LOG("NACK while setting new data key"); return X76_NACK; @@ -304,8 +266,8 @@ Error X76F041Cart::setDataKey(const uint8_t *newKey) { io::i2cStopWithCS(_X76_WRITE_DELAY); - // Update the data key stored in the class. - memcpy(dataKey, newKey, sizeof(dataKey)); + // Update the data key stored in the dump. + __builtin_memcpy(_dump.dataKey, key, sizeof(_dump.dataKey)); return NO_ERROR; } @@ -319,25 +281,29 @@ enum X76F100Command : uint8_t { _X76F100_ACK_POLL = 0x55 }; -X76F100Cart::X76F100Cart(void) { - Cart(); +// TODO: actually implement this (even though no X76F100 carts were ever made) - 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 */ -ZS01Cart::ZS01Cart(void) { - Cart(); - - chipType = TYPE_ZS01; - flags = HAS_DS2401; -} - -Error ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) { +CartError ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) { io::i2cStart(); + if (!io::i2cWriteBytes( &request.command, sizeof(zs01::Packet), _ZS01_PACKET_DELAY )) { @@ -352,7 +318,8 @@ Error ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) { if (!response.decodeResponse()) return ZS01_CRC_MISMATCH; - _state = response.address; + _encoderState = response.address; + if (response.command != zs01::RESP_NO_ERROR) { LOG("ZS01 error, code=0x%02x", response.command); return ZS01_ERROR; @@ -361,9 +328,9 @@ Error ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) { return NO_ERROR; } -Error ZS01Cart::readCartID(void) { +CartError ZS01Cart::readCartID(void) { zs01::Packet request, response; - Error error; + CartError error; request.address = zs01::ADDR_ZS01_ID; request.encodeReadRequest(); @@ -371,12 +338,12 @@ Error ZS01Cart::readCartID(void) { error = _transact(request, response); if (error) return error; - if (!_validateID(response.data)) + + response.copyTo(_dump.zsID.data); + if (!_dump.zsID.validateDSCRC()) return DS2401_ID_ERROR; - response.copyDataTo(zsID); - - flags |= ZS_ID_OK; + _dump.flags |= DUMP_ZS_ID_OK; request.address = zs01::ADDR_DS2401_ID; request.encodeReadRequest(); @@ -384,114 +351,153 @@ Error ZS01Cart::readCartID(void) { error = _transact(request, response); if (error) return error; - if (!_validateID(response.data)) + + response.copyTo(_dump.cartID.data); + if (!_dump.cartID.validateDSCRC()) return DS2401_ID_ERROR; - response.copyDataTo(cartID); - - flags |= CART_ID_OK; + _dump.flags |= DUMP_CART_ID_OK; return NO_ERROR; } -Error ZS01Cart::readPublicData(void) { +CartError ZS01Cart::readPublicData(void) { zs01::Packet request, response; for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PUBLIC_END; i++) { request.address = i; request.encodeReadRequest(); - Error error = _transact(request, response); + CartError error = _transact(request, response); if (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; } -Error ZS01Cart::readPrivateData(void) { +CartError ZS01Cart::readPrivateData(void) { zs01::Packet request, response; zs01::Key key; - key.unpackFrom(dataKey); + + key.unpackFrom(_dump.dataKey); for (int i = zs01::ADDR_PRIVATE; i < zs01::ADDR_PRIVATE_END; i++) { request.address = i; - request.encodeReadRequest(key, _state); + request.encodeReadRequest(key, _encoderState); - Error error = _transact(request, response); + CartError error = _transact(request, response); if (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.encodeReadRequest(key, _state); + request.encodeReadRequest(key, _encoderState); - Error error = _transact(request, response); + CartError error = _transact(request, response); if (error) return error; - response.copyDataTo(config); + response.copyTo(_dump.config); - flags |= CONFIG_OK; + _dump.flags |= DUMP_CONFIG_OK; return NO_ERROR; } -Error ZS01Cart::writeData(void) { +CartError ZS01Cart::writeData(void) { zs01::Packet request, response; zs01::Key key; - key.unpackFrom(dataKey); + + key.unpackFrom(_dump.dataKey); for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PRIVATE_END; i++) { request.address = i; - request.copyDataFrom(&data[i * sizeof(request.data)]); - request.encodeWriteRequest(key, _state); + request.copyFrom(&_dump.data[i * sizeof(request.data)]); + request.encodeWriteRequest(key, _encoderState); - Error error = _transact(request, response); + CartError error = _transact(request, response); if (error) return error; } request.address = zs01::ADDR_CONFIG; - request.copyDataFrom(config); - request.encodeWriteRequest(key, _state); + request.copyFrom(_dump.config); + request.encodeWriteRequest(key, _encoderState); return _transact(request, response); } -Error ZS01Cart::erase(void) { +CartError ZS01Cart::erase(void) { zs01::Packet request, response; 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.encodeWriteRequest(key, _state); + request.encodeWriteRequest(key, _encoderState); return _transact(request, response); } -Error ZS01Cart::setDataKey(const uint8_t *newKey) { +CartError ZS01Cart::setDataKey(const uint8_t *key) { zs01::Packet request, response; - zs01::Key key; - key.unpackFrom(dataKey); + zs01::Key newKey; + + newKey.unpackFrom(_dump.dataKey); request.address = zs01::ADDR_DATA_KEY; - request.copyDataFrom(newKey); - request.encodeWriteRequest(key, _state); + request.copyFrom(key); + request.encodeWriteRequest(newKey, _encoderState); - Error error = _transact(request, response); + CartError error = _transact(request, response); if (error) return error; - // Update the data key stored in the class. - memcpy(dataKey, newKey, sizeof(dataKey)); + // Update the data key stored in the dump. + __builtin_memcpy(_dump.dataKey, key, sizeof(_dump.dataKey)); 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); + } +} + } diff --git a/src/cartio.hpp b/src/cartio.hpp new file mode 100644 index 0000000..8d4b4fd --- /dev/null +++ b/src/cartio.hpp @@ -0,0 +1,198 @@ + +#pragma once + +#include +#include +#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); + +} diff --git a/src/main.cpp b/src/main.cpp index 5eff63b..8139582 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,7 +99,7 @@ int main(int argc, const char **argv) { !loader.loadTIM(background.tile, "assets/textures/background.tim") || !loader.loadTIM(uiCtx.font.image, "assets/textures/font.tim") || !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"); return 1; diff --git a/src/util.hpp b/src/util.hpp index 4136c92..7eda4c5 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -9,6 +9,15 @@ namespace util { /* Misc. template utilities */ +template 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 static inline T min(T a, T b) { return (a < b) ? a : b; } diff --git a/src/zs01.hpp b/src/zs01.hpp index 926b044..15881e9 100644 --- a/src/zs01.hpp +++ b/src/zs01.hpp @@ -3,7 +3,6 @@ #include #include -#include namespace zs01 { @@ -59,14 +58,14 @@ class Packet { public: uint8_t command, address, data[8], crc[2]; - inline void copyDataFrom(const uint8_t *source) { - memcpy(data, source, sizeof(data)); + inline void copyFrom(const uint8_t *source) { + __builtin_memcpy(data, source, sizeof(data)); } - inline void copyDataTo(uint8_t *dest) const { - memcpy(dest, data, sizeof(data)); + inline void copyTo(uint8_t *dest) const { + __builtin_memcpy(dest, data, sizeof(data)); } - inline void clearData(void) { - memset(data, 0, sizeof(data)); + inline void clear(void) { + __builtin_memset(data, 0, sizeof(data)); } void updateCRC(void);