diff --git a/NEWS.md b/NEWS.md index cdb2700ac..e360fffb7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,6 +15,10 @@ * PSP: Parse the PARAM.SFO file. * Add a "Version" custom property and handle it in all RomData subclasses that have a "Version" or "Revision" field. + * NintendoDS, Nintendo3DSFirm: Initial support for DSi and 3DS NTRBOOT + ROM images. These were previously not detected at all due to missing + fields in the ROM header. + * Thanks to F3l1x_10m for providing test files. * Other changes: * KDE Frameworks 6: Also use the file size unit dialect specified in diff --git a/src/libromdata/Handheld/Nintendo3DSFirm.cpp b/src/libromdata/Handheld/Nintendo3DSFirm.cpp index b8cb4d9bc..c68950d3a 100644 --- a/src/libromdata/Handheld/Nintendo3DSFirm.cpp +++ b/src/libromdata/Handheld/Nintendo3DSFirm.cpp @@ -6,6 +6,8 @@ * SPDX-License-Identifier: GPL-2.0-or-later * ***************************************************************************/ +#include "librpbase/config.librpbase.h" + #include "Nintendo3DSFirm.hpp" #include "RomData_p.hpp" @@ -14,6 +16,11 @@ // Other rom-properties libraries #include "librpbase/crypto/Hash.hpp" +#ifdef ENABLE_DECRYPTION +# include "librpbase/crypto/AesCipherFactory.hpp" +# include "librpbase/crypto/IAesCipher.hpp" +# include "librpbase/crypto/KeyManager.hpp" +#endif /* ENABLE_DECRYPTION */ using namespace LibRpBase; using namespace LibRpFile; using namespace LibRpText; @@ -29,12 +36,15 @@ using std::array; using std::string; using std::unique_ptr; +// Uninitialized vector class +#include "uvector.h" + namespace LibRomData { class Nintendo3DSFirmPrivate final : public RomDataPrivate { public: - explicit Nintendo3DSFirmPrivate(const IRpFilePtr &file); + explicit Nintendo3DSFirmPrivate(const IRpFilePtr &file, Nintendo3DSFirm::StorageType storageType); private: typedef RomDataPrivate super; @@ -50,6 +60,32 @@ class Nintendo3DSFirmPrivate final : public RomDataPrivate // Firmware header // NOTE: Must be byteswapped on access. N3DS_FIRM_Header_t firmHeader; + + // Storage type + Nintendo3DSFirm::StorageType storageType; + + // Retail/debug? + enum class CryptoType : int8_t { + Unknown = -1, + + Retail = 0, + Debug = 1, + }; + CryptoType cryptoType; + +public: + static constexpr off64_t FIRM_MAX_SIZE = 4*1024*1024; + + /** + * Load the firmware binary. (excluding header) + * + * For NTR/SPI, the binary will also be decrypted + * if the keys are available. + * + * @param firmBuf Buffer for the firmware binary + * @return 0 on success; negative POSIX error code on error. + */ + int loadFirmBin(rp::uvector &firmBuf); }; ROMDATA_IMPL(Nintendo3DSFirm) @@ -74,13 +110,155 @@ const RomDataInfo Nintendo3DSFirmPrivate::romDataInfo = { "Nintendo3DSFirm", exts.data(), mimeTypes.data() }; -Nintendo3DSFirmPrivate::Nintendo3DSFirmPrivate(const IRpFilePtr &file) +Nintendo3DSFirmPrivate::Nintendo3DSFirmPrivate(const IRpFilePtr &file, Nintendo3DSFirm::StorageType storageType) : super(file, &romDataInfo) + , storageType(storageType) + , cryptoType(CryptoType::Unknown) { // Clear the various structs. memset(&firmHeader, 0, sizeof(firmHeader)); } +/** + * Load the firmware binary. (excluding header) + * + * For NTR/SPI, the binary will also be decrypted + * if the keys are available. + * + * @param firmBuf Buffer for the firmware binary + * @return 0 on success; negative POSIX error code on error. + */ +int Nintendo3DSFirmPrivate::loadFirmBin(rp::uvector &firmBuf) +{ + firmBuf.clear(); + + off64_t szFile64 = file->size(); + if (szFile64 < static_cast(sizeof(N3DS_FIRM_Header_t))) { + // Firmware file is too small... + return -EIO; + } + szFile64 -= sizeof(N3DS_FIRM_Header_t); + if (szFile64 > FIRM_MAX_SIZE) { + // Firmware file is too big. + return -E2BIG; // FIXME: Probably wrong... + } + + // Firmware binary is 4 MB or less. + const unsigned int szFile = static_cast(szFile64); + firmBuf.resize(szFile); + size_t size = file->seekAndRead(sizeof(N3DS_FIRM_Header_t), firmBuf.data(), firmBuf.size()); + if (size != szFile) { + // Error reading the firmware binary. + firmBuf.clear(); + int ret = file->lastError(); + if (ret == 0) { + ret = -EIO; + } + return ret; + } + +#ifdef ENABLE_DECRYPTION + if (storageType > Nintendo3DSFirm::StorageType::NAND) { + // Decrypt the sections. + // - IV is [offset, load_addr, size, size] (all are packed as 32-bit little-endian) + // - Key depends on prod/devel and NTR/SPI. + // TODO: Decrypt the signature to determine prod vs. devel. Assuming debug if not using sighaxed for now. + // Check for NCCH for official images, or maybe "has at least one 32-bit 00000000" for unofficial? + const char *keyName; + switch (storageType) { + default: + assert(!"Key not supported?!?!"); + // TODO: Some other error? + return -ENOENT; + case Nintendo3DSFirm::StorageType::NTR: + keyName = (cryptoType != CryptoType::Debug) ? "ctr-ntr-boot" : "ctr-dev-ntr-boot"; + break; + case Nintendo3DSFirm::StorageType::SPI: + keyName = (cryptoType != CryptoType::Debug) ? "ctr-spi-boot" : "ctr-dev-spi-boot"; + break; + } + + KeyManager *const keyManager = KeyManager::instance(); + assert(keyManager != nullptr); + if (!keyManager) { + // TODO: Some other error? + return -EIO; + } + + KeyManager::KeyData_t keyData; + KeyManager::VerifyResult res = keyManager->get(keyName, &keyData); + if (res != KeyManager::VerifyResult::OK) { + // TODO: Some other error? + return -EIO; + } + + // Initialize an AesCipher. + unique_ptr cipher(AesCipherFactory::create()); + assert((bool)cipher); + if (!cipher) { + // No AES cipher is available... + // TODO: Some other error? + return -ENOTSUP; + } + + // Set decryption parameters. + cipher->setChainingMode(IAesCipher::ChainingMode::CBC); + + // Decrypt each section. + for (const N3DS_FIRM_Section_Header_t §ion : firmHeader.sections) { + if (section.size == 0) { + // Empty section. Skip it. + continue; + } + + uint32_t addr = le32_to_cpu(section.offset); + assert(addr >= sizeof(N3DS_FIRM_Header_t)); + if (addr < sizeof(N3DS_FIRM_Header_t)) { + // Invalid starting address. + // TODO: Return an error. + continue; + } + addr -= sizeof(N3DS_FIRM_Header_t); + + uint32_t size = le32_to_cpu(section.size); + assert(addr + size <= firmBuf.size()); + if (addr + size > firmBuf.size()) { + // Out of bounds? + // TODO: Return an error. + continue; + } + + // Section sizes must be a mutiple of 16. + assert(size % 16 == 0); + if (size % 16 != 0) { + // Round it up... + size &= ~15; + size += 16; + } + + // Set the key and IV. + union { + uint32_t u32[4]; + uint8_t u8[16]; + } iv; + iv.u32[0] = section.offset; + iv.u32[1] = section.load_addr; + iv.u32[2] = section.size; + iv.u32[3] = section.size; + cipher->setKey(keyData.key, keyData.length); + cipher->setIV(iv.u8, sizeof(iv.u8)); + + // Decrypt the data. + // TODO: Check for errors. + cipher->decrypt(&firmBuf[addr], size); + } + } +#endif /* ENABLE_DECRYPTION */ + + // TODO: Decrypt the firmware binary, if necessary. + return 0; +} + /** Nintendo3DSFirm **/ /** @@ -94,10 +272,11 @@ Nintendo3DSFirmPrivate::Nintendo3DSFirmPrivate(const IRpFilePtr &file) * * NOTE: Check isValid() to determine if this is a valid ROM. * - * @param file Open ROM image. + * @param file Open ROM image + * @param type Storage type; used to determine if decryption is needed. */ -Nintendo3DSFirm::Nintendo3DSFirm(const IRpFilePtr &file) - : super(new Nintendo3DSFirmPrivate(file)) +Nintendo3DSFirm::Nintendo3DSFirm(const IRpFilePtr &file, StorageType type) + : super(new Nintendo3DSFirmPrivate(file, type)) { RP_D(Nintendo3DSFirm); d->mimeType = "application/x-nintendo-3ds-firm"; // unofficial, not on fd.o @@ -126,6 +305,29 @@ Nintendo3DSFirm::Nintendo3DSFirm(const IRpFilePtr &file) if (!d->isValid) { d->file.reset(); + return; + } + + // Check for a sighaxed signature. + // If present, we can determine the crypto type. + // Otherwise, we'll need to use heuristics, and/or try decrypting + // the signature with various public keys. + const uint32_t first4 = be32_to_cpu(d->firmHeader.signature32[0]); + switch (first4) { + case 0x37E96B10: + // NTR/SPI retail + // FIXME: Distinguish between NTR and SPI? + d->cryptoType = Nintendo3DSFirmPrivate::CryptoType::Retail; + break; + case 0x18722BC7: + // NTR/SPI debug + // FIXME: Distinguish between NTR and SPI? + d->cryptoType = Nintendo3DSFirmPrivate::CryptoType::Debug; + break; + default: + // Unknown... + // TODO: Try to decrypt the signature? + break; } } @@ -211,19 +413,10 @@ int Nintendo3DSFirm::loadFieldData(void) d->fields.reserve(6); // Maximum of 6 fields. // Read the firmware binary. - unique_ptr firmBuf; - unsigned int szFile = 0; - if (d->file->size() <= 4*1024*1024) { - // Firmware binary is 4 MB or less. - szFile = static_cast(d->file->size()); - firmBuf.reset(new uint8_t[szFile]); - d->file->rewind(); - size_t size = d->file->read(firmBuf.get(), szFile); - if (size != szFile) { - // Error reading the firmware binary. - firmBuf.reset(); - } - } + // NOTE: firmBuf does *not* include the FIRM header. + rp::uvector firmBuf; + int ret = d->loadFirmBin(firmBuf); + // TODO: If decryption failed, show a warning. // If both ARM11 and ARM9 entry points are non-zero, // check if this is an official 3DS firmware binary. @@ -237,13 +430,33 @@ int Nintendo3DSFirm::loadFieldData(void) // Calculate the CRC32 and look it up. // TODO: Check firmBuf before initializing CRC32? Hash crc32Hash(Hash::Algorithm::CRC32); - if (crc32Hash.isUsable() && firmBuf) { - crc32Hash.process(firmBuf.get(), szFile); + if (crc32Hash.isUsable() && !firmBuf.empty()) { + // NOTE: The CRC32s include the FIRM header. + crc32Hash.process(firmHeader, sizeof(*firmHeader)); + crc32Hash.process(firmBuf.data(), firmBuf.size()); const uint32_t crc = crc32Hash.getHash32(); firmBin = Nintendo3DSFirmData::lookup_firmBin(crc); if (firmBin != nullptr) { // Official firmware binary. - firmBinDesc = (firmBin->isNew3DS ? "New3DS FIRM" : "Old3DS FIRM"); + if (firmBin->flags & Nintendo3DSFirmData::FLAG_New3DS) { + // New3DS + if (firmBin->flags & Nintendo3DSFirmData::FLAG_AGB_FIRM) { + firmBinDesc = "New3DS AGB_FIRM"; + } else if (firmBin->flags & Nintendo3DSFirmData::FLAG_TWL_FIRM) { + firmBinDesc = "New3DS TWL_FIRM"; + } else { + firmBinDesc = "New3DS NATIVE_FIRM"; + } + } else { + // Old3DS + if (firmBin->flags & Nintendo3DSFirmData::FLAG_AGB_FIRM) { + firmBinDesc = "Old3DS AGB_FIRM"; + } else if (firmBin->flags & Nintendo3DSFirmData::FLAG_TWL_FIRM) { + firmBinDesc = "Old3DS TWL_FIRM"; + } else { + firmBinDesc = "Old3DS NATIVE_FIRM"; + } + } } else { // Check for a custom FIRM. checkCustomFIRM = true; @@ -269,7 +482,7 @@ int Nintendo3DSFirm::loadFieldData(void) if (!memcmp(&firmHeader->reserved[0x2D], "B9S", 3)) { // This is Boot9Strap. firmBinDesc = "Boot9Strap"; - } else if (firmBuf) { + } else if (!firmBuf.empty()) { // Check for sighax installer. // NOTE: String has a NULL terminator. static constexpr char sighax_magic[] = "3DS BOOTHAX INS"; @@ -293,7 +506,9 @@ int Nintendo3DSFirm::loadFieldData(void) // System version d->fields.addField_string(C_("Nintendo3DSFirm", "System Version"), fmt::format(FSTR("{:d}.{:d}"), firmBin->sys.major, firmBin->sys.minor)); - } else if (firmBuf && checkARM9) { + + // TODO: Check sections for NCCH headers? + } else if (!firmBuf.empty() && checkARM9) { // Check for ARM9 homebrew // Version strings @@ -318,7 +533,7 @@ int Nintendo3DSFirm::loadFieldData(void) string s_verstr; for (const auto &p : arm9VerStr_tbl) { const char *verstr = static_cast(memmem( - firmBuf.get(), szFile, p.searchstr, p.searchlen)); + firmBuf.data(), firmBuf.size(), p.searchstr, p.searchlen)); if (!verstr) continue; @@ -326,7 +541,7 @@ int Nintendo3DSFirm::loadFieldData(void) // Version does NOT include the 'v' character. verstr += p.searchlen; - const char *end = (const char*)firmBuf.get() + szFile; + const char *const end = reinterpret_cast(firmBuf.data()) + firmBuf.size(); int count = 0; while (verstr < end && count < 32 && verstr[count] != 0 && !ISSPACE(verstr[count]) && verstr[count] != ')') @@ -351,18 +566,18 @@ int Nintendo3DSFirm::loadFieldData(void) const uint32_t first4 = be32_to_cpu(firmHeader->signature32[0]); struct sighaxStatus_tbl_t { uint32_t first4; - char status[12]; + char status[16]; }; static constexpr array sighaxStatus_tbl = {{ {0xB6724531, "NAND retail"}, // SciresM {0x6EFF209C, "NAND retail"}, // sighax.com - {0x88697CDC, "NAND devkit"}, // SciresM + {0x88697CDC, "NAND debug"}, // SciresM {0x6CF52F89, "NCSD retail"}, - {0x53CB0E4E, "NCSD devkit"}, + {0x53CB0E4E, "NCSD debug"}, - {0x37E96B10, "SPI retail"}, - {0x18722BC7, "SPI devkit"}, + {0x37E96B10, "NTR/SPI retail"}, + {0x18722BC7, "NTR/SPI debug"}, }}; const char *s_sighax_status = nullptr; diff --git a/src/libromdata/Handheld/Nintendo3DSFirm.hpp b/src/libromdata/Handheld/Nintendo3DSFirm.hpp index e9bfa5ce5..5a584f685 100644 --- a/src/libromdata/Handheld/Nintendo3DSFirm.hpp +++ b/src/libromdata/Handheld/Nintendo3DSFirm.hpp @@ -12,7 +12,51 @@ namespace LibRomData { -ROMDATA_DECL_BEGIN(Nintendo3DSFirm) +ROMDATA_DECL_BEGIN_NO_CTOR(Nintendo3DSFirm) + +public: + enum class StorageType { + Unknown = -1, + + NAND = 0, // Standard NAND boot; not encrypted + NTR = 1, // NTRBOOT; encrypted + SPI = 2, // SPIBOOT; encrypted + }; + + /** + * Read a Nintendo 3DS firmware binary. + * + * A ROM image must be opened by the caller. The file handle + * will be ref()'d and must be kept open in order to load + * data from the ROM image. + * + * To close the file, either delete this object or call close(). + * + * NOTE: Check isValid() to determine if this is a valid ROM. + * + * @param file Open ROM image + */ + explicit Nintendo3DSFirm(const LibRpFile::IRpFilePtr &file) + : Nintendo3DSFirm(file, StorageType::Unknown) + {} + + /** + * Read a Nintendo 3DS firmware binary. + * + * A ROM image must be opened by the caller. The file handle + * will be ref()'d and must be kept open in order to load + * data from the ROM image. + * + * To close the file, either delete this object or call close(). + * + * NOTE: Check isValid() to determine if this is a valid ROM. + * + * @param file Open ROM image + * @param storageType Storage type; used to determine if decryption is needed. + */ + explicit Nintendo3DSFirm(const LibRpFile::IRpFilePtr &file, StorageType storageType); + +ROMDATA_DECL_COMMON_FNS() ROMDATA_DECL_END() } // namespace LibRomData diff --git a/src/libromdata/Handheld/NintendoDS.cpp b/src/libromdata/Handheld/NintendoDS.cpp index 3e77a8b47..39bed0fc5 100644 --- a/src/libromdata/Handheld/NintendoDS.cpp +++ b/src/libromdata/Handheld/NintendoDS.cpp @@ -14,6 +14,10 @@ #include "data/NintendoLanguage.hpp" #include "../Console/WiiCommon.hpp" +// for 3DS NTRBOOT ROMs +#include "Nintendo3DSFirm.hpp" +#include "n3ds_firm_structs.h" + // Other rom-properties libraries #include "librpbase/config/Config.hpp" #include "librpbase/disc/DiscReader.hpp" @@ -49,7 +53,7 @@ const array NintendoDSPrivate::exts = {{ nullptr }}; -const array NintendoDSPrivate::mimeTypes = {{ +const array NintendoDSPrivate::mimeTypes = {{ // Unofficial MIME types from FreeDesktop.org. "application/x-nintendo-ds-rom", @@ -59,6 +63,8 @@ const array NintendoDSPrivate::mimeTypes = {{ // Unofficial MIME types. // TODO: Get these upstreamed on FreeDesktop.org. "application/x-nintendo-dsi-rom", + "application/x-nintendo-dsi-ntrboot", + "application/x-nintendo-3ds-ntrboot", nullptr }}; @@ -103,8 +109,8 @@ inline string NintendoDSPrivate::getGameID(void) const */ std::string NintendoDSPrivate::dsi_getTitleID(void) const { - assert(romHeader.unitcode & 0x02); - if (!(romHeader.unitcode & 0x02)) { + assert(isDSi()); + if (!(isDSi())) { return {}; } @@ -152,6 +158,32 @@ int NintendoDSPrivate::loadIconTitleData(void) return 0; } +/** + * Get the publisher. + * @return Publisher, or empty string on error. + */ +string NintendoDSPrivate::getPublisher(void) +{ + const char *const publisher = NintendoPublishers::lookup(romHeader.company); + if (publisher) { + return publisher; + } + + // Unknown publisher. Print the company code as two characters if they're + // both alphanumeric; otherwise, hexadecimal. + string s_publisher; + if (isalnum_ascii(romHeader.company[0]) && isalnum_ascii(romHeader.company[1])) { + s_publisher = fmt::format(FRUN(C_("RomData", "Unknown ({:c}{:c})")), + romHeader.company[0], romHeader.company[1]); + } else { + s_publisher = fmt::format(FRUN(C_("RomData", "Unknown ({:0>2X} {:0>2X})")), + static_cast(romHeader.company[0]), + static_cast(romHeader.company[1])); + } + + return s_publisher; +} + /** * Convert a Nintendo DS(i) region value to a GameTDB language code. * @param ndsRegion Nintendo DS region. @@ -424,10 +456,29 @@ NintendoDS::NintendoDS(const IRpFilePtr &file, bool cia) d->secData = d->checkNDSSecurityData(); d->secArea = d->checkNDSSecureArea(); - // Set the MIME type. (unofficial) - d->mimeType = (d->romType == NintendoDSPrivate::RomType::DSi_Exclusive) - ? "application/x-nintendo-dsi-rom" // (not on fd.o) - : "application/x-nintendo-ds-rom"; + // Set the file type and MIME type. + // NOTE: RomData defaults to FileType::ROM_Image, so file type will + // only be set for NTRBOOT ROMs. + switch (d->romType) { + case NintendoDSPrivate::RomType::DSi_Exclusive: + // NOTE: Not on FreeDesktop.org. + d->mimeType = "application/x-nintendo-dsi-rom"; + break; + + case NintendoDSPrivate::RomType::NTRBOOT_DSi: + d->mimeType = "application/x-nintendo-dsi-rom"; + d->fileType = FileType::FirmwareBinary; // TODO: Better file type? + break; + + case NintendoDSPrivate::RomType::NTRBOOT_3DS: + d->mimeType = "application/x-nintendo-3ds-rom"; + d->fileType = FileType::FirmwareBinary; // TODO: Better file type? + break; + + default: + d->mimeType = "application/x-nintendo-ds-rom"; + break; + } } /** @@ -478,6 +529,42 @@ int NintendoDS::isRomSupported_static(const DetectInfo *info) return static_cast(NintendoDSPrivate::RomType::NDS_Slot2); } + // NTRBOOT ROMs don't have a valid logo, but *do* have port 0x40001A4 settings. + // NOTE: 3DS NTRBOOT matches NTR settings, but DSi NTRBOOT has its own? + if ((romHeader->cardControl13 == cpu_to_le32(NDS_CARD_CONTROL_13_NTR) && + romHeader->cardControlBF == cpu_to_le32(NDS_CARD_CONTROL_BF_NTR)) || + (romHeader->cardControl13 == cpu_to_le32(NDS_CARD_CONTROL_13_NTRBOOT_TWL) && + romHeader->cardControlBF == cpu_to_le32(NDS_CARD_CONTROL_BF_NTRBOOT_TWL))) + { + // It's an NTRBOOT ROM. + // NOTE: DSi NTRBOOT ROMs have valid data in the DSi-specific header area, + // up to the "unknown" field (0xFF?), whereas 3DS NTRBOOT ROMs have a + // mostly empty header. + bool isZero = true; + do { +#define CHECK_DSi_ARRAY(arr) \ + for (size_t i = 0; isZero && i < ARRAY_SIZE(romHeader->dsi.arr); i++) { \ + if (romHeader->dsi.arr[i] != 0) { \ + isZero = false; \ + break; \ + } \ + } \ + if (!isZero) break + + CHECK_DSi_ARRAY(global_mbk); + CHECK_DSi_ARRAY(arm9_mbk); + CHECK_DSi_ARRAY(arm7_mbk); + CHECK_DSi_ARRAY(arm9_mbk9_master); + // FIXME: Is this value always 0xFF for DSi NTRBOOT? + if (romHeader->dsi.unknown & 0x80) { + isZero = false; + } + } while (0); + + return (isZero) ? static_cast(NintendoDSPrivate::RomType::NTRBOOT_3DS) + : static_cast(NintendoDSPrivate::RomType::NTRBOOT_DSi); + } + // Not supported. return static_cast(NintendoDSPrivate::RomType::Unknown); } @@ -501,43 +588,54 @@ const char *NintendoDS::systemName(unsigned int type) const "NintendoDS::systemName() array index optimization needs to be updated."); // Bits 0-1: Type. (long, short, abbreviation) - // Bit 2: 0 for NDS, 1 for DSi-exclusive. - // Bit 3: 0 for worldwide, 1 for China. (iQue DS) - static const array sysNames = {{ - // Nintendo (worldwide) + // Bit 2: 0 for worldwide, 1 for China. (iQue DS) + static const array sysNames_NDS = {{ "Nintendo DS", "Nintendo DS", "NDS", nullptr, + "iQue DS", "iQue DS", "NDS", nullptr + }}; + static const array sysNames_DSi = {{ "Nintendo DSi", "Nintendo DSi", "DSi", nullptr, - - // iQue (China) - "iQue DS", "iQue DS", "NDS", nullptr, "iQue DSi", "iQue DSi", "DSi", nullptr }}; + // NTRBOOT (3DS) doesn't have a region code. + static const array sysNames_3DS = {{ + "Nintendo 3DS", "Nintendo 3DS", "3DS", nullptr, + }}; + // "iQue" is only used if the localized system name is requested // *and* the ROM's region code is China only. unsigned int idx = (type & SYSNAME_TYPE_MASK); - if (d->romType == NintendoDSPrivate::RomType::DSi_Exclusive) { - // DSi-exclusive game. - idx |= (1U << 2); - if ((type & SYSNAME_REGION_MASK) == SYSNAME_REGION_ROM_LOCAL) { - if ((d->romHeader.dsi.region_code == cpu_to_le32(DSi_REGION_CHINA)) || - (d->romHeader.nds_region & 0x80)) - { - // iQue DSi. - idx |= (1U << 3); + switch (d->romType) { + case NintendoDSPrivate::RomType::DSi_Exclusive: + case NintendoDSPrivate::RomType::NTRBOOT_DSi: + // TODO: Is the region code relevant for NTRBOOT DSi? + if ((type & SYSNAME_REGION_MASK) == SYSNAME_REGION_ROM_LOCAL) { + if ((d->romHeader.dsi.region_code == cpu_to_le32(DSi_REGION_CHINA)) || + (d->romHeader.nds_region & 0x80)) + { + // iQue DSi. + idx |= (1U << 3); + } } - } - } else { - // NDS-only and/or DSi-enhanced game. - if ((type & SYSNAME_REGION_MASK) == SYSNAME_REGION_ROM_LOCAL) { - if (d->romHeader.nds_region & 0x80) { - // iQue DS. - idx |= (1U << 3); + return sysNames_DSi[idx]; + + case NintendoDSPrivate::RomType::NTRBOOT_3DS: + return sysNames_3DS[idx]; + + default: + // NDS-only and/or DSi-enhanced game. + if ((type & SYSNAME_REGION_MASK) == SYSNAME_REGION_ROM_LOCAL) { + if (d->romHeader.nds_region & 0x80) { + // iQue DS. + idx |= (1U << 3); + } } - } + return sysNames_NDS[idx]; } - return sysNames[idx]; + // Not recognized... + return nullptr; } /** @@ -673,10 +771,12 @@ int NintendoDS::loadFieldData(void) // - Show IR cart and/or other accessories? (NAND ROM, etc.) const char *nds_romType; const uint16_t dsi_filetype = le16_to_cpu(romHeader->dsi.title_id.catID); - if (d->cia || ((romHeader->unitcode & NintendoDSPrivate::DS_HW_DSi) && + if (d->isNTRBOOT()) { + nds_romType = "NTRBOOT"; + } else if (d->cia || ((romHeader->unitcode & NintendoDSPrivate::DS_HW_DSi) && dsi_filetype != DSi_FTYPE_CARTRIDGE)) { - // DSiWare. + // DSiWare // TODO: Verify games that are available as both // cartridge and DSiWare. if (dsi_filetype == DSi_FTYPE_DSiWARE) { @@ -719,26 +819,8 @@ int NintendoDS::loadFieldData(void) d->fields.addField_string(C_("RomData", "Game ID"), d->getGameID()); // Publisher - const char *const publisher_title = C_("RomData", "Publisher"); - const char *const publisher = NintendoPublishers::lookup(romHeader->company); - if (publisher) { - d->fields.addField_string(publisher_title, publisher); - } else { - if (isalnum_ascii(romHeader->company[0]) && isalnum_ascii(romHeader->company[1])) { - const array s_company = {{ - romHeader->company[0], - romHeader->company[1], - '\0' - }}; - d->fields.addField_string(publisher_title, - fmt::format(FRUN(C_("RomData", "Unknown ({:s})")), s_company.data())); - } else { - d->fields.addField_string(publisher_title, - fmt::format(FRUN(C_("RomData", "Unknown ({:0>2X} {:0>2X})")), - static_cast(romHeader->company[0]), - static_cast(romHeader->company[1]))); - } - } + // TODO: Use publisher from the full title? + d->fields.addField_string(C_("RomData", "Publisher"), d->getPublisher()); // ROM version d->fields.addField_string_numeric(C_("RomData", "Revision"), @@ -802,20 +884,67 @@ int NintendoDS::loadFieldData(void) d->fields.addField_bitfield(C_("NintendoDS", "DS Region Code"), v_nds_region_bitfield_names, 0, nds_region); - if (!(hw_type & NintendoDSPrivate::DS_HW_DSi)) { - // Not a DSi-enhanced or DSi-exclusive ROM image. - if (romHeader->dsi.flags != 0) { - // DSi flags. - // NOTE: These are present in NDS games released after the DSi, - // even if the game isn't DSi-enhanced. - d->fields.addTab("DSi"); - auto *const vv_dsi_flags = d->getDSiFlagsStringVector(); - RomFields::AFLD_PARAMS params(RomFields::RFT_LISTDATA_CHECKBOXES, 8); - params.headers = nullptr; - params.data.single = vv_dsi_flags; - params.mxd.checkboxes = romHeader->dsi.flags; - d->fields.addField_listData(C_("RomData", "Flags"), ¶ms); + if (!d->isDSi() && romHeader->dsi.flags != 0) { + // Not a DSi-enhanced or DSi-exclusive ROM image, + // but DSi flags are present. + // NOTE: These are present in NDS games released after the DSi, + // even if the game isn't DSi-enhanced. + d->fields.addTab("DSi"); + auto *const vv_dsi_flags = d->getDSiFlagsStringVector(); + RomFields::AFLD_PARAMS params(RomFields::RFT_LISTDATA_CHECKBOXES, 8); + params.headers = nullptr; + params.data.single = vv_dsi_flags; + params.mxd.checkboxes = romHeader->dsi.flags; + d->fields.addField_listData(C_("RomData", "Flags"), ¶ms); + } + + if (d->isNTRBOOT()) do { + // NTRBOOT + if (d->romType == NintendoDSPrivate::RomType::NTRBOOT_3DS) { + // Add a FIRM tab. + + // Read the FIRM header and determine the total amount of data. + N3DS_FIRM_Header_t firmHeader; + size_t size = d->file->seekAndRead(N3DS_NTRBOOT_FIRM_OFFSET, &firmHeader, sizeof(firmHeader)); + if (size != sizeof(firmHeader)) { + break; + } + assert(firmHeader.magic == cpu_to_be32(N3DS_FIRM_MAGIC)); + if (firmHeader.magic != cpu_to_be32(N3DS_FIRM_MAGIC)) { + break; + } + + off64_t firmSize = sizeof(firmHeader); + for (const N3DS_FIRM_Section_Header_t §ion : firmHeader.sections) { + firmSize += le32_to_cpu(section.size); + } + + // Create a DiscReader for the FIRM. + IDiscReaderPtr discReader = std::make_shared(d->file, N3DS_NTRBOOT_FIRM_OFFSET, firmSize); + if (!discReader->isOpen()) { + // Failed to open the DiscReader. + break; + } + // Read the FIRM data. + unique_ptr firmFile(new Nintendo3DSFirm(discReader, Nintendo3DSFirm::StorageType::NTR)); + if (!firmFile->isValid()) { + // Failed to open the Nintendo3DSFirm. + break; + } + + const RomFields *const other = firmFile->fields(); + assert(other != nullptr); + if (other) { + // Add the fields. + d->fields.addTab("FIRM"); + d->fields.addFields_romFields(other, -1); + } } + // TODO: DSi NTRBOOT stuff? + } while (0); + + if (!d->isDSi()) { + // Remainder of function is for DSi-enhanced or DSi-exclusive ROMs only. return d->fields.count(); } @@ -1042,25 +1171,7 @@ int NintendoDS::loadMetaData(void) // Publisher // TODO: Use publisher from the full title? - const char *const publisher = NintendoPublishers::lookup(romHeader->company); - if (publisher) { - d->metaData.addMetaData_string(Property::Publisher, publisher); - } else { - if (isalnum_ascii(romHeader->company[0]) && isalnum_ascii(romHeader->company[1])) { - const array s_company = {{ - romHeader->company[0], - romHeader->company[1], - '\0' - }}; - d->metaData.addMetaData_string(Property::Publisher, - fmt::format(FRUN(C_("RomData", "Unknown ({:s})")), s_company.data())); - } else { - d->metaData.addMetaData_string(Property::Publisher, - fmt::format(FRUN(C_("RomData", "Unknown ({:0>2X} {:0>2X})")), - static_cast(romHeader->company[0]), - static_cast(romHeader->company[1]))); - } - } + d->metaData.addMetaData_string(Property::Publisher, d->getPublisher()); /** Custom properties! **/ @@ -1194,6 +1305,11 @@ int NintendoDS::extURLs(ImageType imageType, vector &extURLs, int size) return -ENOENT; } + if (d->isNTRBOOT()) { + // NTRBOOT ROMs don't have any external images. (...yet) + return -ENOENT; + } + if (d->isDSi()) { // Check for DSi SRLs that aren't cartridge dumps. // TODO: Does GameTDB have DSiWare covers? diff --git a/src/libromdata/Handheld/NintendoDS_p.hpp b/src/libromdata/Handheld/NintendoDS_p.hpp index 1f20ae9c3..9d5701f50 100644 --- a/src/libromdata/Handheld/NintendoDS_p.hpp +++ b/src/libromdata/Handheld/NintendoDS_p.hpp @@ -40,7 +40,7 @@ class NintendoDSPrivate final : public LibRpBase::RomDataPrivate public: /** RomDataInfo **/ static const std::array exts; - static const std::array mimeTypes; + static const std::array mimeTypes; static const LibRpBase::RomDataInfo romDataInfo; public: @@ -90,6 +90,9 @@ class NintendoDSPrivate final : public LibRpBase::RomDataPrivate DSi_Enhanced = 2, // Nintendo DSi-enhanced ROM DSi_Exclusive = 3, // Nintendo DSi-exclusive ROM + NTRBOOT_DSi = 4, // NTRBOOT for DSi repair + NTRBOOT_3DS = 5, // NTRBOOT for 3DS repair + Max }; RomType romType; @@ -114,7 +117,31 @@ class NintendoDSPrivate final : public LibRpBase::RomDataPrivate */ inline bool isDSi(void) const { - return !!(romHeader.unitcode & 0x02); + switch (romType) { + case RomType::DSi_Enhanced: + case RomType::DSi_Exclusive: + case RomType::NTRBOOT_DSi: + return true; + + default: + return false; + } + } + + /** + * Is this an NTRBOOT ROM? + * @return True if it is; false if it isn't. + */ + inline bool isNTRBOOT(void) const + { + switch (romType) { + case RomType::NTRBOOT_DSi: + case RomType::NTRBOOT_3DS: + return true; + + default: + return false; + } } /** @@ -135,6 +162,12 @@ class NintendoDSPrivate final : public LibRpBase::RomDataPrivate */ int loadIconTitleData(void); + /** + * Get the publisher. + * @return Publisher, or empty string on error. + */ + std::string getPublisher(void); + // If true, this is an SRL in a 3DS CIA. // Some fields shouldn't be displayed. bool cia; diff --git a/src/libromdata/Handheld/n3ds_firm_structs.h b/src/libromdata/Handheld/n3ds_firm_structs.h index 9399abee8..bf3a2d39f 100644 --- a/src/libromdata/Handheld/n3ds_firm_structs.h +++ b/src/libromdata/Handheld/n3ds_firm_structs.h @@ -2,7 +2,7 @@ * ROM Properties Page shell extension. (libromdata) * * n3ds_firm_structs.h: Nintendo 3DS firmware data structures. * * * - * Copyright (c) 2016-2024 by David Korth. * + * Copyright (c) 2016-2026 by David Korth. * * SPDX-License-Identifier: GPL-2.0-or-later * ***************************************************************************/ @@ -29,7 +29,7 @@ typedef struct _N3DS_FIRM_Section_Header_t { uint32_t copy_method; // [0x00C] 0 = NDMA, 1 = XDMA, 2 = CPU memcpy() uint8_t sha256[32]; // [0x010] SHA-256 of the previous fields } N3DS_FIRM_Section_Header_t; -ASSERT_STRUCT(_N3DS_FIRM_Section_Header_t, 48); +ASSERT_STRUCT(N3DS_FIRM_Section_Header_t, 48); /** * Nintendo 3DS firmware binary header struct. @@ -50,7 +50,10 @@ typedef struct _N3DS_FIRM_Header_t { uint32_t signature32[0x100/4]; // [0x100] RSA-2048 signature (uint32_t version) }; } N3DS_FIRM_Header_t; -ASSERT_STRUCT(_N3DS_FIRM_Header_t, 512); +ASSERT_STRUCT(N3DS_FIRM_Header_t, 512); + +// NTRBOOT: FIRM is located at 0x7E00. +#define N3DS_NTRBOOT_FIRM_OFFSET 0x7E00 #ifdef __cplusplus } diff --git a/src/libromdata/Handheld/nds_structs.h b/src/libromdata/Handheld/nds_structs.h index 830d5e54c..a72280fa6 100644 --- a/src/libromdata/Handheld/nds_structs.h +++ b/src/libromdata/Handheld/nds_structs.h @@ -16,6 +16,32 @@ extern "C" { #endif +/** + * Nintendo DS: arm9/arm7 code info struct + * + * All fields are little-endian. + */ +typedef struct _NDS_CodeInfo_t { + uint32_t rom_offset; // [0x000] + uint32_t entry_address; // [0x004] + uint32_t ram_address; // [0x008] + uint32_t size; // [0x00C] +} NDS_CodeInfo_t; +ASSERT_STRUCT(NDS_CodeInfo_t, 16); + +/** + * Nintendo DSi: arm9i/arm7i code info struct + * + * All fields are little-endian. + */ +typedef struct _DSi_CodeInfo_t { + uint32_t rom_offset; // [0x000] On arm9i: Usually 0xXX03000h, where XX is the 1MB boundary after the NDS area. + uint32_t arm7i_param_addr; // [0x004] For arm7i only: Pointer to base address where structures are passed to the title. + uint32_t load_address; // [0x008] + uint32_t size; // [0x00C] +} DSi_CodeInfo_t; +ASSERT_STRUCT(DSi_CodeInfo_t, 16); + /** * Nintendo DS ROM header. * This matches the ROM header format exactly. @@ -24,169 +50,146 @@ extern "C" { * All fields are little-endian. * NOTE: Strings are NOT null-terminated! */ +#define NDS_CARD_CONTROL_13_NTR 0x00586000 +#define NDS_CARD_CONTROL_BF_NTR 0x001808F8 +// FIXME: Verify that these are used by actual TWL NTRBOOT carts, not just homebrew. +#define NDS_CARD_CONTROL_13_NTRBOOT_TWL 0x00416657 +#define NDS_CARD_CONTROL_BF_NTRBOOT_TWL 0x081808F8 typedef struct _NDS_RomHeader { - char title[12]; + char title[12]; // [0x000] // Some compilers pad this structure to a multiple of 4 bytes #pragma pack(1) union RP_PACKED { - char id6[6]; // Game code. (ID6) + char id6[6]; // [0x00C] Game code (ID6) struct RP_PACKED { - char id4[4]; // Game code. (ID4) - char company[2]; // Company code. + char id4[4]; // [0x00C] Game code (ID4) + char company[2]; // [0x010] Company code }; }; #pragma pack() - // 0x12 - uint8_t unitcode; // 00h == NDS, 02h == NDS+DSi, 03h == DSi only - uint8_t enc_seed_select; - uint8_t device_capacity; - uint8_t reserved1[7]; - uint8_t reserved2_dsi; - uint8_t nds_region; // NDS region code (See NDS_Region_e) - uint8_t rom_version; - uint8_t autostart; - - // 0x20 - struct { - uint32_t rom_offset; - uint32_t entry_address; - uint32_t ram_address; - uint32_t size; - } arm9; - struct { - uint32_t rom_offset; - uint32_t entry_address; - uint32_t ram_address; - uint32_t size; - } arm7; - - // 0x40 - uint32_t fnt_offset; // File Name Table offset - uint32_t fnt_size; // File Name Table size - uint32_t fat_offset; - uint32_t fat_size; + uint8_t unitcode; // [0x012] // 00h == NDS, 02h == NDS+DSi, 03h == DSi only + uint8_t enc_seed_select; // [0x013] + uint8_t device_capacity; // [0x014] + uint8_t reserved1[7]; // [0x015] + uint8_t reserved2_dsi; // [0x01C] + uint8_t nds_region; // [0x01D] // NDS region code (See NDS_Region_e) + uint8_t rom_version; // [0x01E] + uint8_t autostart; // [0x01F] + + NDS_CodeInfo_t arm9; // [0x020] + NDS_CodeInfo_t arm7; // [0x030] + + uint32_t fnt_offset; // [0x040] File Name Table offset + uint32_t fnt_size; // [0x044] File Name Table size + uint32_t fat_offset; // [0x048] + uint32_t fat_size; // [0x04C] // 0x50 - uint32_t arm9_overlay_offset; - uint32_t arm9_overlay_size; - uint32_t arm7_overlay_offset; - uint32_t arm7_overlay_size; - - // 0x60 - uint32_t cardControl13; // Port 0x40001A4 setting for normal commands (usually 0x00586000) - uint32_t cardControlBF; // Port 0x40001A4 setting for KEY1 commands (usually 0x001808F8) - - // 0x68 - uint32_t icon_offset; - uint16_t secure_area_checksum; // CRC32 of 0x0020...0x7FFF - uint16_t secure_area_delay; // Delay, in 131 kHz units (0x051E=10ms, 0x0D7E=26ms) - - uint32_t arm9_auto_load_list_ram_address; - uint32_t arm7_auto_load_list_ram_address; - - uint64_t secure_area_disable; - - // 0x80 - uint32_t total_used_rom_size; // Excluding DSi area - uint32_t rom_header_size; // Usually 0x4000 - uint8_t reserved3[0x38]; - uint8_t nintendo_logo[0x9C]; // GBA-style Nintendo logo - uint16_t nintendo_logo_checksum; // CRC16 of nintendo_logo[] (always 0xCF56) - uint16_t header_checksum; // CRC16 of 0x0000...0x015D - - // 0x160 + uint32_t arm9_overlay_offset; // [0x050] + uint32_t arm9_overlay_size; // [0x054] + uint32_t arm7_overlay_offset; // [0x058] + uint32_t arm7_overlay_size; // [0x05C] + + uint32_t cardControl13; // [0x060] Port 0x40001A4 setting for normal commands (usually 0x00586000) + uint32_t cardControlBF; // [0x064] Port 0x40001A4 setting for KEY1 commands (usually 0x001808F8) + + uint32_t icon_offset; // [0x068] Offset to NDS_IconTitleData + uint16_t secure_area_checksum; // [0x06C] CRC32 of 0x0020...0x7FFF + uint16_t secure_area_delay; // [0x06E] Delay, in 131 kHz units (0x051E=10ms, 0x0D7E=26ms) + + uint32_t arm9_auto_load_list_ram_address; // [0x070] + uint32_t arm7_auto_load_list_ram_address; // [0x074] + + uint64_t secure_area_disable; // [0x078] + + uint32_t total_used_rom_size; // [0x080] Total used ROM size, excluding DSi area + uint32_t rom_header_size; // [0x084] Usually 0x4000 + uint8_t reserved3[0x38]; // [0x088] + uint8_t nintendo_logo[0x9C]; // [0x0C0] GBA-style Nintendo logo + uint16_t nintendo_logo_checksum; // [0x15C] CRC16 of nintendo_logo[] (always 0xCF56) + uint16_t header_checksum; // [0x15E] CRC16 of 0x0000...0x015D + struct { - uint32_t rom_offset; - uint32_t size; - uint32_t ram_address; + uint32_t rom_offset; // [0x160] + uint32_t size; // [0x164] + uint32_t ram_address; // [0x168] } debug; - // 0x16C - uint8_t reserved4[4]; - uint8_t reserved5[0x10]; + uint8_t reserved4[4]; // [0x16C] + uint8_t reserved5[0x10]; // [0x170] - /** DSi-specific **/ + /** DSi-specific [0x180] **/ struct { // 0x180 [memory settings] - uint32_t global_mbk[5]; // Global MBK1..MBK5 settings. - uint32_t arm9_mbk[3]; // Local ARM9 MBK6..MBK8 settings. - uint32_t arm7_mbk[3]; // Local ARM7 MBK6..MBK8 settings. - uint8_t arm9_mbk9_master[3]; // Global MBK9 setting, WRAM slot master. - uint8_t unknown; // Usually 0x03, but System Menu has 0xFC, System Settings has 0x00. + uint32_t global_mbk[5]; // [0x180] Global MBK1..MBK5 settings + uint32_t arm9_mbk[3]; // [0x194] Local ARM9 MBK6..MBK8 settings + uint32_t arm7_mbk[3]; // [0x1A0] Local ARM7 MBK6..MBK8 settings + uint8_t arm9_mbk9_master[3]; // [0x1AC] Global MBK9 setting, WRAM slot master + uint8_t unknown; // [0x1AF] Usually 0x03, but System Menu has 0xFC, System Settings has 0x00 // 0x1B0 - uint32_t region_code; // DSi region code (See DSi_Region_e) - uint32_t access_control; // ??? - uint32_t arm7_scfg_mask; - uint8_t reserved1[3]; // Unknown flags. (always 0) - uint8_t flags; // See DSi_Flags. + uint32_t region_code; // [0x1B0] DSi region code (See DSi_Region_e) + uint32_t access_control; // [0x1B4] ??? + uint32_t arm7_scfg_mask; // [0x1B8] + uint8_t reserved1[3]; // [0x1BC] Unknown flags (always 0) + uint8_t flags; // [0x1BF] See DSi_Flags // 0x1C0 - struct { - uint32_t rom_offset; // Usually 0xXX03000h, where XX is the 1MB boundary after the NDS area. - uint32_t reserved; // Zero-filled. - uint32_t load_address; - uint32_t size; - } arm9i; - struct { - uint32_t rom_offset; - uint32_t param_addr; // Pointer to base address where structures are passed to the title. - uint32_t load_address; - uint32_t size; - } arm7i; + DSi_CodeInfo_t arm9i; // [0x1C0] + DSi_CodeInfo_t arm7i; // [0x1D0] // 0x1E0 [digest offsets] struct { - uint32_t ntr_region_offset; // Usually the same as ARM9 rom_offset, 0x0004000 - uint32_t ntr_region_length; - uint32_t twl_region_offset; // Usually the same as ARM9i rom_offset, 0xXX03000 - uint32_t twl_region_length; - uint32_t sector_hashtable_offset; // SHA1 HMACs on all sectors - uint32_t sector_hashtable_length; // in the above NTR+TWL regions. - uint32_t block_hashtable_offset; // SHA1 HMACs on each N entries - uint32_t block_hashtable_length; // in the above Sector Hashtable. - uint32_t sector_size; // e.g. 0x400 bytes per sector - uint32_t block_sector_count; // e.g. 0x20 sectors per block + uint32_t ntr_region_offset; // [0x1E0] Usually the same as ARM9 rom_offset, 0x0004000 + uint32_t ntr_region_length; // [0x1E4] + uint32_t twl_region_offset; // [0x1E8] Usually the same as ARM9i rom_offset, 0xXX03000 + uint32_t twl_region_length; // [0x1EC] + uint32_t sector_hashtable_offset; // [0x1F0] SHA1 HMACs on all sectors + uint32_t sector_hashtable_length; // [0x1F4] in the above NTR+TWL regions. + uint32_t block_hashtable_offset; // [0x1F8] SHA1 HMACs on each N entries + uint32_t block_hashtable_length; // [0x1FC] in the above Sector Hashtable. + uint32_t sector_size; // [0x200] e.g. 0x400 bytes per sector + uint32_t block_sector_count; // [0x204] e.g. 0x20 sectors per block } digest; // 0x208 - uint32_t icon_title_size; // Size of icon/title. (usually 0x23C0) - uint32_t reserved2; // 00 00 01 00 - uint32_t total_used_rom_size; // *INCLUDING* DSi area - uint32_t reserved3[3]; // 00 00 00 00; 84 D0 04 00; 2C 05 00 00 + uint32_t icon_title_size; // [0x208] Size of icon/title (usually 0x23C0) + uint32_t reserved2; // [0x20C] 00 00 01 00 + uint32_t total_used_rom_size; // [0x210] *INCLUDING* DSi area + uint32_t reserved3[3]; // [0x214] 00 00 00 00; 84 D0 04 00; 2C 05 00 00 // 0x220 - uint32_t modcrypt1_offset; // Usually the same as ARM9i rom_offset, 0xXX03000 - uint32_t modcrypt1_size; // Usually min(0x4000, ARM9i ((size + 0x0F) & ~0x0F)) - uint32_t modcrypt2_offset; // 0 for none - uint32_t modcrypt2_size; // 0 for none + uint32_t modcrypt1_offset; // [0x220] Usually the same as ARM9i rom_offset, 0xXX03000 + uint32_t modcrypt1_size; // [0x224] Usually min(0x4000, ARM9i ((size + 0x0F) & ~0x0F)) + uint32_t modcrypt2_offset; // [0x228] 0 for none + uint32_t modcrypt2_size; // [0x22C] 0 for none // 0x230 Nintendo_TitleID_LE_t title_id; // [0x230] Title ID // 0x238 - uint32_t sd_public_sav_size; - uint32_t sd_private_sav_size; + uint32_t sd_public_sav_size; // [0x238] + uint32_t sd_private_sav_size; // [0x23C] // 0x240 - uint8_t reserved6[176]; // Zero-filled + uint8_t reserved6[176]; // [0x240] Zero-filled // 0x2F0 - uint8_t age_ratings[0x10]; // Age ratings. + uint8_t age_ratings[0x10]; // [0x2F0] Age ratings // 0x300 - uint8_t sha1_hmac_arm9[20]; // SHA1 HMAC of ARM9 (with encrypted secure area) - uint8_t sha1_hmac_arm7[20]; // SHA1 HMAC of ARM7 - uint8_t sha1_hmac_digest_master[20]; - uint8_t sha1_hmac_icon_title[20]; - uint8_t sha1_hmac_arm9i[20]; // decrypted - uint8_t sha1_hmac_arm7i[20]; // decrypted - uint8_t reserved7[40]; - uint8_t sha1_hmac_arm9_nosecure[20]; // SHA1 HMAC of ARM9 without 16 KB secure area - uint8_t reserved8[2636]; - uint8_t debug_args[0x180]; // Zero and unchecked on retail; used for arguments on debug. - uint8_t rsa_sha1[0x80]; // RSA SHA1 signature on 0x000...0xDFF. + uint8_t sha1_hmac_arm9[20]; // [0x300] SHA1 HMAC of ARM9 (with encrypted secure area) + uint8_t sha1_hmac_arm7[20]; // [0x314] SHA1 HMAC of ARM7 + uint8_t sha1_hmac_digest_master[20]; // [0x328] + uint8_t sha1_hmac_icon_title[20]; // [0x33C] + uint8_t sha1_hmac_arm9i[20]; // [0x350] decrypted + uint8_t sha1_hmac_arm7i[20]; // [0x364] decrypted + uint8_t reserved7[40]; // [0x378] + uint8_t sha1_hmac_arm9_nosecure[20]; // [0x3A0] SHA1 HMAC of ARM9 without 16 KB secure area + uint8_t reserved8[2636]; // [0x3B4] + uint8_t debug_args[0x180]; // [0xE00] Zero and unchecked on retail; used for arguments on debug. + uint8_t rsa_sha1[0x80]; // [0xF80] RSA SHA1 signature on 0x000...0xDFF. } dsi; } NDS_RomHeader; ASSERT_STRUCT(NDS_RomHeader, 4096); diff --git a/src/libromdata/RomDataFactory.cpp b/src/libromdata/RomDataFactory.cpp index 5a558d12d..1bb974cde 100644 --- a/src/libromdata/RomDataFactory.cpp +++ b/src/libromdata/RomDataFactory.cpp @@ -259,9 +259,9 @@ static std::once_flag once_mimeTypes; * - magic2: Second 32-bit magic number, if available. */ #ifdef ENABLE_XML -static constexpr size_t romDataFns_magic_count = 38; +static constexpr size_t romDataFns_magic_count = 40; #else /* !ENABLE_XML */ -static constexpr size_t romDataFns_magic_count = 37; +static constexpr size_t romDataFns_magic_count = 39; #endif /* ENABLE_XML */ static const array romDataFns_magic = {{ // Consoles @@ -291,6 +291,10 @@ static const array romDataFns_magic = {{ GetRomDataFns_magic1(Nintendo3DS_SMDH, ATTR_HAS_THUMBNAIL | ATTR_HAS_METADATA, 0, 'SMDH'), GetRomDataFns_magic1(Nintendo3DS, ATTR_HAS_THUMBNAIL | ATTR_HAS_DPOVERLAY | ATTR_HAS_METADATA, 0, 'Z3DS'), // Z3DS format only! GetRomDataFns_magic2(NintendoDS, ATTR_HAS_THUMBNAIL | ATTR_HAS_DPOVERLAY | ATTR_HAS_METADATA, 0xC0, 0x24FFAE51, 0xC8604FE2), + // NTRBOOT ROMs don't have the logo, but *do* have the port 0x40001A4 command settings. + // NOTE: 3DS NTRBOOT matches NTR settings, but DSi NTRBOOT has its own? + GetRomDataFns_magic2(NintendoDS, ATTR_NONE, 0x60, 0x00605800, 0x00F80818), // NTRBOOT (3DS) + GetRomDataFns_magic2(NintendoDS, ATTR_NONE, 0x60, 0x57664100, 0xF8081808), // NTRBOOT (DSi) // Common GetRomDataFns_magic1(ParamSFO, ATTR_HAS_METADATA, 0, 0x00505346), // '\0PSF' diff --git a/src/libromdata/crypto/KeyStoreUI.cpp b/src/libromdata/crypto/KeyStoreUI.cpp index 46d5ebbbb..7fd98e315 100644 --- a/src/libromdata/crypto/KeyStoreUI.cpp +++ b/src/libromdata/crypto/KeyStoreUI.cpp @@ -1294,12 +1294,14 @@ KeyStoreUI::ImportReturn KeyStoreUIPrivate::importN3DSboot9bin(IRpFile *file) } // Key addresses and indexes. - static const array keyBinAddress = {{ - {0x5720, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Retail_SpiBoot)}, + static const array keyBinAddress = {{ + {0x5720, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Retail_NtrBoot)}, + {0x5730, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Retail_SpiBoot)}, {0x59D0, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Retail_Slot0x2CKeyX)}, {0x5A20, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Retail_Slot0x3DKeyX)}, - {0x5740, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Debug_SpiBoot)}, + {0x5740, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Debug_NtrBoot)}, + {0x5750, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Debug_SpiBoot)}, {0x5DD0, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Debug_Slot0x2CKeyX)}, {0x5E20, static_cast(N3DSVerifyKeys::EncryptionKeys::Key_Debug_Slot0x3DKeyX)}, }}; diff --git a/src/libromdata/crypto/N3DSVerifyKeys.cpp b/src/libromdata/crypto/N3DSVerifyKeys.cpp index 219367726..b55b3acf6 100644 --- a/src/libromdata/crypto/N3DSVerifyKeys.cpp +++ b/src/libromdata/crypto/N3DSVerifyKeys.cpp @@ -32,6 +32,7 @@ namespace Private { // Verification key names static const array(EncryptionKeys::Key_Max)> EncryptionKeyNames = {{ // Retail + "ctr-ntr-boot", "ctr-spi-boot", "ctr-Slot0x18KeyX", "ctr-Slot0x1BKeyX", @@ -52,6 +53,7 @@ static const array(EncryptionKeys::Key_Max)> En "ctr-Slot0x3DKeyNormal-5", // Debug + "ctr-dev-ntr-boot", "ctr-dev-spi-boot", "ctr-dev-FixedCryptoKey", "ctr-dev-Slot0x18KeyX", @@ -77,10 +79,14 @@ static const array(EncryptionKeys::Key_Max)> En static const uint8_t EncryptionKeyVerifyData[static_cast(EncryptionKeys::Key_Max)][16] = { /** Retail **/ - // Key_Retail_SpiBoot + // Key_Retail_NtrBoot {0xCB,0x41,0xA2,0x74,0xD8,0x51,0x3A,0x38, 0x9A,0x4A,0xBB,0x2E,0x87,0x2C,0xB8,0xB9}, + // Key_Retail_SpiBoot + {0xB1,0x0A,0x34,0xEA,0x5B,0x25,0x22,0x15, + 0x72,0x21,0xd7,0x4B,0x86,0x08,0xAA,0x1D}, + // Key_Retail_Slot0x18KeyX {0xE6,0x2E,0x52,0x4A,0x3A,0x17,0x28,0xC8, 0xC0,0xFA,0x0C,0x3D,0x74,0x5D,0x74,0x41}, @@ -143,10 +149,14 @@ static const uint8_t EncryptionKeyVerifyData[static_cast(EncryptionKeys: /** Debug **/ - // Key_Debug_SpiBoot + // Key_Debug_NtrBoot {0xDD,0xB7,0xA0,0x17,0x55,0xB2,0x84,0xB8, 0x7A,0x65,0xD5,0x64,0x10,0x5E,0x07,0x99}, + // Key_Debug_SpiBoot + {0x45,0x1C,0x86,0x6C,0x5B,0xA9,0xBD,0x09, + 0x9E,0x1C,0xE3,0x15,0xC4,0xC1,0xBC,0xA5}, + // Key_Debug_FixedCryptoKey {0x1E,0x95,0x82,0xCD,0x65,0x2A,0xE3,0x3F, 0x90,0xEB,0x91,0x3F,0x77,0xE0,0x0A,0x35}, diff --git a/src/libromdata/crypto/N3DSVerifyKeys.hpp b/src/libromdata/crypto/N3DSVerifyKeys.hpp index 3c34a8a03..19ca05c27 100644 --- a/src/libromdata/crypto/N3DSVerifyKeys.hpp +++ b/src/libromdata/crypto/N3DSVerifyKeys.hpp @@ -92,7 +92,8 @@ enum class EncryptionKeys { Key_Unknown = -1, // Retail - Key_Retail_SpiBoot = 0, + Key_Retail_NtrBoot = 0, // NTRBOOT from an NTR cartridge + Key_Retail_SpiBoot, // SPIBOOT from WLAN firmware Key_Retail_Slot0x18KeyX, Key_Retail_Slot0x1BKeyX, Key_Retail_Slot0x25KeyX, @@ -112,7 +113,8 @@ enum class EncryptionKeys { Key_Retail_Slot0x3DKeyNormal_5, // Debug - Key_Debug_SpiBoot, + Key_Debug_NtrBoot, // NTRBOOT from an NTR cartridge + Key_Debug_SpiBoot, // SPIBOOT from WLAN firmware Key_Debug_FixedCryptoKey, Key_Debug_Slot0x18KeyX, Key_Debug_Slot0x1BKeyX, diff --git a/src/libromdata/data/Nintendo3DSFirmData.cpp b/src/libromdata/data/Nintendo3DSFirmData.cpp index 7e9456bca..d28a36990 100644 --- a/src/libromdata/data/Nintendo3DSFirmData.cpp +++ b/src/libromdata/data/Nintendo3DSFirmData.cpp @@ -18,56 +18,58 @@ namespace LibRomData { namespace Nintendo3DSFirmData { /** * Firmware binary version information. * NOTE: Sorted by CRC32 for bsearch(). + * TODO: Add TWL_FIRM and AGB_FIRM. */ -static const array firmBins = {{ - {0x0FD41774, {2,27, 0}, { 1, 0}, false}, - {0x104F1A22, {2,50, 9}, {10, 2}, true}, - {0x11A9A4BA, {2,36, 0}, { 5, 1}, false}, - {0x13A10539, {2,39, 0}, { 7, 0}, false}, - {0x14AF04A9, {2,58, 0}, {11,16}, true}, - {0x2180C8A7, {2,57, 0}, {11,14}, false}, - {0x2B0726F1, {2,49, 0}, { 9, 5}, false}, - {0x32E9236F, {2,35, 6}, { 5, 0}, false}, - {0x415BEAFE, {2,52, 0}, {11, 2}, true}, - {0x41C8A171, {2,52, 0}, {11, 2}, false}, - {0x4380DB8D, {2,46, 0}, { 9, 0}, false}, - {0x4A07016A, {2,54, 0}, {11, 4}, true}, - {0x4EE22A07, {2,55, 0}, {11, 8}, false}, - {0x528E293F, {2,37, 0}, { 6, 0}, false}, - {0x584C9AF5, {2,48, 3}, { 9, 3}, false}, - {0x6488499E, {2,33, 4}, { 4, 0}, false}, - {0x6E4ED781, {2,50,11}, {10, 4}, false}, - {0x70A08ACD, {2,28, 0}, { 1, 1}, false}, - {0x7421ACB4, {2,53, 0}, {11, 3}, false}, - {0x80D26BB6, {2,30,18}, { 2, 1}, false}, - {0x8662D9E4, {2,50, 7}, {10, 0}, true}, - {0x8904168D, {2,46, 0}, { 9, 0}, true}, - {0x90B92754, {2,38, 0}, { 6, 1}, false}, - {0x925C092E, {2,45, 5}, { 8, 1}, true}, - {0x93D29ADA, {2,50, 7}, {10, 0}, false}, - {0x9622D367, {2,44, 6}, { 8, 0}, false}, - {0x985699BF, {2,51, 2}, {11, 1}, false}, - {0x98640F5C, {2,34, 0}, { 4, 1}, false}, - {0xA89A6392, {2,51, 0}, {11, 0}, true}, - {0xA8E660DF, {2,49, 0}, { 9, 5}, true}, - {0xAB6D5279, {2,51, 0}, {11, 0}, false}, - {0xACCC5EC4, {2,50,11}, {10, 4}, true}, - {0xB1E2179B, {2,58, 0}, {11,16}, false}, - {0xB7B6499E, {2,50, 1}, { 9, 6}, true}, - {0xBDD9D878, {2,50, 9}, {10, 2}, false}, - {0xC110E2F9, {2,56, 0}, {11,12}, true}, - {0xC5380DCC, {2,53, 0}, {11, 3}, true}, - {0xC645B9A5, {2,50, 1}, { 9, 6}, false}, - {0xC9829406, {2,29, 7}, { 2, 0}, false}, - {0xDA0F7831, {2,54, 0}, {11, 4}, false}, - {0xE0D74F64, {2,32,15}, { 3, 0}, false}, - {0xE25F25F5, {2,31,40}, { 2, 2}, false}, - {0xEA07F21E, {2,40, 0}, { 7, 2}, false}, - {0xEE23547A, {2,55, 0}, {11, 8}, true}, - {0xF0ADC912, {2,57, 0}, {11,14}, true}, - {0xF5D833A2, {2,51, 2}, {11, 1}, true}, - {0xFA7997F7, {2,56, 0}, {11,12}, false}, - {0xFFA6777A, {2,48, 3}, { 9, 3}, true}, +static const array firmBins = {{ + {0x0FD41774, {2,27, 0}, { 1, 0}, 0}, + {0x104F1A22, {2,50, 9}, {10, 2}, FLAG_New3DS}, + {0x11A9A4BA, {2,36, 0}, { 5, 1}, 0}, + {0x13A10539, {2,39, 0}, { 7, 0}, 0}, + {0x14AF04A9, {2,58, 0}, {11,16}, FLAG_New3DS}, + {0x2180C8A7, {2,57, 0}, {11,14}, 0}, + {0x2B0726F1, {2,49, 0}, { 9, 5}, 0}, + {0x32E9236F, {2,35, 6}, { 5, 0}, 0}, + {0x415BEAFE, {2,52, 0}, {11, 2}, FLAG_New3DS}, + {0x41C8A171, {2,52, 0}, {11, 2}, 0}, + {0x4380DB8D, {2,46, 0}, { 9, 0}, 0}, + {0x4A07016A, {2,54, 0}, {11, 4}, FLAG_New3DS}, + {0x4EE22A07, {2,55, 0}, {11, 8}, 0}, + {0x528E293F, {2,37, 0}, { 6, 0}, 0}, + {0x584C9AF5, {2,48, 3}, { 9, 3}, 0}, + {0x6488499E, {2,33, 4}, { 4, 0}, 0}, + {0x6E4ED781, {2,50,11}, {10, 4}, 0}, + {0x70A08ACD, {2,28, 0}, { 1, 1}, 0}, + {0x7421ACB4, {2,53, 0}, {11, 3}, 0}, + {0x7D1F9D40, {2,32, 0}, { 3, 0}, FLAG_SafeMode}, // FIXME: Verify kernel and FW versions! + {0x80D26BB6, {2,30,18}, { 2, 1}, 0}, + {0x8662D9E4, {2,50, 7}, {10, 0}, FLAG_New3DS}, + {0x8904168D, {2,46, 0}, { 9, 0}, FLAG_New3DS}, + {0x90B92754, {2,38, 0}, { 6, 1}, 0}, + {0x925C092E, {2,45, 5}, { 8, 1}, FLAG_New3DS}, + {0x93D29ADA, {2,50, 7}, {10, 0}, 0}, + {0x9622D367, {2,44, 6}, { 8, 0}, 0}, + {0x985699BF, {2,51, 2}, {11, 1}, 0}, + {0x98640F5C, {2,34, 0}, { 4, 1}, 0}, + {0xA89A6392, {2,51, 0}, {11, 0}, FLAG_New3DS}, + {0xA8E660DF, {2,49, 0}, { 9, 5}, FLAG_New3DS}, + {0xAB6D5279, {2,51, 0}, {11, 0}, 0}, + {0xACCC5EC4, {2,50,11}, {10, 4}, FLAG_New3DS}, + {0xB1E2179B, {2,58, 0}, {11,16}, 0}, + {0xB7B6499E, {2,50, 1}, { 9, 6}, FLAG_New3DS}, + {0xBDD9D878, {2,50, 9}, {10, 2}, 0}, + {0xC110E2F9, {2,56, 0}, {11,12}, FLAG_New3DS}, + {0xC5380DCC, {2,53, 0}, {11, 3}, FLAG_New3DS}, + {0xC645B9A5, {2,50, 1}, { 9, 6}, 0}, + {0xC9829406, {2,29, 7}, { 2, 0}, 0}, + {0xDA0F7831, {2,54, 0}, {11, 4}, 0}, + {0xE0D74F64, {2,32,15}, { 3, 0}, 0}, + {0xE25F25F5, {2,31,40}, { 2, 2}, 0}, + {0xEA07F21E, {2,40, 0}, { 7, 2}, 0}, + {0xEE23547A, {2,55, 0}, {11, 8}, FLAG_New3DS}, + {0xF0ADC912, {2,57, 0}, {11,14}, FLAG_New3DS}, + {0xF5D833A2, {2,51, 2}, {11, 1}, FLAG_New3DS}, + {0xFA7997F7, {2,56, 0}, {11,12}, 0}, + {0xFFA6777A, {2,48, 3}, { 9, 3}, FLAG_New3DS}, }}; /** Public functions **/ diff --git a/src/libromdata/data/Nintendo3DSFirmData.hpp b/src/libromdata/data/Nintendo3DSFirmData.hpp index f9ce3b09b..c892e152b 100644 --- a/src/libromdata/data/Nintendo3DSFirmData.hpp +++ b/src/libromdata/data/Nintendo3DSFirmData.hpp @@ -12,18 +12,27 @@ namespace LibRomData { namespace Nintendo3DSFirmData { +// Flags +enum Flags : uint8_t { + FLAG_New3DS = (1U << 0), // for New 3DS + FLAG_Devel = (1U << 1), // devkit version + FLAG_SafeMode = (1U << 2), // safe mode only + FLAG_TWL_FIRM = (1U << 3), // TWL_FIRM + FLAG_AGB_FIRM = (1U << 4), // AGB_FIRM +}; + struct FirmBin_t { - uint32_t crc; // FIRM CRC32. - struct { // Kernel version. + uint32_t crc; // FIRM CRC32 + struct { // Kernel version uint8_t major; uint8_t minor; uint8_t revision; } kernel; - struct { // System version. + struct { // System version uint8_t major; uint8_t minor; } sys; - bool isNew3DS; // Is this New3DS? + uint8_t flags; // See Flags }; /** diff --git a/xdg/mime.no-thumbnail.types b/xdg/mime.no-thumbnail.types index 9fecd55fa..cde688d43 100644 --- a/xdg/mime.no-thumbnail.types +++ b/xdg/mime.no-thumbnail.types @@ -46,6 +46,8 @@ application/xml # AndroidManifestXML (too generic?) application/x-atari-lynx-rom # Lynx application/x-nintendo-3ds-emmc # Nintendo3DS application/x-nintendo-3ds-firm # Nintendo3DSFirm +application/x-nintendo-3ds-ntrboot # NintendoDS +application/x-nintendo-dsi-ntrboot # NintendoDS application/x-pokemon-mini-rom # PokemonMini application/x-virtual-boy-rom # VirtualBoy diff --git a/xdg/rom-properties.xml b/xdg/rom-properties.xml index a70189e38..4eeafb8bb 100644 --- a/xdg/rom-properties.xml +++ b/xdg/rom-properties.xml @@ -909,6 +909,39 @@ + + + Nintendo DSi NTRBOOT ROM + + + + + + + + + + + + + + + + Nintendo 3DS NTRBOOT ROM + + + + + + + + + + + + + +