2021-08-24 09:46:20 -06:00
|
|
|
/*
|
2024-06-15 09:01:19 -06:00
|
|
|
Copyright 2016-2024 melonDS team
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
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/.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2021-11-20 04:16:59 -07:00
|
|
|
#include <codecvt>
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
#include "DSi.h"
|
|
|
|
#include "DSi_AES.h"
|
|
|
|
#include "DSi_NAND.h"
|
2023-10-11 09:20:05 -06:00
|
|
|
#include "FATIO.h"
|
2021-11-20 04:09:13 -07:00
|
|
|
#include "Platform.h"
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
#include "sha1/sha1.hpp"
|
|
|
|
#include "tiny-AES-c/aes.hpp"
|
|
|
|
|
|
|
|
#include "fatfs/ff.h"
|
|
|
|
|
2023-11-25 10:32:09 -07:00
|
|
|
using namespace melonDS::Platform;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-11-25 10:32:09 -07:00
|
|
|
namespace melonDS::DSi_NAND
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
NANDImage::NANDImage(Platform::FileHandle* nandfile, const DSiKey& es_keyY) noexcept : NANDImage(nandfile, es_keyY.data())
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-10-11 09:20:05 -06:00
|
|
|
}
|
2022-09-23 14:53:23 -06:00
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
NANDImage::NANDImage(Platform::FileHandle* nandfile, const u8* es_keyY) noexcept
|
|
|
|
{
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!nandfile)
|
2023-10-11 09:20:05 -06:00
|
|
|
return;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
Length = FileLength(nandfile);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
// read the nocash footer
|
|
|
|
|
2023-08-18 14:50:57 -06:00
|
|
|
FileSeek(nandfile, -0x40, FileSeekOrigin::End);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
char nand_footer[16];
|
|
|
|
const char* nand_footer_ref = "DSi eMMC CID/CPU";
|
2023-10-11 09:20:05 -06:00
|
|
|
FileRead(nand_footer, 1, sizeof(nand_footer), nandfile);
|
|
|
|
if (memcmp(nand_footer, nand_footer_ref, sizeof(nand_footer)))
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
// There is another copy of the footer at 000FF800h for the case
|
|
|
|
// that by external tools the image was cut off
|
|
|
|
// See https://problemkaputt.de/gbatek.htm#dsisdmmcimages
|
2023-08-18 14:50:57 -06:00
|
|
|
FileSeek(nandfile, 0x000FF800, FileSeekOrigin::Start);
|
2023-10-11 09:20:05 -06:00
|
|
|
FileRead(nand_footer, 1, sizeof(nand_footer), nandfile);
|
|
|
|
if (memcmp(nand_footer, nand_footer_ref, sizeof(nand_footer)))
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Error, "ERROR: NAND missing nocash footer\n");
|
2023-08-28 12:01:15 -06:00
|
|
|
CloseFile(nandfile);
|
2023-10-11 09:20:05 -06:00
|
|
|
return;
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
FileRead(eMMC_CID.data(), 1, sizeof(eMMC_CID), nandfile);
|
|
|
|
FileRead(&ConsoleID, 1, sizeof(ConsoleID), nandfile);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
// init NAND crypto
|
|
|
|
|
|
|
|
SHA1_CTX sha;
|
|
|
|
u8 tmp[20];
|
|
|
|
u8 keyX[16], keyY[16];
|
|
|
|
|
|
|
|
SHA1Init(&sha);
|
2023-10-11 09:20:05 -06:00
|
|
|
SHA1Update(&sha, eMMC_CID.data(), sizeof(eMMC_CID));
|
2021-08-24 09:46:20 -06:00
|
|
|
SHA1Final(tmp, &sha);
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
Bswap128(FATIV.data(), tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
*(u32*)&keyX[0] = (u32)ConsoleID;
|
|
|
|
*(u32*)&keyX[4] = (u32)ConsoleID ^ 0x24EE6906;
|
|
|
|
*(u32*)&keyX[8] = (u32)(ConsoleID >> 32) ^ 0xE65B601D;
|
|
|
|
*(u32*)&keyX[12] = (u32)(ConsoleID >> 32);
|
|
|
|
|
|
|
|
*(u32*)&keyY[0] = 0x0AB9DC76;
|
|
|
|
*(u32*)&keyY[4] = 0xBD4DC4D3;
|
|
|
|
*(u32*)&keyY[8] = 0x202DDD1D;
|
|
|
|
*(u32*)&keyY[12] = 0xE1A00005;
|
|
|
|
|
|
|
|
DSi_AES::DeriveNormalKey(keyX, keyY, tmp);
|
2023-10-11 09:20:05 -06:00
|
|
|
Bswap128(FATKey.data(), tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
|
|
|
|
*(u32*)&keyX[0] = 0x4E00004A;
|
|
|
|
*(u32*)&keyX[4] = 0x4A00004E;
|
|
|
|
*(u32*)&keyX[8] = (u32)(ConsoleID >> 32) ^ 0xC80C4B72;
|
|
|
|
*(u32*)&keyX[12] = (u32)ConsoleID;
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
memcpy(keyY, es_keyY, sizeof(keyY));
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
DSi_AES::DeriveNormalKey(keyX, keyY, tmp);
|
2023-10-11 09:20:05 -06:00
|
|
|
Bswap128(ESKey.data(), tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
CurFile = nandfile;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
NANDImage::~NANDImage()
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-08-18 14:50:57 -06:00
|
|
|
if (CurFile) CloseFile(CurFile);
|
2021-08-24 09:46:20 -06:00
|
|
|
CurFile = nullptr;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
NANDImage::NANDImage(NANDImage&& other) noexcept :
|
|
|
|
CurFile(other.CurFile),
|
|
|
|
eMMC_CID(other.eMMC_CID),
|
|
|
|
ConsoleID(other.ConsoleID),
|
|
|
|
FATIV(other.FATIV),
|
|
|
|
FATKey(other.FATKey),
|
2023-12-26 08:34:04 -07:00
|
|
|
ESKey(other.ESKey),
|
|
|
|
Length(other.Length)
|
2023-10-11 09:20:05 -06:00
|
|
|
{
|
|
|
|
other.CurFile = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
NANDImage& NANDImage::operator=(NANDImage&& other) noexcept
|
|
|
|
{
|
|
|
|
if (this != &other)
|
|
|
|
{
|
Refactor how save data (including SD cards) is initialized (#1898)
* Remove `FATStorage::Open` and `FATStorage::Close`
- That's what the constructor and destructor are for, respectively
* Add `FATStorage::IsReadOnly`
* Slight cleanup of `FATStorage`
- Make it move-constructible and move-assignable
- Represent the absence of a sync directory with `std::optional`, not an empty string
- Add `FATStorageArgs` for later use
* Refactor `CartHomebrew` to accept an optional `FATStorageArgs`
- `CartHomebrew` uses it to load an SD card image
- Not passing a `FATStorage` directly because we won't know if we need to load the card until we parse the ROM
- Store the `FATStorage` inside a `std::optional` instead of a pointer
- `CartHomebrew::Reset` no longer reloads the SD card; the frontend needs to set it with the `SetSDCard` method
* Close `NANDImage::CurFile` when move-assigning
- Whoops
* Add `Args.h`
- To construct a `NDS` or `DSi` with arguments
- Mostly intended for system files
* Fix incorrect `final` placement
* Refactor how `DSi`'s NAND and SD card are set
- Provide them via a `DSiArgs` argument in the constructor
- Give `DSi_MMCStorage` ownership of the `NANDImage` or `FATStorage` as needed, and expose getters/setters
- Replace `DSi_SDHost::Ports` with a `array<unique_ptr, 2>` to reduce the risk of leaks
- Store `DSi_MMCStorage`'s disk images in a `std::variant`
- The SD card and NAND image are no longer reset in `Reset()`; the frontend will need to do that itself
* Add getters/setters on `DSi` itself for its storage media
* Remove newly-unused `Platform::ConfigEntry`s
* Use `DSi::SetNAND` in the frontend
* Add `EmuThread::NeedToRecreateConsole`
* Document `NDSArgs` and give its fields default values
* Refactor how system files are loaded upon construction
- Pass `NDSArgs&&` into `NDS`'s constructor
- Use `std::array` for the emulator's BIOS images and the built-in FreeBIOS, to simplify copying and comparison
- Initialize the BIOS, firmware, and SD cards from `NDSArgs` or `DSiArgs`
- Add a new default constructor for `NDS` (not `DSi`) that initializes the DS with default system files
- Embed `FirmwareMem::Firmware` directly instead of in a `unique_ptr`
- `SPIHost` now takes a `Firmware&&` that it forwards to `FirmwareMem`
- Add `Firmware` getters/setters plus `const` variants for `NDS`, `Firmware`, and `FirmwareMem`
- Simplify installation of firmware
* Initialize the DSi BIOS in the constructor
- Change `DSi::ARM9iBIOS` and `ARM7iBIOS` to `std::array`
* Update the frontend to reflect the core's changes
* Remove `DSi_SDHost::CloseHandles`
* Pass `nullopt` instead of the empty string when folder sync is off
* Deduplicate ROM extraction logic
- `LoadGBAROM` and `LoadROM` now delegate to `LoadROMData`
- Also use `unique_ptr` instead of `new[]`
* Oops, missed some `get()`'s
* Move `NDS::IsLoadedARM9BIOSBuiltIn` to the header
- So it's likelier to be inlined
- Same for the ARM7 version
* Remove `NDS::SetConsoleType`
* Add `NDS::SetFirmware`
* Move `GBACart::SetupSave` to be `protected`
- It was only ever used inside the class
* Rename `GBACart::LoadSave` to `SetSaveMemory`
- Same for the cart slot
* Declare `GBACartSlot` as a friend of `GBACart::CartCommon`
* Revise `GBACartSlot`'s getters and setters
- Rename `InsertROM` and `LoadROM` to `SetCart`
- Add a `GetCart` method
* Clean up getters and setters for NDS and GBA carts
* Clean up how carts are inserted into the slots
- Remove setters that operate directly on pointers, to simplify error-handling (use ParseROM instead)
- Add overloads for all carts that accept a `const u8*` (to copy the ROM data) and a `unique_ptr<u8[]>` (to move the ROM data)
- Store all ROM and RAM data in `unique_ptr`
- Default-initialize all fields
- Simplify constructors and destructors, inheriting where applicable
* Refactor GBA save data insertion
- Make `SetupSave` private and non-virtual and move its logic to be in `SetSaveMemory`
- Add overloads for setting save data in the constructor
- Update the SRAM completely in `SetSaveMemory`
* Clean up `NDSCart::CartCommon::SetSaveMemory`
- Move its declaration next to the other `SaveMemory` methods
- Move its (empty) implementation to the header
* Add some comments
* Add Utils.cpp and Utils.h
* Rename some functions in Utils for clarity
* Add `GBACart::ParseROM` and `NDSCart::ParseROM` overloads that accept `unique_ptr<u8[]>`
- The `u8*` overloads delegate to these new overloads
- Also move `SetupSave` for both kinds of carts to be private non-virtual methods
* Finalize the `NDSCart` refactor
- Add `NDSCartArgs` to pass to `ParseROM`
- Add SRAM arguments for all retail carts
- Initialize SRAM inside the constructor
- Delegate to other constructors where possible
* Replace `ROMManager::NDSSave` and `GBASave` with `unique_ptr`
* Make both cart slots return the previously-inserted cart in `EjectCart`
- Primarily intended for reusing carts when resetting the console
* Make `NDS::EjectCart` return the old cart
* Initialize both cart slots with the provided ROM (if any)
* Make `NDS::EjectGBACart` return the ejected cart
* Clean up some comments in Args.h
* Rename `ROMManager::LoadBIOS` to `BootToMenu`
- Clarifies the intent
* Add `ROMManager::LoadDLDISDCard`
* Add a doc comment
* Refactor how the `NDS` is created or updated
- Rewrite `CreateConsole` to read from `Config` and load system files, but accept carts as arguments
- Fail without creating an `NDS` if any required system file doesn't load
- Add `UpdateConsole`, which delegates to `CreateConsole` if switching modes or starting the app
- Use `std::variant` to indicate whether a cart should be removed, inserted, or reused
- Load all system files (plus SD cards) in `UpdateConsole`
- Eject the cart and reinsert it into the new console if applicable
* Respect some more `Config` settings in the `Load*` functions
* Remove `InstallNAND` in favor of `LoadNAND`
* Fix some potential bugs in `LoadROMData`
* Oops, forgot to delete the definition of `InstallNAND`
* Add functions to get `FATStorageArgs`
- Not the cards themselves, but to get the arguments you _would_ use to load the cards
* Refactor `ROMManager::LoadROM`
- Load the ROM and save data before trying to initialize the console
* Clean up `ROMManager::Reset` and `BootToMenu`
- Let `EmuThread::UpdateConsole` do the heavy lifting
* Clean up `LoadGBAROM`
* Remove some unused functions
* Set the default DSi BIOS to be broken in `DSiArgs`
* Respect `Config::DSiFullBIOSBoot` when loading DSi BIOS files
* Remove some more unused functions
* Remove redundant `virtual` specifiers
* Refactor `NDSCart::CartCommon::Type()` to return a member instead of a constant
- One less virtual dispatch
- The cart type is read in `NDSCartSlot::DoSavestate`, which is a path downstream (due to rewinding)
* Remove a hash that I computed for debugging purposes
* Make `ByteSwap` `constexpr`
* Remove an unused `#include`
* Remove unnecessary functions from the NDS carts
- Mostly overrides that added nothing
* Default-initialize all NDSCart fields
* Make `GBACart::Type()` not rely on virtual dispatch
- `GBACartSlot::DoSavestate` calls it, and savestates can be a hot path downstream
* Don't forget to reset the base class in `CartGameSolarSensor::Reset()`
* Remove redundant `virtual` specifiers
* Default-initialize some fields in `GBACart`
* Fix ROMs not loading from archives in the frontend
- Whoops
* Change how the `Firmware` member is declared
* Forgot an include in Utils.cpp
* Rename `FirmwareMem::Firmware` to `FirmwareData` to fix a build error on Linux
- One of these days I'll convince you people to let me use `camelCaseMemberNames`
* Add `override` to places in `DSi_MMCStorage` that warrant it
* Fix firmware saving on the frontend
- Remove `GetConfigString` and `ConfigEntry::WifiSettingsPath` while I'm at it
* Add a non-const `GetNAND()`
2023-12-04 09:57:22 -07:00
|
|
|
if (CurFile)
|
|
|
|
CloseFile(CurFile);
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
CurFile = other.CurFile;
|
|
|
|
eMMC_CID = other.eMMC_CID;
|
|
|
|
ConsoleID = other.ConsoleID;
|
|
|
|
FATIV = other.FATIV;
|
|
|
|
FATKey = other.FATKey;
|
|
|
|
ESKey = other.ESKey;
|
2023-12-26 08:34:04 -07:00
|
|
|
Length = other.Length;
|
2023-10-11 09:20:05 -06:00
|
|
|
|
|
|
|
other.CurFile = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
NANDMount::NANDMount(NANDImage& nand) noexcept : Image(&nand)
|
2022-09-23 14:53:23 -06:00
|
|
|
{
|
2023-10-11 09:20:05 -06:00
|
|
|
if (!nand)
|
|
|
|
return;
|
|
|
|
|
|
|
|
CurFS = std::make_unique<FATFS>();
|
|
|
|
ff_disk_open(
|
|
|
|
[this](BYTE* buf, LBA_t sector, UINT num) {
|
|
|
|
return this->FF_ReadNAND(buf, sector, num);
|
|
|
|
},
|
|
|
|
[this](const BYTE* buf, LBA_t sector, UINT num) {
|
|
|
|
return this->FF_WriteNAND(buf, sector, num);
|
|
|
|
},
|
|
|
|
(LBA_t)(nand.GetLength()>>9)
|
|
|
|
);
|
|
|
|
|
|
|
|
FRESULT res;
|
|
|
|
res = f_mount(CurFS.get(), "0:", 0);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
|
|
|
Log(LogLevel::Error, "NAND mounting failed: %d\n", res);
|
|
|
|
f_unmount("0:");
|
|
|
|
ff_disk_close();
|
|
|
|
return;
|
|
|
|
}
|
2022-09-23 14:53:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
NANDMount::~NANDMount() noexcept
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-10-11 09:20:05 -06:00
|
|
|
f_unmount("0:");
|
|
|
|
ff_disk_close();
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
void NANDImage::SetupFATCrypto(AES_ctx* ctx, u32 ctr)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
u8 iv[16];
|
2023-10-11 09:20:05 -06:00
|
|
|
memcpy(iv, FATIV.data(), sizeof(iv));
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
u32 res;
|
|
|
|
res = iv[15] + (ctr & 0xFF);
|
|
|
|
iv[15] = (res & 0xFF);
|
|
|
|
res = iv[14] + ((ctr >> 8) & 0xFF) + (res >> 8);
|
|
|
|
iv[14] = (res & 0xFF);
|
|
|
|
res = iv[13] + ((ctr >> 16) & 0xFF) + (res >> 8);
|
|
|
|
iv[13] = (res & 0xFF);
|
|
|
|
res = iv[12] + (ctr >> 24) + (res >> 8);
|
|
|
|
iv[12] = (res & 0xFF);
|
|
|
|
iv[11] += (res >> 8);
|
|
|
|
for (int i = 10; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if (iv[i+1] == 0) iv[i]++;
|
|
|
|
else break;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
AES_init_ctx_iv(ctx, FATKey.data(), iv);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
u32 NANDImage::ReadFATBlock(u64 addr, u32 len, u8* buf)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
u32 ctr = (u32)(addr >> 4);
|
|
|
|
|
|
|
|
AES_ctx ctx;
|
|
|
|
SetupFATCrypto(&ctx, ctr);
|
|
|
|
|
2023-08-18 14:50:57 -06:00
|
|
|
FileSeek(CurFile, addr, FileSeekOrigin::Start);
|
|
|
|
u32 res = FileRead(buf, len, 1, CurFile);
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!res) return 0;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < len; i += 16)
|
|
|
|
{
|
|
|
|
u8 tmp[16];
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(tmp, &buf[i]);
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, tmp, sizeof(tmp));
|
|
|
|
Bswap128(&buf[i], tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
u32 NANDImage::WriteFATBlock(u64 addr, u32 len, const u8* buf)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
u32 ctr = (u32)(addr >> 4);
|
|
|
|
|
|
|
|
AES_ctx ctx;
|
|
|
|
SetupFATCrypto(&ctx, ctr);
|
|
|
|
|
2023-08-18 14:50:57 -06:00
|
|
|
FileSeek(CurFile, addr, FileSeekOrigin::Start);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
for (u32 s = 0; s < len; s += 0x200)
|
|
|
|
{
|
|
|
|
u8 tempbuf[0x200];
|
|
|
|
|
|
|
|
for (u32 i = 0; i < 0x200; i += 16)
|
|
|
|
{
|
|
|
|
u8 tmp[16];
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(tmp, &buf[s+i]);
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, tmp, sizeof(tmp));
|
|
|
|
Bswap128(&tempbuf[i], tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
2023-08-18 14:50:57 -06:00
|
|
|
u32 res = FileWrite(tempbuf, sizeof(tempbuf), 1, CurFile);
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!res) return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
UINT NANDMount::FF_ReadNAND(BYTE* buf, LBA_t sector, UINT num)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
// TODO: allow selecting other partitions?
|
|
|
|
u64 baseaddr = 0x10EE00;
|
|
|
|
|
|
|
|
u64 blockaddr = baseaddr + (sector * 0x200ULL);
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
u32 res = Image->ReadFATBlock(blockaddr, num*0x200, buf);
|
2021-08-24 09:46:20 -06:00
|
|
|
return res >> 9;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
UINT NANDMount::FF_WriteNAND(const BYTE* buf, LBA_t sector, UINT num)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
// TODO: allow selecting other partitions?
|
|
|
|
u64 baseaddr = 0x10EE00;
|
|
|
|
|
|
|
|
u64 blockaddr = baseaddr + (sector * 0x200ULL);
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
u32 res = Image->WriteFATBlock(blockaddr, num*0x200, buf);
|
2021-08-24 09:46:20 -06:00
|
|
|
return res >> 9;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDImage::ESEncrypt(u8* data, u32 len) const
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
AES_ctx ctx;
|
|
|
|
u8 iv[16];
|
|
|
|
u8 mac[16];
|
|
|
|
|
|
|
|
iv[0] = 0x02;
|
|
|
|
for (int i = 0; i < 12; i++) iv[1+i] = data[len+0x1C-i];
|
|
|
|
iv[13] = 0x00;
|
|
|
|
iv[14] = 0x00;
|
|
|
|
iv[15] = 0x01;
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
AES_init_ctx_iv(&ctx, ESKey.data(), iv);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
u32 blklen = (len + 0xF) & ~0xF;
|
|
|
|
mac[0] = 0x3A;
|
|
|
|
for (int i = 1; i < 13; i++) mac[i] = iv[i];
|
|
|
|
mac[13] = (blklen >> 16) & 0xFF;
|
|
|
|
mac[14] = (blklen >> 8) & 0xFF;
|
|
|
|
mac[15] = blklen & 0xFF;
|
|
|
|
|
|
|
|
AES_ECB_encrypt(&ctx, mac);
|
|
|
|
|
|
|
|
u32 coarselen = len & ~0xF;
|
|
|
|
for (u32 i = 0; i < coarselen; i += 16)
|
|
|
|
{
|
|
|
|
u8 tmp[16];
|
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(tmp, &data[i]);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
for (int i = 0; i < 16; i++) mac[i] ^= tmp[i];
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, tmp, 16);
|
|
|
|
AES_ECB_encrypt(&ctx, mac);
|
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(&data[i], tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
u32 remlen = len - coarselen;
|
|
|
|
if (remlen)
|
|
|
|
{
|
|
|
|
u8 rem[16];
|
|
|
|
|
2023-09-02 10:56:58 -06:00
|
|
|
memset(rem, 0, 16);
|
|
|
|
for (int i = 0; i < remlen; i++)
|
|
|
|
rem[15-i] = data[coarselen+i];
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
for (int i = 0; i < 16; i++) mac[i] ^= rem[i];
|
2023-08-28 12:01:15 -06:00
|
|
|
AES_CTR_xcrypt_buffer(&ctx, rem, sizeof(rem));
|
2021-08-24 09:46:20 -06:00
|
|
|
AES_ECB_encrypt(&ctx, mac);
|
|
|
|
|
2023-09-02 10:56:58 -06:00
|
|
|
for (int i = 0; i < remlen; i++)
|
|
|
|
data[coarselen+i] = rem[15-i];
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Iv[13] = 0x00;
|
|
|
|
ctx.Iv[14] = 0x00;
|
|
|
|
ctx.Iv[15] = 0x00;
|
2023-08-28 12:01:15 -06:00
|
|
|
AES_CTR_xcrypt_buffer(&ctx, mac, sizeof(mac));
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(&data[len], mac);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
u8 footer[16];
|
|
|
|
|
|
|
|
iv[0] = 0x00;
|
|
|
|
iv[1] = 0x00;
|
|
|
|
iv[2] = 0x00;
|
|
|
|
for (int i = 0; i < 12; i++) iv[3+i] = data[len+0x1C-i];
|
|
|
|
iv[15] = 0x00;
|
|
|
|
|
|
|
|
footer[15] = 0x3A;
|
|
|
|
footer[2] = (len >> 16) & 0xFF;
|
|
|
|
footer[1] = (len >> 8) & 0xFF;
|
|
|
|
footer[0] = len & 0xFF;
|
|
|
|
|
|
|
|
AES_ctx_set_iv(&ctx, iv);
|
2023-08-28 12:01:15 -06:00
|
|
|
AES_CTR_xcrypt_buffer(&ctx, footer, sizeof(footer));
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
data[len+0x10] = footer[15];
|
|
|
|
data[len+0x1D] = footer[2];
|
|
|
|
data[len+0x1E] = footer[1];
|
|
|
|
data[len+0x1F] = footer[0];
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-12-12 03:07:22 -07:00
|
|
|
bool NANDImage::ESDecrypt(u8* data, u32 len) const
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
AES_ctx ctx;
|
|
|
|
u8 iv[16];
|
|
|
|
u8 mac[16];
|
|
|
|
|
|
|
|
iv[0] = 0x02;
|
|
|
|
for (int i = 0; i < 12; i++) iv[1+i] = data[len+0x1C-i];
|
|
|
|
iv[13] = 0x00;
|
|
|
|
iv[14] = 0x00;
|
|
|
|
iv[15] = 0x01;
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
AES_init_ctx_iv(&ctx, ESKey.data(), iv);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
u32 blklen = (len + 0xF) & ~0xF;
|
|
|
|
mac[0] = 0x3A;
|
|
|
|
for (int i = 1; i < 13; i++) mac[i] = iv[i];
|
|
|
|
mac[13] = (blklen >> 16) & 0xFF;
|
|
|
|
mac[14] = (blklen >> 8) & 0xFF;
|
|
|
|
mac[15] = blklen & 0xFF;
|
|
|
|
|
|
|
|
AES_ECB_encrypt(&ctx, mac);
|
|
|
|
|
|
|
|
u32 coarselen = len & ~0xF;
|
|
|
|
for (u32 i = 0; i < coarselen; i += 16)
|
|
|
|
{
|
|
|
|
u8 tmp[16];
|
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(tmp, &data[i]);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
AES_CTR_xcrypt_buffer(&ctx, tmp, sizeof(tmp));
|
2021-08-24 09:46:20 -06:00
|
|
|
for (int i = 0; i < 16; i++) mac[i] ^= tmp[i];
|
|
|
|
AES_ECB_encrypt(&ctx, mac);
|
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(&data[i], tmp);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
u32 remlen = len - coarselen;
|
|
|
|
if (remlen)
|
|
|
|
{
|
|
|
|
u8 rem[16];
|
|
|
|
|
|
|
|
u32 ivnum = (coarselen >> 4) + 1;
|
|
|
|
iv[13] = (ivnum >> 16) & 0xFF;
|
|
|
|
iv[14] = (ivnum >> 8) & 0xFF;
|
|
|
|
iv[15] = ivnum & 0xFF;
|
|
|
|
|
2023-09-02 10:56:58 -06:00
|
|
|
memset(rem, 0, 16);
|
|
|
|
AES_ctx_set_iv(&ctx, iv);
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, rem, 16);
|
|
|
|
|
|
|
|
for (int i = 0; i < remlen; i++)
|
|
|
|
rem[15-i] = data[coarselen+i];
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
AES_ctx_set_iv(&ctx, iv);
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, rem, 16);
|
|
|
|
for (int i = 0; i < 16; i++) mac[i] ^= rem[i];
|
|
|
|
AES_ECB_encrypt(&ctx, mac);
|
|
|
|
|
2023-09-02 10:56:58 -06:00
|
|
|
for (int i = 0; i < remlen; i++)
|
|
|
|
data[coarselen+i] = rem[15-i];
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Iv[13] = 0x00;
|
|
|
|
ctx.Iv[14] = 0x00;
|
|
|
|
ctx.Iv[15] = 0x00;
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, mac, 16);
|
|
|
|
|
|
|
|
u8 footer[16];
|
|
|
|
|
|
|
|
iv[0] = 0x00;
|
|
|
|
iv[1] = 0x00;
|
|
|
|
iv[2] = 0x00;
|
|
|
|
for (int i = 0; i < 12; i++) iv[3+i] = data[len+0x1C-i];
|
|
|
|
iv[15] = 0x00;
|
|
|
|
|
2023-08-28 12:01:15 -06:00
|
|
|
Bswap128(footer, &data[len+0x10]);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
AES_ctx_set_iv(&ctx, iv);
|
2023-08-28 12:01:15 -06:00
|
|
|
AES_CTR_xcrypt_buffer(&ctx, footer, sizeof(footer));
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
data[len+0x10] = footer[15];
|
|
|
|
data[len+0x1D] = footer[2];
|
|
|
|
data[len+0x1E] = footer[1];
|
|
|
|
data[len+0x1F] = footer[0];
|
|
|
|
|
|
|
|
u32 footerlen = footer[0] | (footer[1] << 8) | (footer[2] << 16);
|
|
|
|
if (footerlen != len)
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Error, "ESDecrypt: bad length %d (expected %d)\n", len, footerlen);
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < 16; i++)
|
|
|
|
{
|
|
|
|
if (data[len+i] != mac[15-i])
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Warn, "ESDecrypt: bad MAC\n");
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
bool NANDMount::ReadSerialData(DSiSerialData& dataS)
|
2021-08-30 12:26:49 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2023-10-24 15:28:14 -06:00
|
|
|
FRESULT res = f_open(&file, "0:/sys/HWINFO_S.dat", FA_OPEN_EXISTING | FA_READ);
|
2021-08-30 12:26:49 -06:00
|
|
|
|
|
|
|
if (res == FR_OK)
|
|
|
|
{
|
2023-10-24 15:28:14 -06:00
|
|
|
u32 nread;
|
2023-10-02 09:54:17 -06:00
|
|
|
f_read(&file, &dataS, sizeof(DSiSerialData), &nread);
|
2021-08-30 12:26:49 -06:00
|
|
|
f_close(&file);
|
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
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);
|
|
|
|
|
2021-08-30 12:26:49 -06:00
|
|
|
if (res == FR_OK)
|
|
|
|
{
|
2023-10-24 15:28:14 -06:00
|
|
|
u32 nread;
|
2023-10-02 09:54:17 -06:00
|
|
|
f_read(&file, dataN.data(), sizeof(dataN), &nread);
|
2021-08-30 12:26:49 -06:00
|
|
|
f_close(&file);
|
|
|
|
}
|
2023-10-24 15:28:14 -06:00
|
|
|
|
|
|
|
return res == FR_OK;
|
2021-08-30 12:26:49 -06:00
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
void NANDMount::ReadHardwareInfo(DSiSerialData& dataS, DSiHardwareInfoN& dataN)
|
|
|
|
{
|
|
|
|
ReadSerialData(dataS);
|
|
|
|
ReadHardwareInfoN(dataN);
|
|
|
|
}
|
2021-08-30 12:26:49 -06:00
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
bool NANDMount::ReadUserData(DSiFirmwareSystemSettings& data)
|
2021-08-30 12:26:49 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-30 12:26:49 -06:00
|
|
|
FRESULT res;
|
|
|
|
u32 nread;
|
|
|
|
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL f1, f2;
|
2021-08-30 12:26:49 -06:00
|
|
|
int v1, v2;
|
|
|
|
|
|
|
|
res = f_open(&f1, "0:/shared1/TWLCFG0.dat", FA_OPEN_EXISTING | FA_READ);
|
|
|
|
if (res != FR_OK)
|
|
|
|
v1 = -1;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
u8 tmp;
|
|
|
|
f_lseek(&f1, 0x81);
|
|
|
|
f_read(&f1, &tmp, 1, &nread);
|
|
|
|
v1 = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = f_open(&f2, "0:/shared1/TWLCFG1.dat", FA_OPEN_EXISTING | FA_READ);
|
|
|
|
if (res != FR_OK)
|
|
|
|
v2 = -1;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
u8 tmp;
|
|
|
|
f_lseek(&f2, 0x81);
|
|
|
|
f_read(&f2, &tmp, 1, &nread);
|
|
|
|
v2 = tmp;
|
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
if (v1 < 0 && v2 < 0) return false;
|
2021-08-30 12:26:49 -06:00
|
|
|
|
|
|
|
if (v2 > v1)
|
|
|
|
{
|
|
|
|
file = f2;
|
|
|
|
f_close(&f1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
file = f1;
|
|
|
|
f_close(&f2);
|
|
|
|
}
|
|
|
|
|
|
|
|
f_lseek(&file, 0);
|
2023-10-02 09:54:17 -06:00
|
|
|
f_read(&file, &data, sizeof(DSiFirmwareSystemSettings), &nread);
|
2021-08-30 12:26:49 -06:00
|
|
|
f_close(&file);
|
2023-10-24 15:28:14 -06:00
|
|
|
|
|
|
|
return true;
|
2021-08-30 12:26:49 -06:00
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
static bool SaveUserData(const char* filename, const DSiFirmwareSystemSettings& data)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-10-24 15:28:14 -06:00
|
|
|
FF_FIL file;
|
|
|
|
if (FRESULT res = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ | FA_WRITE); res != FR_OK)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-10-24 15:28:14 -06:00
|
|
|
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)
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
u32 bytes_written = 0;
|
|
|
|
FRESULT res = f_write(&file, &data, sizeof(DSiFirmwareSystemSettings), &bytes_written);
|
|
|
|
f_close(&file);
|
2021-11-20 04:09:13 -07:00
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
if (res != FR_OK || bytes_written != sizeof(DSiFirmwareSystemSettings))
|
|
|
|
{
|
|
|
|
Log(LogLevel::Error, "NAND: editing file %s failed: %d\n", filename, res);
|
|
|
|
return false;
|
|
|
|
}
|
2023-10-02 09:54:17 -06:00
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
return true;
|
|
|
|
}
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
bool NANDMount::ApplyUserData(const DSiFirmwareSystemSettings& data)
|
|
|
|
{
|
|
|
|
bool ok0 = SaveUserData("0:/shared1/TWLCFG0.dat", data);
|
|
|
|
bool ok1 = SaveUserData("0:/shared1/TWLCFG1.dat", data);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
return ok0 && ok1;
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void debug_listfiles(const char* path)
|
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_DIR dir;
|
|
|
|
FF_FILINFO info;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
|
|
|
|
|
|
|
res = f_opendir(&dir, path);
|
|
|
|
if (res != FR_OK) return;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
res = f_readdir(&dir, &info);
|
2021-10-28 10:47:13 -06:00
|
|
|
if (res != FR_OK) break;
|
|
|
|
if (!info.fname[0]) break;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
char fullname[512];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fullname, sizeof(fullname), "%s/%s", path, info.fname);
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Debug, "[%c] %s\n", (info.fattrib&AM_DIR)?'D':'F', fullname);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
if (info.fattrib & AM_DIR)
|
|
|
|
{
|
|
|
|
debug_listfiles(fullname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f_closedir(&dir);
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ImportFile(const char* path, const u8* data, size_t len)
|
2023-07-08 14:17:30 -06:00
|
|
|
{
|
|
|
|
if (!data || !len || !path)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
FF_FIL file;
|
|
|
|
FRESULT res;
|
|
|
|
|
|
|
|
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 buf[0x1000];
|
|
|
|
for (u32 i = 0; i < len; i += sizeof(buf))
|
|
|
|
{ // For each block in the file...
|
|
|
|
u32 blocklen;
|
|
|
|
if ((i + sizeof(buf)) > len)
|
|
|
|
blocklen = len - i;
|
|
|
|
else
|
|
|
|
blocklen = sizeof(buf);
|
|
|
|
|
|
|
|
u32 nwrite;
|
|
|
|
memcpy(buf, data + i, blocklen);
|
|
|
|
f_write(&file, buf, blocklen, &nwrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
Log(LogLevel::Debug, "Imported file from memory to %s\n", path);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ImportFile(const char* path, const char* in)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
Platform::FileHandle* fin = OpenLocalFile(in, FileMode::Read);
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!fin)
|
|
|
|
return false;
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
u32 len = FileLength(fin);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
2023-10-24 15:28:14 -06:00
|
|
|
CloseFile(fin);
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-10-28 10:47:13 -06:00
|
|
|
u8 buf[0x1000];
|
2023-07-08 14:17:30 -06:00
|
|
|
for (u32 i = 0; i < len; i += sizeof(buf))
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
u32 blocklen;
|
2023-07-08 14:17:30 -06:00
|
|
|
if ((i + sizeof(buf)) > len)
|
2021-08-24 09:46:20 -06:00
|
|
|
blocklen = len - i;
|
|
|
|
else
|
2023-07-08 14:17:30 -06:00
|
|
|
blocklen = sizeof(buf);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
u32 nwrite;
|
2023-10-24 15:28:14 -06:00
|
|
|
FileRead(buf, blocklen, 1, fin);
|
2021-08-24 09:46:20 -06:00
|
|
|
f_write(&file, buf, blocklen, &nwrite);
|
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
CloseFile(fin);
|
2021-08-24 09:46:20 -06:00
|
|
|
f_close(&file);
|
|
|
|
|
2023-07-08 14:17:30 -06:00
|
|
|
Log(LogLevel::Debug, "Imported file from %s to %s\n", in, path);
|
|
|
|
|
2021-08-24 09:46:20 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ExportFile(const char* path, const char* out)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
|
|
|
|
|
|
|
res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
|
|
|
|
if (res != FR_OK)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
u32 len = f_size(&file);
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
Platform::FileHandle* fout = OpenLocalFile(out, FileMode::Write);
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!fout)
|
|
|
|
{
|
|
|
|
f_close(&file);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-10-28 10:47:13 -06:00
|
|
|
u8 buf[0x1000];
|
|
|
|
for (u32 i = 0; i < len; i += 0x1000)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
u32 blocklen;
|
2021-10-28 10:47:13 -06:00
|
|
|
if ((i + 0x1000) > len)
|
2021-08-24 09:46:20 -06:00
|
|
|
blocklen = len - i;
|
|
|
|
else
|
2021-10-28 10:47:13 -06:00
|
|
|
blocklen = 0x1000;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
u32 nread;
|
|
|
|
f_read(&file, buf, blocklen, &nread);
|
2023-10-24 15:28:14 -06:00
|
|
|
FileWrite(buf, blocklen, 1, fout);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
2023-10-24 15:28:14 -06:00
|
|
|
CloseFile(fout);
|
2021-08-24 09:46:20 -06:00
|
|
|
f_close(&file);
|
|
|
|
|
2023-07-08 14:17:30 -06:00
|
|
|
Log(LogLevel::Debug, "Exported file from %s to %s\n", path, out);
|
|
|
|
|
2021-08-24 09:46:20 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
void NANDMount::RemoveFile(const char* path)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FILINFO info;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res = f_stat(path, &info);
|
|
|
|
if (res != FR_OK) return;
|
|
|
|
|
|
|
|
if (info.fattrib & AM_RDO)
|
|
|
|
f_chmod(path, 0, AM_RDO);
|
|
|
|
|
|
|
|
f_unlink(path);
|
2023-07-08 14:17:30 -06:00
|
|
|
Log(LogLevel::Debug, "Removed file at %s\n", path);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
void NANDMount::RemoveDir(const char* path)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_DIR dir;
|
|
|
|
FF_FILINFO info;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
|
|
|
|
|
|
|
res = f_stat(path, &info);
|
|
|
|
if (res != FR_OK) return;
|
|
|
|
|
|
|
|
if (info.fattrib & AM_RDO)
|
|
|
|
f_chmod(path, 0, AM_RDO);
|
|
|
|
|
|
|
|
res = f_opendir(&dir, path);
|
|
|
|
if (res != FR_OK) return;
|
|
|
|
|
|
|
|
std::vector<std::string> dirlist;
|
|
|
|
std::vector<std::string> filelist;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
res = f_readdir(&dir, &info);
|
|
|
|
if (res != FR_OK) break;
|
|
|
|
if (!info.fname[0]) break;
|
|
|
|
|
|
|
|
char fullname[512];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fullname, sizeof(fullname), "%s/%s", path, info.fname);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
if (info.fattrib & AM_RDO)
|
|
|
|
f_chmod(path, 0, AM_RDO);
|
|
|
|
|
|
|
|
if (info.fattrib & AM_DIR)
|
|
|
|
dirlist.push_back(fullname);
|
|
|
|
else
|
|
|
|
filelist.push_back(fullname);
|
|
|
|
}
|
|
|
|
|
|
|
|
f_closedir(&dir);
|
|
|
|
|
|
|
|
for (std::vector<std::string>::iterator it = dirlist.begin(); it != dirlist.end(); it++)
|
|
|
|
{
|
|
|
|
const char* path = (*it).c_str();
|
|
|
|
RemoveDir(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (std::vector<std::string>::iterator it = filelist.begin(); it != filelist.end(); it++)
|
|
|
|
{
|
|
|
|
const char* path = (*it).c_str();
|
|
|
|
f_unlink(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
f_unlink(path);
|
2023-07-08 14:17:30 -06:00
|
|
|
Log(LogLevel::Debug, "Removed directory at %s\n", path);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
u32 NANDMount::GetTitleVersion(u32 category, u32 titleid)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
FRESULT res;
|
|
|
|
char path[256];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(path, sizeof(path), "0:/title/%08x/%08x/content/title.tmd", category, titleid);
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
|
|
|
|
if (res != FR_OK)
|
|
|
|
return 0xFFFFFFFF;
|
|
|
|
|
|
|
|
u32 version;
|
|
|
|
u32 nread;
|
|
|
|
f_lseek(&file, 0x1E4);
|
|
|
|
f_read(&file, &version, 4, &nread);
|
|
|
|
version = (version >> 24) | ((version & 0xFF0000) >> 8) | ((version & 0xFF00) << 8) | (version << 24);
|
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
void NANDMount::ListTitles(u32 category, std::vector<u32>& titlelist)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
FRESULT res;
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_DIR titledir;
|
2021-08-24 09:46:20 -06:00
|
|
|
char path[256];
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(path, sizeof(path), "0:/title/%08x", category);
|
2021-08-24 09:46:20 -06:00
|
|
|
res = f_opendir(&titledir, path);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Warn, "NAND: !! no title dir (%s)\n", path);
|
2021-08-24 09:46:20 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FILINFO info;
|
2021-08-24 09:46:20 -06:00
|
|
|
f_readdir(&titledir, &info);
|
|
|
|
if (!info.fname[0])
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (strlen(info.fname) != 8)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
u32 titleid;
|
|
|
|
if (sscanf(info.fname, "%08x", &titleid) < 1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
u32 version = GetTitleVersion(category, titleid);
|
|
|
|
if (version == 0xFFFFFFFF)
|
|
|
|
continue;
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(path, sizeof(path), "0:/title/%08x/%08x/content/%08x.app", category, titleid, version);
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FILINFO appinfo;
|
2021-08-24 09:46:20 -06:00
|
|
|
res = f_stat(path, &appinfo);
|
|
|
|
if (res != FR_OK)
|
|
|
|
continue;
|
|
|
|
if (appinfo.fattrib & AM_DIR)
|
|
|
|
continue;
|
|
|
|
if (appinfo.fsize < 0x4000)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// title is good, add it to the list
|
|
|
|
titlelist.push_back(titleid);
|
|
|
|
}
|
|
|
|
|
|
|
|
f_closedir(&titledir);
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::TitleExists(u32 category, u32 titleid)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
char path[256];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(path, sizeof(path), "0:/title/%08x/%08x/content/title.tmd", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
FRESULT res = f_stat(path, nullptr);
|
|
|
|
return (res == FR_OK);
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
void NANDMount::GetTitleInfo(u32 category, u32 titleid, u32& version, NDSHeader* header, NDSBanner* banner)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
version = GetTitleVersion(category, titleid);
|
|
|
|
if (version == 0xFFFFFFFF)
|
|
|
|
return;
|
|
|
|
|
|
|
|
FRESULT res;
|
|
|
|
|
|
|
|
char path[256];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(path, sizeof(path), "0:/title/%08x/%08x/content/%08x.app", category, titleid, version);
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
|
|
|
|
if (res != FR_OK)
|
|
|
|
return;
|
|
|
|
|
|
|
|
u32 nread;
|
2021-08-26 10:59:07 -06:00
|
|
|
f_read(&file, header, sizeof(NDSHeader), &nread);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
if (banner)
|
|
|
|
{
|
2021-08-26 10:59:07 -06:00
|
|
|
u32 banneraddr = header->BannerOffset;
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!banneraddr)
|
|
|
|
{
|
2021-08-27 08:45:26 -06:00
|
|
|
memset(banner, 0, sizeof(NDSBanner));
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
f_lseek(&file, banneraddr);
|
2021-08-26 10:59:07 -06:00
|
|
|
f_read(&file, banner, sizeof(NDSBanner), &nread);
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::CreateTicket(const char* path, u32 titleid0, u32 titleid1, u8 version)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
|
|
|
u32 nwrite;
|
|
|
|
|
|
|
|
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Error, "CreateTicket: failed to create file (%d)\n", res);
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 ticket[0x2C4];
|
|
|
|
memset(ticket, 0, 0x2C4);
|
|
|
|
|
|
|
|
// signature, atleast make it look like we tried :P
|
|
|
|
*(u32*)&ticket[0x000] = 0x01000100;
|
|
|
|
strcpy((char*)&ticket[0x140], "Root-CA00000001-XS00000006");
|
|
|
|
|
|
|
|
*(u32*)&ticket[0x1DC] = titleid0;
|
|
|
|
*(u32*)&ticket[0x1E0] = titleid1;
|
|
|
|
ticket[0x1E6] = version;
|
|
|
|
|
|
|
|
memset(&ticket[0x222], 0xFF, 0x20);
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
Image->ESEncrypt(ticket, 0x2A4);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
f_write(&file, ticket, 0x2C4, &nwrite);
|
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::CreateSaveFile(const char* path, u32 len)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
if (len == 0) return true;
|
|
|
|
if (len < 0x200) return false;
|
|
|
|
if (len > 0x8000000) return false;
|
|
|
|
|
|
|
|
u32 clustersize, maxfiles, totsec16, fatsz16;
|
|
|
|
|
|
|
|
// CHECKME!
|
2022-03-07 13:08:29 -07:00
|
|
|
// code inspired from https://github.com/Epicpkmn11/NTM/blob/master/arm9/src/sav.c
|
|
|
|
const u16 sectorsize = 0x200;
|
|
|
|
|
|
|
|
// fit maximum sectors for the size
|
|
|
|
const u16 maxsectors = len / sectorsize;
|
|
|
|
u16 tracksize = 1;
|
|
|
|
u16 headcount = 1;
|
|
|
|
u16 totsec16next = 0;
|
|
|
|
while (totsec16next <= maxsectors)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2022-03-07 13:08:29 -07:00
|
|
|
totsec16next = tracksize * (headcount + 1) * (headcount + 1);
|
|
|
|
if (totsec16next <= maxsectors)
|
|
|
|
{
|
|
|
|
headcount++;
|
|
|
|
totsec16 = totsec16next;
|
|
|
|
|
|
|
|
tracksize++;
|
|
|
|
totsec16next = tracksize * headcount * headcount;
|
|
|
|
if (totsec16next <= maxsectors)
|
|
|
|
{
|
|
|
|
totsec16 = totsec16next;
|
|
|
|
}
|
|
|
|
}
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
2022-03-07 13:08:29 -07:00
|
|
|
totsec16next = (tracksize + 1) * headcount * headcount;
|
|
|
|
if (totsec16next <= maxsectors)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2022-03-07 13:08:29 -07:00
|
|
|
tracksize++;
|
|
|
|
totsec16 = totsec16next;
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|
|
|
|
|
2022-03-07 13:08:29 -07:00
|
|
|
maxfiles = len < 0x8C000 ? 0x20 : 0x200;
|
|
|
|
clustersize = (totsec16 > (8 << 10)) ? 8 : (totsec16 > (1 << 10) ? 4 : 1);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2022-03-07 13:08:29 -07:00
|
|
|
#define ALIGN(v, a) (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))
|
|
|
|
u16 totalclusters = ALIGN(totsec16, clustersize) / clustersize;
|
|
|
|
u32 fatbytes = (ALIGN(totalclusters, 2) / 2) * 3; // 2 sectors -> 3 byte
|
|
|
|
fatsz16 = ALIGN(fatbytes, sectorsize) / sectorsize;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
|
|
|
u32 nwrite;
|
|
|
|
|
|
|
|
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Error, "CreateSaveFile: failed to create file (%d)\n", res);
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8* data = new u8[len];
|
|
|
|
memset(data, 0, len);
|
|
|
|
|
|
|
|
// create FAT header
|
|
|
|
data[0x000] = 0xE9;
|
|
|
|
memcpy(&data[0x003], "MSWIN4.1", 8);
|
2022-03-07 13:08:29 -07:00
|
|
|
*(u16*)&data[0x00B] = sectorsize; // bytes per sector
|
|
|
|
data[0x00D] = clustersize;
|
2021-08-24 09:46:20 -06:00
|
|
|
*(u16*)&data[0x00E] = 1; // reserved sectors
|
|
|
|
data[0x010] = 2; // num FATs
|
2022-03-07 13:08:29 -07:00
|
|
|
*(u16*)&data[0x011] = maxfiles;
|
2021-08-24 09:46:20 -06:00
|
|
|
*(u16*)&data[0x013] = totsec16;
|
|
|
|
data[0x015] = 0xF8;
|
|
|
|
*(u16*)&data[0x016] = fatsz16;
|
2022-03-07 13:08:29 -07:00
|
|
|
*(u16*)&data[0x018] = tracksize;
|
|
|
|
*(u16*)&data[0x01A] = headcount;
|
|
|
|
data[0x024] = 0x05;
|
2021-08-24 09:46:20 -06:00
|
|
|
data[0x026] = 0x29;
|
2022-03-07 13:08:29 -07:00
|
|
|
*(u32*)&data[0x027] = 0x12345678;
|
2021-08-24 09:46:20 -06:00
|
|
|
memcpy(&data[0x02B], "VOLUMELABEL", 11);
|
|
|
|
memcpy(&data[0x036], "FAT12 ", 8);
|
|
|
|
*(u16*)&data[0x1FE] = 0xAA55;
|
|
|
|
|
|
|
|
f_write(&file, data, len, &nwrite);
|
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
delete[] data;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::InitTitleFileStructure(const NDSHeader& header, const DSi_TMD::TitleMetadata& tmd, bool readonly)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
2023-07-08 14:17:30 -06:00
|
|
|
u32 titleid0 = tmd.GetCategory();
|
|
|
|
u32 titleid1 = tmd.GetID();
|
2021-08-24 09:46:20 -06:00
|
|
|
FRESULT res;
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_DIR ticketdir;
|
|
|
|
FF_FILINFO info;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
char fname[128];
|
2021-10-28 10:47:13 -06:00
|
|
|
FF_FIL file;
|
2021-08-24 09:46:20 -06:00
|
|
|
u32 nwrite;
|
|
|
|
|
|
|
|
// ticket
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/ticket/%08x", titleid0);
|
2021-12-15 22:33:32 -07:00
|
|
|
f_mkdir(fname);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/ticket/%08x/%08x.tik", titleid0, titleid1);
|
2023-07-08 14:17:30 -06:00
|
|
|
if (!CreateTicket(fname, tmd.GetCategoryNoByteswap(), tmd.GetIDNoByteswap(), header.ROMVersion))
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (readonly) f_chmod(fname, AM_RDO, AM_RDO);
|
|
|
|
|
|
|
|
// folder
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x", titleid0);
|
2021-12-17 18:56:27 -07:00
|
|
|
f_mkdir(fname);
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x", titleid0, titleid1);
|
2021-08-24 09:46:20 -06:00
|
|
|
f_mkdir(fname);
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/content", titleid0, titleid1);
|
2021-08-24 09:46:20 -06:00
|
|
|
f_mkdir(fname);
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data", titleid0, titleid1);
|
2021-08-24 09:46:20 -06:00
|
|
|
f_mkdir(fname);
|
|
|
|
|
|
|
|
// data
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/public.sav", titleid0, titleid1);
|
2023-07-08 14:17:30 -06:00
|
|
|
if (!CreateSaveFile(fname, header.DSiPublicSavSize))
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/private.sav", titleid0, titleid1);
|
2023-07-08 14:17:30 -06:00
|
|
|
if (!CreateSaveFile(fname, header.DSiPrivateSavSize))
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
|
2023-07-08 14:17:30 -06:00
|
|
|
if (header.AppFlags & 0x04)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
// custom banner file
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/banner.sav", titleid0, titleid1);
|
2021-08-24 09:46:20 -06:00
|
|
|
res = f_open(&file, fname, FA_CREATE_ALWAYS | FA_WRITE);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Error, "ImportTitle: failed to create banner.sav (%d)\n", res);
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 bannersav[0x4000];
|
2023-07-08 14:17:30 -06:00
|
|
|
memset(bannersav, 0, sizeof(bannersav));
|
|
|
|
f_write(&file, bannersav, sizeof(bannersav), &nwrite);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TMD
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/content/title.tmd", titleid0, titleid1);
|
2021-08-24 09:46:20 -06:00
|
|
|
res = f_open(&file, fname, FA_CREATE_ALWAYS | FA_WRITE);
|
|
|
|
if (res != FR_OK)
|
|
|
|
{
|
2023-03-23 11:04:38 -06:00
|
|
|
Log(LogLevel::Error, "ImportTitle: failed to create TMD (%d)\n", res);
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-08 14:17:30 -06:00
|
|
|
f_write(&file, &tmd, sizeof(DSi_TMD::TitleMetadata), &nwrite);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
f_close(&file);
|
|
|
|
|
|
|
|
if (readonly) f_chmod(fname, AM_RDO, AM_RDO);
|
|
|
|
|
2023-07-08 14:17:30 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ImportTitle(const char* appfile, const DSi_TMD::TitleMetadata& tmd, bool readonly)
|
2023-07-08 14:17:30 -06:00
|
|
|
{
|
|
|
|
NDSHeader header {};
|
|
|
|
{
|
2023-10-24 15:28:14 -06:00
|
|
|
Platform::FileHandle* f = OpenLocalFile(appfile, FileMode::Read);
|
2023-07-08 14:17:30 -06:00
|
|
|
if (!f) return false;
|
2023-10-24 15:28:14 -06:00
|
|
|
FileRead(&header, sizeof(header), 1, f);
|
|
|
|
CloseFile(f);
|
2023-07-08 14:17:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
u32 version = tmd.Contents.GetVersion();
|
|
|
|
Log(LogLevel::Info, ".app version: %08x\n", version);
|
|
|
|
|
|
|
|
u32 titleid0 = tmd.GetCategory();
|
|
|
|
u32 titleid1 = tmd.GetID();
|
|
|
|
Log(LogLevel::Info, "Title ID: %08x/%08x\n", titleid0, titleid1);
|
|
|
|
|
|
|
|
if (!InitTitleFileStructure(header, tmd, readonly))
|
|
|
|
{
|
|
|
|
Log(LogLevel::Error, "ImportTitle: failed to initialize file structure for imported title\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-08-24 09:46:20 -06:00
|
|
|
// executable
|
|
|
|
|
2023-07-08 14:17:30 -06:00
|
|
|
char fname[128];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/content/%08x.app", titleid0, titleid1, version);
|
2021-08-24 09:46:20 -06:00
|
|
|
if (!ImportFile(fname, appfile))
|
|
|
|
{
|
2023-07-08 14:17:30 -06:00
|
|
|
Log(LogLevel::Error, "ImportTitle: failed to create executable\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (readonly) f_chmod(fname, AM_RDO, AM_RDO);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ImportTitle(const u8* app, size_t appLength, const DSi_TMD::TitleMetadata& tmd, bool readonly)
|
2023-07-08 14:17:30 -06:00
|
|
|
{
|
|
|
|
if (!app || appLength < sizeof(NDSHeader))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
NDSHeader header {};
|
|
|
|
memcpy(&header, app, sizeof(header));
|
|
|
|
|
|
|
|
u32 version = tmd.Contents.GetVersion();
|
|
|
|
Log(LogLevel::Info, ".app version: %08x\n", version);
|
|
|
|
|
|
|
|
u32 titleid0 = tmd.GetCategory();
|
|
|
|
u32 titleid1 = tmd.GetID();
|
|
|
|
Log(LogLevel::Info, "Title ID: %08x/%08x\n", titleid0, titleid1);
|
|
|
|
|
|
|
|
if (!InitTitleFileStructure(header, tmd, readonly))
|
|
|
|
{
|
|
|
|
Log(LogLevel::Error, "ImportTitle: failed to initialize file structure for imported title\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// executable
|
|
|
|
|
|
|
|
char fname[128];
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/content/%08x.app", titleid0, titleid1, version);
|
2023-07-08 14:17:30 -06:00
|
|
|
if (!ImportFile(fname, app, appLength))
|
|
|
|
{
|
|
|
|
Log(LogLevel::Error, "ImportTitle: failed to create executable\n");
|
2021-08-24 09:46:20 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (readonly) f_chmod(fname, AM_RDO, AM_RDO);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
void NANDMount::DeleteTitle(u32 category, u32 titleid)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
char fname[128];
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/ticket/%08x/%08x.tik", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
RemoveFile(fname);
|
|
|
|
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
RemoveDir(fname);
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
u32 NANDMount::GetTitleDataMask(u32 category, u32 titleid)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
u32 version;
|
2021-08-26 10:59:07 -06:00
|
|
|
NDSHeader header;
|
2021-08-24 09:46:20 -06:00
|
|
|
|
2021-08-26 10:59:07 -06:00
|
|
|
GetTitleInfo(category, titleid, version, &header, nullptr);
|
2021-08-24 09:46:20 -06:00
|
|
|
if (version == 0xFFFFFFFF)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
u32 ret = 0;
|
2021-08-26 10:59:07 -06:00
|
|
|
if (header.DSiPublicSavSize != 0) ret |= (1 << TitleData_PublicSav);
|
|
|
|
if (header.DSiPrivateSavSize != 0) ret |= (1 << TitleData_PrivateSav);
|
|
|
|
if (header.AppFlags & 0x04) ret |= (1 << TitleData_BannerSav);
|
2021-08-24 09:46:20 -06:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ImportTitleData(u32 category, u32 titleid, int type, const char* file)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
char fname[128];
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case TitleData_PublicSav:
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/public.sav", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TitleData_PrivateSav:
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/private.sav", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TitleData_BannerSav:
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/banner.sav", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoveFile(fname);
|
|
|
|
return ImportFile(fname, file);
|
|
|
|
}
|
|
|
|
|
2023-10-11 09:20:05 -06:00
|
|
|
bool NANDMount::ExportTitleData(u32 category, u32 titleid, int type, const char* file)
|
2021-08-24 09:46:20 -06:00
|
|
|
{
|
|
|
|
char fname[128];
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case TitleData_PublicSav:
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/public.sav", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TitleData_PrivateSav:
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/private.sav", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TitleData_BannerSav:
|
2023-10-01 13:58:56 -06:00
|
|
|
snprintf(fname, sizeof(fname), "0:/title/%08x/%08x/data/banner.sav", category, titleid);
|
2021-08-24 09:46:20 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ExportFile(fname, file);
|
|
|
|
}
|
|
|
|
|
2023-10-02 09:54:17 -06:00
|
|
|
void DSiFirmwareSystemSettings::UpdateHash()
|
|
|
|
{
|
|
|
|
SHA1_CTX sha;
|
|
|
|
SHA1Init(&sha);
|
|
|
|
SHA1Update(&sha, &Bytes[0x88], 0x128);
|
|
|
|
SHA1Final(Hash.data(), &sha);
|
|
|
|
}
|
|
|
|
|
2021-08-24 09:46:20 -06:00
|
|
|
}
|