/* Copyright 2016-2023 melonDS team This file is part of melonDS. melonDS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. melonDS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with melonDS. If not, see http://www.gnu.org/licenses/. */ #ifndef DSI_NAND_H #define DSI_NAND_H #include "types.h" #include "fatfs/ff.h" #include "NDS_Header.h" #include "DSi_TMD.h" #include "SPI_Firmware.h" #include #include #include #include struct AES_ctx; namespace melonDS::DSi_NAND { enum { TitleData_PublicSav, TitleData_PrivateSav, TitleData_BannerSav, }; union DSiFirmwareSystemSettings; union DSiSerialData; using DSiHardwareInfoN = std::array; using DSiKey = std::array; class NANDImage { public: explicit NANDImage(Platform::FileHandle* nandfile, const DSiKey& es_keyY) noexcept; explicit NANDImage(Platform::FileHandle* nandfile, const u8* es_keyY) noexcept; ~NANDImage(); NANDImage(const NANDImage&) = delete; NANDImage& operator=(const NANDImage&) = delete; NANDImage(NANDImage&& other) noexcept; NANDImage& operator=(NANDImage&& other) noexcept; Platform::FileHandle* GetFile() { return CurFile; } [[nodiscard]] const DSiKey& GetEMMCID() const noexcept { return eMMC_CID; } [[nodiscard]] u64 GetConsoleID() const noexcept { return ConsoleID; } [[nodiscard]] u64 GetLength() const noexcept { return Length; } explicit operator bool() const { return CurFile != nullptr; } private: friend class NANDMount; void SetupFATCrypto(AES_ctx* ctx, u32 ctr); u32 ReadFATBlock(u64 addr, u32 len, u8* buf); u32 WriteFATBlock(u64 addr, u32 len, const u8* buf); bool ESEncrypt(u8* data, u32 len) const; bool ESDecrypt(u8* data, u32 len); Platform::FileHandle* CurFile = nullptr; DSiKey eMMC_CID; u64 ConsoleID; DSiKey FATIV; DSiKey FATKey; DSiKey ESKey; u64 Length; }; class NANDMount { public: explicit NANDMount(NANDImage& nand) noexcept; ~NANDMount() noexcept; NANDMount(const NANDMount&) = delete; NANDMount& operator=(const NANDMount&) = delete; // Move constructor deleted so that the closure passed to FATFS can't be invalidated NANDMount(NANDMount&&) = delete; NANDMount& operator=(NANDMount&&) = delete; bool ReadSerialData(DSiSerialData& dataS); bool ReadHardwareInfoN(DSiHardwareInfoN& dataN); void ReadHardwareInfo(DSiSerialData& dataS, DSiHardwareInfoN& dataN); bool ReadUserData(DSiFirmwareSystemSettings& data); /// Saves the given system settings to the DSi NAND, /// to both TWLCFG0.dat and TWLCFG1.dat. bool ApplyUserData(const DSiFirmwareSystemSettings& data); void ListTitles(u32 category, std::vector& titlelist); bool TitleExists(u32 category, u32 titleid); void GetTitleInfo(u32 category, u32 titleid, u32& version, NDSHeader* header, NDSBanner* banner); bool ImportTitle(const char* appfile, const DSi_TMD::TitleMetadata& tmd, bool readonly); bool ImportTitle(const u8* app, size_t appLength, const DSi_TMD::TitleMetadata& tmd, bool readonly); void DeleteTitle(u32 category, u32 titleid); u32 GetTitleDataMask(u32 category, u32 titleid); bool ImportTitleData(u32 category, u32 titleid, int type, const char* file); bool ExportTitleData(u32 category, u32 titleid, int type, const char* file); bool ImportFile(const char* path, const u8* data, size_t len); bool ImportFile(const char* path, const char* in); bool ExportFile(const char* path, const char* out); void RemoveFile(const char* path); void RemoveDir(const char* path); explicit operator bool() const { return Image != nullptr && CurFS != nullptr; } private: u32 GetTitleVersion(u32 category, u32 titleid); bool CreateTicket(const char* path, u32 titleid0, u32 titleid1, u8 version); bool CreateSaveFile(const char* path, u32 len); bool InitTitleFileStructure(const NDSHeader& header, const DSi_TMD::TitleMetadata& tmd, bool readonly); UINT FF_ReadNAND(BYTE* buf, LBA_t sector, UINT num); UINT FF_WriteNAND(const BYTE* buf, LBA_t sector, UINT num); NANDImage* Image; // We keep a pointer to CurFS because fatfs maintains a global pointer to it; // therefore if we embed the FATFS directly in the object, // we can't give it move semantics. std::unique_ptr CurFS; }; typedef std::array SHA1Hash; typedef std::array TitleID; /// Firmware settings for the DSi, saved to the NAND as TWLCFG0.dat or TWLCFG1.dat. /// The DSi mirrors this information to its own firmware for compatibility with NDS games. /// @note The file is normally 16KiB, but only the first 432 bytes are used; /// the rest is FF-padded. /// This struct excludes the padding. /// @see https://problemkaputt.de/gbatek.htm#dsisdmmcfirmwaresystemsettingsdatafiles union DSiFirmwareSystemSettings { struct { SHA1Hash Hash; u8 Zero00[108]; u8 Version; u8 UpdateCounter; u8 Zero01[2]; u32 BelowRAMAreaSize; u32 ConfigFlags; u8 Zero02; u8 CountryCode; Firmware::Language Language; u8 RTCYear; u32 RTCOffset; u8 Zero3[4]; u8 EULAVersion; u8 Zero04[9]; u8 AlarmHour; u8 AlarmMinute; u8 Zero05[2]; bool AlarmEnable; u8 Zero06[2]; u8 SystemMenuUsedTitleSlots; u8 SystemMenuFreeTitleSlots; u8 Unknown0; u8 Unknown1; u8 Zero07[3]; TitleID SystemMenuMostRecentTitleID; std::array TouchCalibrationADC1; std::array TouchCalibrationPixel1; std::array TouchCalibrationADC2; std::array TouchCalibrationPixel2; u8 Unknown2[4]; u8 Zero08[4]; u8 FavoriteColor; u8 Zero09; u8 BirthdayMonth; u8 BirthdayDay; char16_t Nickname[11]; char16_t Message[27]; u8 ParentalControlsFlags; u8 Zero10[6]; u8 ParentalControlsRegion; u8 ParentalControlsYearsOfAgeRating; u8 ParentalControlsSecretQuestion; u8 Unknown3; u8 Zero11[2]; char ParentalControlsPIN[5]; char16_t ParentalControlsSecretAnswer[65]; }; u8 Bytes[432]; void UpdateHash(); }; static_assert(sizeof(DSiFirmwareSystemSettings) == 432, "DSiFirmwareSystemSettings must be exactly 432 bytes"); enum class ConsoleRegion : u8 { Japan, USA, Europe, Australia, China, Korea, }; /// Languages that the given NAND image supports. /// @see https://problemkaputt.de/gbatek.htm#dsiregions enum DSiSupportedLanguageMask : u32 { NoLanguagesSet = 0, JapaneseSupported = 1 << 0, EnglishSupported = 1 << 1, FrenchSupported = 1 << 2, GermanSupported = 1 << 3, ItalianSupported = 1 << 4, SpanishSupported = 1 << 5, ChineseSupported = 1 << 6, KoreanSupported = 1 << 7, JapanLanguages = JapaneseSupported, AmericaLanguages = EnglishSupported | FrenchSupported | SpanishSupported, EuropeLanguages = EnglishSupported | FrenchSupported | GermanSupported | ItalianSupported | SpanishSupported, AustraliaLanguages = EnglishSupported, // "Unknown (supposedly Chinese/Mandarin?, and maybe English or so)" ChinaLanguages = ChineseSupported | EnglishSupported, KoreaLanguages = KoreanSupported, }; /// Data file saved to 0:/sys/HWINFO_S.dat. /// @note The file is normally 16KiB, but only the first 164 bytes are used; /// the rest is FF-padded. /// This struct excludes the padding. /// @see https://problemkaputt.de/gbatek.htm#dsisdmmcfirmwaremiscfiles union DSiSerialData { struct { u8 RsaSha1HMAC[0x80]; u32 Version; u32 EntrySize; DSiSupportedLanguageMask SupportedLanguages; u8 Unknown0[4]; ConsoleRegion Region; char Serial[12]; u8 Unknown1[3]; u8 TitleIDLSBs[4]; }; u8 Bytes[164]; }; static_assert(sizeof(DSiSerialData) == 164, "DSiSerialData must be exactly 164 bytes"); } #endif // DSI_NAND_H