mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 13:27:41 -07:00
270 lines
8.4 KiB
C++
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
|