aboutsummaryrefslogtreecommitdiff
path: root/src/DSi_NAND.h
blob: 99a9ee7c44c9c954f1beabb375ad4ea8c6cdcf9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/*
    Copyright 2016-2022 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 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);
    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