diff options
| -rw-r--r-- | src/DSi.cpp | 5 | ||||
| -rw-r--r-- | src/DSi_NAND.cpp | 140 | ||||
| -rw-r--r-- | src/DSi_NAND.h | 37 | ||||
| -rw-r--r-- | src/NDSCart.cpp | 3 | ||||
| -rw-r--r-- | src/NDS_Header.h | 17 | ||||
| -rw-r--r-- | src/OpenGLSupport.cpp | 6 | ||||
| -rw-r--r-- | src/Platform.h | 7 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/Platform.cpp | 8 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/ROMManager.cpp | 88 | 
9 files changed, 189 insertions, 122 deletions
diff --git a/src/DSi.cpp b/src/DSi.cpp index 17d79a6..15160b2 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -540,8 +540,9 @@ void SetupDirectBoot()                      ARM9Write32(0x02000400+i, *(u32*)&userdata.Bytes[0x88+i]);                  DSi_NAND::DSiSerialData hwinfoS {}; +                nand.ReadSerialData(hwinfoS);                  DSi_NAND::DSiHardwareInfoN hwinfoN; -                nand.ReadHardwareInfo(hwinfoS, hwinfoN); +                nand.ReadHardwareInfoN(hwinfoN);                  for (u32 i = 0; i < 0x14; i+=4)                      ARM9Write32(0x02000600+i, *(u32*)&hwinfoN[0x88+i]); @@ -944,7 +945,7 @@ bool LoadNAND()          NDS::ARM7->JumpTo(bootparams[6]);      } -    nandmount.PatchUserData(); +    // user data is now expected to be patched by the frontend      return true;  } diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index 03bed9b..79458cb 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -172,7 +172,7 @@ NANDMount::NANDMount(NANDImage& nand) noexcept : Image(&nand)  } -NANDMount::~NANDMount() +NANDMount::~NANDMount() noexcept  {      f_unmount("0:");      ff_disk_close(); @@ -467,30 +467,43 @@ bool NANDImage::ESDecrypt(u8* data, u32 len)      return true;  } - -void NANDMount::ReadHardwareInfo(DSiSerialData& dataS, DSiHardwareInfoN& dataN) +bool NANDMount::ReadSerialData(DSiSerialData& dataS)  {      FF_FIL file; -    FRESULT res; -    u32 nread; +    FRESULT res = f_open(&file, "0:/sys/HWINFO_S.dat", FA_OPEN_EXISTING | FA_READ); -    res = f_open(&file, "0:/sys/HWINFO_S.dat", FA_OPEN_EXISTING | FA_READ);      if (res == FR_OK)      { +        u32 nread;          f_read(&file, &dataS, sizeof(DSiSerialData), &nread);          f_close(&file);      } -    res = f_open(&file, "0:/sys/HWINFO_N.dat", FA_OPEN_EXISTING | FA_READ); +    return res == FR_OK; +} + +bool NANDMount::ReadHardwareInfoN(DSiHardwareInfoN& dataN) +{ +    FF_FIL file; +    FRESULT res = f_open(&file, "0:/sys/HWINFO_N.dat", FA_OPEN_EXISTING | FA_READ); +      if (res == FR_OK)      { +        u32 nread;          f_read(&file, dataN.data(), sizeof(dataN), &nread);          f_close(&file);      } + +    return res == FR_OK;  } +void NANDMount::ReadHardwareInfo(DSiSerialData& dataS, DSiHardwareInfoN& dataN) +{ +    ReadSerialData(dataS); +    ReadHardwareInfoN(dataN); +} -void NANDMount::ReadUserData(DSiFirmwareSystemSettings& data) +bool NANDMount::ReadUserData(DSiFirmwareSystemSettings& data)  {      FF_FIL file;      FRESULT res; @@ -521,7 +534,7 @@ void NANDMount::ReadUserData(DSiFirmwareSystemSettings& data)          v2 = tmp;      } -    if (v1 < 0 && v2 < 0) return; +    if (v1 < 0 && v2 < 0) return false;      if (v2 > v1)      { @@ -537,73 +550,40 @@ void NANDMount::ReadUserData(DSiFirmwareSystemSettings& data)      f_lseek(&file, 0);      f_read(&file, &data, sizeof(DSiFirmwareSystemSettings), &nread);      f_close(&file); + +    return true;  } -void NANDMount::PatchUserData() +static bool SaveUserData(const char* filename, const DSiFirmwareSystemSettings& data)  { -    FRESULT res; - -    for (int i = 0; i < 2; i++) +    FF_FIL file; +    if (FRESULT res = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ | FA_WRITE); res != FR_OK)      { -        char filename[64]; -        snprintf(filename, sizeof(filename), "0:/shared1/TWLCFG%d.dat", i); - -        FF_FIL file; -        res = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ | FA_WRITE); -        if (res != FR_OK) -        { -            Log(LogLevel::Error, "NAND: editing file %s failed: %d\n", filename, res); -            continue; -        } - -        DSiFirmwareSystemSettings contents; -        u32 nres; -        f_lseek(&file, 0); -        f_read(&file, &contents, sizeof(DSiFirmwareSystemSettings), &nres); +        Log(LogLevel::Error, "NAND: editing file %s failed: %d\n", filename, res); +        return false; +    } +    // TODO: If the file couldn't be opened, try creating a new one in its place +    // (after all, we have the data for that) -        // override user settings, if needed -        if (Platform::GetConfigBool(Platform::Firm_OverrideSettings)) -        { -            // setting up username -            std::string orig_username = Platform::GetConfigString(Platform::Firm_Username); -            std::u16string username = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(orig_username); -            size_t usernameLength = std::min(username.length(), (size_t) 10); -            memset(&contents.Nickname, 0, sizeof(contents.Nickname)); -            memcpy(&contents.Nickname, username.data(), usernameLength * sizeof(char16_t)); - -            // setting language -            contents.Language = static_cast<SPI_Firmware::Language>(Platform::GetConfigInt(Platform::Firm_Language)); - -            // setting up color -            contents.FavoriteColor = Platform::GetConfigInt(Platform::Firm_Color); - -            // setting up birthday -            contents.BirthdayMonth = Platform::GetConfigInt(Platform::Firm_BirthdayMonth); -            contents.BirthdayDay = Platform::GetConfigInt(Platform::Firm_BirthdayDay); - -            // setup message -            std::string orig_message = Platform::GetConfigString(Platform::Firm_Message); -            std::u16string message = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(orig_message); -            size_t messageLength = std::min(message.length(), (size_t) 26); -            memset(&contents.Message, 0, sizeof(contents.Message)); -            memcpy(&contents.Message, message.data(), messageLength * sizeof(char16_t)); - -            // TODO: make other items configurable? -        } +    u32 bytes_written = 0; +    FRESULT res = f_write(&file, &data, sizeof(DSiFirmwareSystemSettings), &bytes_written); +    f_close(&file); -        // fix touchscreen coords -        contents.TouchCalibrationADC1 = {0, 0}; -        contents.TouchCalibrationPixel1 = {0, 0}; -        contents.TouchCalibrationADC2 = {255 << 4, 191 << 4}; -        contents.TouchCalibrationPixel2 = {255, 191}; +    if (res != FR_OK || bytes_written != sizeof(DSiFirmwareSystemSettings)) +    { +        Log(LogLevel::Error, "NAND: editing file %s failed: %d\n", filename, res); +        return false; +    } -        contents.UpdateHash(); +    return true; +} -        f_lseek(&file, 0); -        f_write(&file, &contents, sizeof(DSiFirmwareSystemSettings), &nres); +bool NANDMount::ApplyUserData(const DSiFirmwareSystemSettings& data) +{ +    bool ok0 = SaveUserData("0:/shared1/TWLCFG0.dat", data); +    bool ok1 = SaveUserData("0:/shared1/TWLCFG1.dat", data); -        f_close(&file); -    } +    return ok0 && ok1;  } @@ -672,21 +652,18 @@ bool NANDMount::ImportFile(const char* path, const u8* data, size_t len)  bool NANDMount::ImportFile(const char* path, const char* in)  {      FF_FIL file; -    FILE* fin;      FRESULT res; -    fin = fopen(in, "rb"); +    Platform::FileHandle* fin = OpenLocalFile(in, FileMode::Read);      if (!fin)          return false; -    fseek(fin, 0, SEEK_END); -    u32 len = (u32)ftell(fin); -    fseek(fin, 0, SEEK_SET); +    u32 len = FileLength(fin);      res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);      if (res != FR_OK)      { -        fclose(fin); +        CloseFile(fin);          return false;      } @@ -700,11 +677,11 @@ bool NANDMount::ImportFile(const char* path, const char* in)              blocklen = sizeof(buf);          u32 nwrite; -        fread(buf, blocklen, 1, fin); +        FileRead(buf, blocklen, 1, fin);          f_write(&file, buf, blocklen, &nwrite);      } -    fclose(fin); +    CloseFile(fin);      f_close(&file);      Log(LogLevel::Debug, "Imported file from %s to %s\n", in, path); @@ -715,7 +692,6 @@ bool NANDMount::ImportFile(const char* path, const char* in)  bool NANDMount::ExportFile(const char* path, const char* out)  {      FF_FIL file; -    FILE* fout;      FRESULT res;      res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ); @@ -724,7 +700,7 @@ bool NANDMount::ExportFile(const char* path, const char* out)      u32 len = f_size(&file); -    fout = fopen(out, "wb"); +    Platform::FileHandle* fout = OpenLocalFile(out, FileMode::Write);      if (!fout)      {          f_close(&file); @@ -742,10 +718,10 @@ bool NANDMount::ExportFile(const char* path, const char* out)          u32 nread;          f_read(&file, buf, blocklen, &nread); -        fwrite(buf, blocklen, 1, fout); +        FileWrite(buf, blocklen, 1, fout);      } -    fclose(fout); +    CloseFile(fout);      f_close(&file);      Log(LogLevel::Debug, "Exported file from %s to %s\n", path, out); @@ -1144,10 +1120,10 @@ bool NANDMount::ImportTitle(const char* appfile, const DSi_TMD::TitleMetadata& t  {      NDSHeader header {};      { -        FILE* f = fopen(appfile, "rb"); +        Platform::FileHandle* f = OpenLocalFile(appfile, FileMode::Read);          if (!f) return false; -        fread(&header, sizeof(header), 1, f); -        fclose(f); +        FileRead(&header, sizeof(header), 1, f); +        CloseFile(f);      }      u32 version = tmd.Contents.GetVersion(); diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 777afe0..0077eac 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -25,6 +25,7 @@  #include "DSi_TMD.h"  #include "SPI_Firmware.h"  #include <array> +#include <memory>  #include <vector>  #include <string> @@ -84,7 +85,7 @@ class NANDMount  {  public:      explicit NANDMount(NANDImage& nand) noexcept; -    ~NANDMount(); +    ~NANDMount() noexcept;      NANDMount(const NANDMount&) = delete;      NANDMount& operator=(const NANDMount&) = delete; @@ -92,10 +93,15 @@ public:      NANDMount(NANDMount&&) = delete;      NANDMount& operator=(NANDMount&&) = delete; +    bool ReadSerialData(DSiSerialData& dataS); +    bool ReadHardwareInfoN(DSiHardwareInfoN& dataN);      void ReadHardwareInfo(DSiSerialData& dataS, DSiHardwareInfoN& dataN); -    void ReadUserData(DSiFirmwareSystemSettings& data); -    void PatchUserData(); +    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); @@ -211,6 +217,29 @@ enum class ConsoleRegion : u8      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. @@ -223,7 +252,7 @@ union DSiSerialData          u8 RsaSha1HMAC[0x80];          u32 Version;          u32 EntrySize; -        u32 SupportedLanguages; +        DSiSupportedLanguageMask SupportedLanguages;          u8 Unknown0[4];          ConsoleRegion Region;          char Serial[12]; diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index f1598eb..400f7d6 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1650,8 +1650,7 @@ std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)      bool dsi = header.IsDSi();      bool badDSiDump = false; -    u32 dsiRegion = header.DSiRegionMask; -    if (dsi && dsiRegion == 0) +    if (dsi && header.DSiRegionMask == RegionMask::NoRegion)      {          Log(LogLevel::Info, "DS header indicates DSi, but region is zero. Going in bad dump mode.\n");          badDSiDump = true; diff --git a/src/NDS_Header.h b/src/NDS_Header.h index 4496b95..626f80c 100644 --- a/src/NDS_Header.h +++ b/src/NDS_Header.h @@ -22,6 +22,21 @@  #include <string.h>
  #include "types.h"
 +/// Set to indicate the console regions that a ROM (including DSiWare)
 +/// can be played on.
 +enum RegionMask : u32
 +{
 +    NoRegion = 0,
 +    Japan = 1 << 0,
 +    USA = 1 << 1,
 +    Europe = 1 << 2,
 +    Australia = 1 << 3,
 +    China = 1 << 4,
 +    Korea = 1 << 5,
 +    Reserved = ~(Japan | USA | Europe | Australia | China | Korea),
 +    RegionFree = 0xFFFFFFFF,
 +};
 +
  // Consult GBATEK for info on what these are
  struct NDSHeader
  {
 @@ -105,7 +120,7 @@ struct NDSHeader      u8 DSiMBKWriteProtect[3]; // global MBK9 setting
      u8 DSiWRAMCntSetting; // global WRAMCNT setting
 -    u32 DSiRegionMask;
 +    RegionMask DSiRegionMask;
      u32 DSiPermissions[2];
      u8 Reserved6[3];
      u8 AppFlags; // flags at 1BF
 diff --git a/src/OpenGLSupport.cpp b/src/OpenGLSupport.cpp index f1914fc..5a8da11 100644 --- a/src/OpenGLSupport.cpp +++ b/src/OpenGLSupport.cpp @@ -72,9 +72,9 @@ bool BuildShaderProgram(const char* vs, const char* fs, GLuint* ids, const char*          //printf("shader source:\n--\n%s\n--\n", fs);          delete[] log; -        FILE* logf = fopen("shaderfail.log", "w"); -        fwrite(fs, len+1, 1, logf); -        fclose(logf); +        Platform::FileHandle* logf = Platform::OpenFile("shaderfail.log", Platform::FileMode::WriteText); +        Platform::FileWrite(fs, len+1, 1, logf); +        Platform::CloseFile(logf);          glDeleteShader(ids[0]);          glDeleteShader(ids[1]); diff --git a/src/Platform.h b/src/Platform.h index b40dce9..a379d85 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -121,13 +121,6 @@ enum ConfigEntry      DSiSD_FolderSync,      DSiSD_FolderPath, -    Firm_OverrideSettings [[deprecated("Individual fields can now be overridden")]], -    Firm_Username, -    Firm_Language, -    Firm_BirthdayMonth, -    Firm_BirthdayDay, -    Firm_Color, -    Firm_Message,      Firm_MAC,      WifiSettingsPath, diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 2fa0b18..0cb9574 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -207,11 +207,6 @@ int GetConfigInt(ConfigEntry entry)      case DSiSD_ImageSize: return imgsizes[Config::DSiSDSize]; -    case Firm_Language: return Config::FirmwareLanguage; -    case Firm_BirthdayMonth: return Config::FirmwareBirthdayMonth; -    case Firm_BirthdayDay: return Config::FirmwareBirthdayDay; -    case Firm_Color: return Config::FirmwareFavouriteColour; -      case AudioBitDepth: return Config::AudioBitDepth;  #ifdef GDBSTUB_ENABLED @@ -244,7 +239,6 @@ bool GetConfigBool(ConfigEntry entry)      case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0;      case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; -    case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0;      case DSi_FullBIOSBoot: return Config::DSiFullBIOSBoot != 0;  #ifdef GDBSTUB_ENABLED @@ -267,8 +261,6 @@ std::string GetConfigString(ConfigEntry entry)      case DSiSD_ImagePath: return Config::DSiSDPath;      case DSiSD_FolderPath: return Config::DSiSDFolderPath; -    case Firm_Username: return Config::FirmwareUsername; -    case Firm_Message: return Config::FirmwareMessage;      case WifiSettingsPath: return Config::WifiSettingsPath;      } diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 206332b..cc65dfd 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -47,6 +47,7 @@ using std::pair;  using std::string;  using std::tie;  using std::unique_ptr; +using std::wstring_convert;  using namespace Platform;  namespace ROMManager @@ -875,7 +876,7 @@ void LoadUserSettingsFromConfig(SPI_Firmware::Firmware& firmware)      UserData& currentData = firmware.EffectiveUserData();      // setting up username -    std::string orig_username = Platform::GetConfigString(Platform::Firm_Username); +    std::string orig_username = Config::FirmwareUsername;      if (!orig_username.empty())      { // If the frontend defines a username, take it. If not, leave the existing one.          std::u16string username = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(orig_username); @@ -884,7 +885,7 @@ void LoadUserSettingsFromConfig(SPI_Firmware::Firmware& firmware)          memcpy(currentData.Nickname, username.data(), usernameLength * sizeof(char16_t));      } -    auto language = static_cast<Language>(Platform::GetConfigInt(Platform::Firm_Language)); +    auto language = static_cast<Language>(Config::FirmwareLanguage);      if (language != Language::Reserved)      { // If the frontend specifies a language (rather than using the existing value)...          currentData.Settings &= ~Language::Reserved; // ..clear the existing language... @@ -892,26 +893,26 @@ void LoadUserSettingsFromConfig(SPI_Firmware::Firmware& firmware)      }      // setting up color -    u8 favoritecolor = Platform::GetConfigInt(Platform::Firm_Color); +    u8 favoritecolor = Config::FirmwareFavouriteColour;      if (favoritecolor != 0xFF)      {          currentData.FavoriteColor = favoritecolor;      } -    u8 birthmonth = Platform::GetConfigInt(Platform::Firm_BirthdayMonth); +    u8 birthmonth = Config::FirmwareBirthdayMonth;      if (birthmonth != 0)      { // If the frontend specifies a birth month (rather than using the existing value)...          currentData.BirthdayMonth = birthmonth;      } -    u8 birthday = Platform::GetConfigInt(Platform::Firm_BirthdayDay); +    u8 birthday = Config::FirmwareBirthdayDay;      if (birthday != 0)      { // If the frontend specifies a birthday (rather than using the existing value)...          currentData.BirthdayDay = birthday;      }      // setup message -    std::string orig_message = Platform::GetConfigString(Platform::Firm_Message); +    std::string orig_message = Config::FirmwareMessage;      if (!orig_message.empty())      {          std::u16string message = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(orig_message); @@ -966,7 +967,7 @@ static Platform::FileHandle* OpenNANDFile() noexcept          FileHandle* orig = Platform::OpenLocalFile(nandpath, FileMode::Read);          if (!orig)          { -            Log(LogLevel::Error, "Failed to open DSi NAND\n"); +            Log(LogLevel::Error, "Failed to open DSi NAND from %s\n", nandpath.c_str());              return nullptr;          } @@ -984,16 +985,77 @@ bool InstallNAND(const u8* es_keyY)      if (!nandfile)          return false; -    if (auto nand = std::make_unique<DSi_NAND::NANDImage>(nandfile, es_keyY); *nand) +    DSi_NAND::NANDImage nandImage(nandfile, es_keyY); +    if (!nandImage)      { -        DSi::NANDImage = std::move(nand); -        return true; +        Log(LogLevel::Error, "Failed to parse DSi NAND\n"); +        return false;      } -    else + +    // scoped so that mount isn't alive when we move the NAND image to DSi::NANDImage      { -        DSi::NANDImage = nullptr; -        return false; +        auto mount = DSi_NAND::NANDMount(nandImage); +        if (!mount) +        { +            Log(LogLevel::Error, "Failed to mount DSi NAND\n"); +            return false; +        } + +        DSi_NAND::DSiFirmwareSystemSettings settings {}; +        if (!mount.ReadUserData(settings)) +        { +            Log(LogLevel::Error, "Failed to read DSi NAND user data\n"); +            return false; +        } + +        // override user settings, if needed +        if (Config::FirmwareOverrideSettings) +        { +            // we store relevant strings as UTF-8, so we need to convert them to UTF-16 +            auto converter = wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}; + +            // setting up username +            std::u16string username = converter.from_bytes(Config::FirmwareUsername); +            size_t usernameLength = std::min(username.length(), (size_t) 10); +            memset(&settings.Nickname, 0, sizeof(settings.Nickname)); +            memcpy(&settings.Nickname, username.data(), usernameLength * sizeof(char16_t)); + +            // setting language +            settings.Language = static_cast<SPI_Firmware::Language>(Config::FirmwareLanguage); + +            // setting up color +            settings.FavoriteColor = Config::FirmwareFavouriteColour; + +            // setting up birthday +            settings.BirthdayMonth = Config::FirmwareBirthdayMonth; +            settings.BirthdayDay = Config::FirmwareBirthdayDay; + +            // setup message +            std::u16string message = converter.from_bytes(Config::FirmwareMessage); +            size_t messageLength = std::min(message.length(), (size_t) 26); +            memset(&settings.Message, 0, sizeof(settings.Message)); +            memcpy(&settings.Message, message.data(), messageLength * sizeof(char16_t)); + +            // TODO: make other items configurable? +        } + +        // fix touchscreen coords +        settings.TouchCalibrationADC1 = {0, 0}; +        settings.TouchCalibrationPixel1 = {0, 0}; +        settings.TouchCalibrationADC2 = {255 << 4, 191 << 4}; +        settings.TouchCalibrationPixel2 = {255, 191}; + +        settings.UpdateHash(); + +        if (!mount.ApplyUserData(settings)) +        { +            Log(LogLevel::Error, "Failed to write patched DSi NAND user data\n"); +            return false; +        }      } + +    DSi::NANDImage = std::make_unique<DSi_NAND::NANDImage>(std::move(nandImage)); +    return true;  }  bool InstallFirmware()  |