melonDS/src/DSi_NAND.h
2024-06-15 17:01:19 +02:00

270 lines
8.4 KiB
C++

/*
Copyright 2016-2024 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 <array>
#include <memory>
#include <vector>
#include <string>
struct AES_ctx;
namespace melonDS::DSi_NAND
{
enum
{
TitleData_PublicSav,
TitleData_PrivateSav,
TitleData_BannerSav,
};
union DSiFirmwareSystemSettings;
union DSiSerialData;
using DSiHardwareInfoN = std::array<u8, 0x9C>;
using DSiKey = std::array<u8, 16>;
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) const;
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<u32>& 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<FATFS> CurFS;
};
typedef std::array<u8, 20> SHA1Hash;
typedef std::array<u8, 8> 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<u16, 2> TouchCalibrationADC1;
std::array<u8, 2> TouchCalibrationPixel1;
std::array<u16, 2> TouchCalibrationADC2;
std::array<u8, 2> 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