Support emulating R4 Revolution/M3DS Simply cartridges. (#1854)
* Support emulating R4 Revolution/M3DS Simply cartridges. * NDSCartR4: Write state information to savestate file. * NDSCart: Use strncmp instead of strcmp for R4 detection. * NDSCartR4: stylistic improvements * NDSCartR4: rudimentary Ace3DS support * NDSCartR4: fix boot when firmware enabled * NDSCartR4: Fix for namespace changes --------- Co-authored-by: RSDuck <RSDuck@users.noreply.github.com>
This commit is contained in:
parent
9bfc9c08ff
commit
6f47c9ed4c
|
@ -38,6 +38,7 @@ add_library(core STATIC
|
||||||
melonDLDI.h
|
melonDLDI.h
|
||||||
NDS.cpp
|
NDS.cpp
|
||||||
NDSCart.cpp
|
NDSCart.cpp
|
||||||
|
NDSCartR4.cpp
|
||||||
Platform.h
|
Platform.h
|
||||||
ROMList.h
|
ROMList.h
|
||||||
ROMList.cpp
|
ROMList.cpp
|
||||||
|
|
|
@ -144,6 +144,48 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
|
||||||
return nwrite==len;
|
return nwrite==len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data)
|
||||||
|
{
|
||||||
|
if (!File) return false;
|
||||||
|
if (FF_File) return false;
|
||||||
|
|
||||||
|
FF_File = File;
|
||||||
|
FF_FileSize = FileSize;
|
||||||
|
ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9));
|
||||||
|
|
||||||
|
FRESULT res;
|
||||||
|
FATFS fs;
|
||||||
|
|
||||||
|
res = f_mount(&fs, "0:", 1);
|
||||||
|
if (res != FR_OK)
|
||||||
|
{
|
||||||
|
ff_disk_close();
|
||||||
|
FF_File = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string prefixedPath("0:/");
|
||||||
|
prefixedPath += path;
|
||||||
|
FF_FIL file;
|
||||||
|
res = f_open(&file, prefixedPath.c_str(), FA_READ);
|
||||||
|
if (res != FR_OK)
|
||||||
|
{
|
||||||
|
f_unmount("0:");
|
||||||
|
ff_disk_close();
|
||||||
|
FF_File = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 nread;
|
||||||
|
f_lseek(&file, start);
|
||||||
|
f_read(&file, data, len, &nread);
|
||||||
|
f_close(&file);
|
||||||
|
|
||||||
|
f_unmount("0:");
|
||||||
|
ff_disk_close();
|
||||||
|
FF_File = nullptr;
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
u32 FATStorage::ReadSectors(u32 start, u32 num, u8* data) const
|
u32 FATStorage::ReadSectors(u32 start, u32 num, u8* data) const
|
||||||
{
|
{
|
||||||
|
@ -156,6 +198,11 @@ u32 FATStorage::WriteSectors(u32 start, u32 num, const u8* data)
|
||||||
return WriteSectorsInternal(File, FileSize, start, num, data);
|
return WriteSectorsInternal(File, FileSize, start, num, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 FATStorage::GetSectorCount() const
|
||||||
|
{
|
||||||
|
return FileSize / 0x200;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
FileHandle* FATStorage::FF_File;
|
FileHandle* FATStorage::FF_File;
|
||||||
u64 FATStorage::FF_FileSize;
|
u64 FATStorage::FF_FileSize;
|
||||||
|
|
|
@ -57,10 +57,13 @@ public:
|
||||||
~FATStorage();
|
~FATStorage();
|
||||||
|
|
||||||
bool InjectFile(const std::string& path, u8* data, u32 len);
|
bool InjectFile(const std::string& path, u8* data, u32 len);
|
||||||
|
u32 ReadFile(const std::string& path, u32 start, u32 len, u8* data);
|
||||||
|
|
||||||
u32 ReadSectors(u32 start, u32 num, u8* data) const;
|
u32 ReadSectors(u32 start, u32 num, u8* data) const;
|
||||||
u32 WriteSectors(u32 start, u32 num, const u8* data);
|
u32 WriteSectors(u32 start, u32 num, const u8* data);
|
||||||
|
|
||||||
[[nodiscard]] bool IsReadOnly() const noexcept { return ReadOnly; }
|
[[nodiscard]] bool IsReadOnly() const noexcept { return ReadOnly; }
|
||||||
|
u64 GetSectorCount() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string FilePath;
|
std::string FilePath;
|
||||||
|
|
230
src/NDSCart.cpp
230
src/NDSCart.cpp
|
@ -1145,12 +1145,12 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
|
|
||||||
CartHomebrew(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
|
CartSD::CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
|
||||||
|
CartSD(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard))
|
||||||
|
{}
|
||||||
|
|
||||||
|
CartSD::CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
|
||||||
CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew),
|
CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew),
|
||||||
SD(std::move(sdcard))
|
SD(std::move(sdcard))
|
||||||
{
|
{
|
||||||
|
@ -1158,112 +1158,11 @@ CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROM
|
||||||
// std::move on optionals usually results in an optional with a moved-from object
|
// std::move on optionals usually results in an optional with a moved-from object
|
||||||
}
|
}
|
||||||
|
|
||||||
CartHomebrew::~CartHomebrew() = default;
|
CartSD::~CartSD() = default;
|
||||||
// The SD card is destroyed by the optional's destructor
|
// The SD card is destroyed by the optional's destructor
|
||||||
|
|
||||||
void CartHomebrew::Reset()
|
|
||||||
{
|
|
||||||
CartCommon::Reset();
|
|
||||||
|
|
||||||
if (SD)
|
void CartSD::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const
|
||||||
{
|
|
||||||
ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), SD->IsReadOnly());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds)
|
|
||||||
{
|
|
||||||
CartCommon::SetupDirectBoot(romname, nds);
|
|
||||||
|
|
||||||
if (SD)
|
|
||||||
{
|
|
||||||
// add the ROM to the SD volume
|
|
||||||
|
|
||||||
if (!SD->InjectFile(romname, ROM.get(), ROMLength))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// setup argv command line
|
|
||||||
|
|
||||||
char argv[512] = {0};
|
|
||||||
int argvlen;
|
|
||||||
|
|
||||||
strncpy(argv, "fat:/", 511);
|
|
||||||
strncat(argv, romname.c_str(), 511);
|
|
||||||
argvlen = strlen(argv);
|
|
||||||
|
|
||||||
const NDSHeader& header = GetHeader();
|
|
||||||
|
|
||||||
u32 argvbase = header.ARM9RAMAddress + header.ARM9Size;
|
|
||||||
argvbase = (argvbase + 0xF) & ~0xF;
|
|
||||||
|
|
||||||
for (u32 i = 0; i <= argvlen; i+=4)
|
|
||||||
nds.ARM9Write32(argvbase+i, *(u32*)&argv[i]);
|
|
||||||
|
|
||||||
nds.ARM9Write32(0x02FFFE70, 0x5F617267);
|
|
||||||
nds.ARM9Write32(0x02FFFE74, argvbase);
|
|
||||||
nds.ARM9Write32(0x02FFFE78, argvlen+1);
|
|
||||||
// The DSi version of ARM9Write32 will be called if nds is really a DSi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len)
|
|
||||||
{
|
|
||||||
if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len);
|
|
||||||
|
|
||||||
switch (cmd[0])
|
|
||||||
{
|
|
||||||
case 0xB7:
|
|
||||||
{
|
|
||||||
u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
|
||||||
memset(data, 0, len);
|
|
||||||
|
|
||||||
if (((addr + len - 1) >> 12) != (addr >> 12))
|
|
||||||
{
|
|
||||||
u32 len1 = 0x1000 - (addr & 0xFFF);
|
|
||||||
ReadROM_B7(addr, len1, data, 0);
|
|
||||||
ReadROM_B7(addr+len1, len-len1, data, len1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ReadROM_B7(addr, len, data, 0);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case 0xC0: // SD read
|
|
||||||
{
|
|
||||||
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
|
||||||
if (SD) SD->ReadSectors(sector, len>>9, data);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case 0xC1: // SD write
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CartHomebrew::ROMCommandFinish(const u8* cmd, u8* data, u32 len)
|
|
||||||
{
|
|
||||||
if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len);
|
|
||||||
|
|
||||||
// TODO: delayed SD writing? like we have for SRAM
|
|
||||||
|
|
||||||
switch (cmd[0])
|
|
||||||
{
|
|
||||||
case 0xC1:
|
|
||||||
{
|
|
||||||
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
|
||||||
if (SD && !SD->IsReadOnly()) SD->WriteSectors(sector, len>>9, data);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return CartCommon::ROMCommandFinish(cmd, data, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const
|
|
||||||
{
|
{
|
||||||
if (patch[0x0D] > binary[dldioffset+0x0F])
|
if (patch[0x0D] > binary[dldioffset+0x0F])
|
||||||
{
|
{
|
||||||
|
@ -1364,7 +1263,7 @@ void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch,
|
||||||
Log(LogLevel::Debug, "applied DLDI patch at %08X\n", dldioffset);
|
Log(LogLevel::Debug, "applied DLDI patch at %08X\n", dldioffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly)
|
void CartSD::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly)
|
||||||
{
|
{
|
||||||
if (*(u32*)&patch[0] != 0xBF8DA5ED ||
|
if (*(u32*)&patch[0] != 0xBF8DA5ED ||
|
||||||
*(u32*)&patch[4] != 0x69684320 ||
|
*(u32*)&patch[4] != 0x69684320 ||
|
||||||
|
@ -1394,7 +1293,7 @@ void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const
|
void CartSD::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const
|
||||||
{
|
{
|
||||||
// TODO: how strict should this be for homebrew?
|
// TODO: how strict should this be for homebrew?
|
||||||
|
|
||||||
|
@ -1403,7 +1302,115 @@ void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const
|
||||||
memcpy(data+offset, ROM.get()+addr, len);
|
memcpy(data+offset, ROM.get()+addr, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
|
||||||
|
CartSD(rom, len, chipid, romparams, std::move(sdcard))
|
||||||
|
{}
|
||||||
|
|
||||||
|
CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
|
||||||
|
CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard))
|
||||||
|
{}
|
||||||
|
|
||||||
|
CartHomebrew::~CartHomebrew() = default;
|
||||||
|
|
||||||
|
void CartHomebrew::Reset()
|
||||||
|
{
|
||||||
|
CartSD::Reset();
|
||||||
|
|
||||||
|
if (SD)
|
||||||
|
ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), SD->IsReadOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds)
|
||||||
|
{
|
||||||
|
CartCommon::SetupDirectBoot(romname, nds);
|
||||||
|
|
||||||
|
if (SD)
|
||||||
|
{
|
||||||
|
// add the ROM to the SD volume
|
||||||
|
|
||||||
|
if (!SD->InjectFile(romname, ROM.get(), ROMLength))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// setup argv command line
|
||||||
|
|
||||||
|
char argv[512] = {0};
|
||||||
|
int argvlen;
|
||||||
|
|
||||||
|
strncpy(argv, "fat:/", 511);
|
||||||
|
strncat(argv, romname.c_str(), 511);
|
||||||
|
argvlen = strlen(argv);
|
||||||
|
|
||||||
|
const NDSHeader& header = GetHeader();
|
||||||
|
|
||||||
|
u32 argvbase = header.ARM9RAMAddress + header.ARM9Size;
|
||||||
|
argvbase = (argvbase + 0xF) & ~0xF;
|
||||||
|
|
||||||
|
for (u32 i = 0; i <= argvlen; i+=4)
|
||||||
|
nds.ARM9Write32(argvbase+i, *(u32*)&argv[i]);
|
||||||
|
|
||||||
|
nds.ARM9Write32(0x02FFFE70, 0x5F617267);
|
||||||
|
nds.ARM9Write32(0x02FFFE74, argvbase);
|
||||||
|
nds.ARM9Write32(0x02FFFE78, argvlen+1);
|
||||||
|
// The DSi version of ARM9Write32 will be called if nds is really a DSi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len)
|
||||||
|
{
|
||||||
|
if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len);
|
||||||
|
|
||||||
|
switch (cmd[0])
|
||||||
|
{
|
||||||
|
case 0xB7:
|
||||||
|
{
|
||||||
|
u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
memset(data, 0, len);
|
||||||
|
|
||||||
|
if (((addr + len - 1) >> 12) != (addr >> 12))
|
||||||
|
{
|
||||||
|
u32 len1 = 0x1000 - (addr & 0xFFF);
|
||||||
|
ReadROM_B7(addr, len1, data, 0);
|
||||||
|
ReadROM_B7(addr+len1, len-len1, data, len1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ReadROM_B7(addr, len, data, 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case 0xC0: // SD read
|
||||||
|
{
|
||||||
|
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
if (SD) SD->ReadSectors(sector, len>>9, data);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case 0xC1: // SD write
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CartHomebrew::ROMCommandFinish(const u8* cmd, u8* data, u32 len)
|
||||||
|
{
|
||||||
|
if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len);
|
||||||
|
|
||||||
|
// TODO: delayed SD writing? like we have for SRAM
|
||||||
|
|
||||||
|
switch (cmd[0])
|
||||||
|
{
|
||||||
|
case 0xC1:
|
||||||
|
{
|
||||||
|
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
if (SD && !SD->IsReadOnly()) SD->WriteSectors(sector, len>>9, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return CartCommon::ROMCommandFinish(cmd, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NDSCartSlot::NDSCartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& rom) noexcept : NDS(nds)
|
NDSCartSlot::NDSCartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& rom) noexcept : NDS(nds)
|
||||||
{
|
{
|
||||||
|
@ -1588,6 +1595,7 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen
|
||||||
dsi = false;
|
dsi = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *gametitle = header.GameTitle;
|
||||||
u32 gamecode = header.GameCodeAsU32();
|
u32 gamecode = header.GameCodeAsU32();
|
||||||
|
|
||||||
u32 arm9base = header.ARM9ROMOffset;
|
u32 arm9base = header.ARM9ROMOffset;
|
||||||
|
@ -1645,6 +1653,8 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen
|
||||||
auto [sram, sramlen] = args ? std::move(*args->SRAM) : std::make_pair(nullptr, 0);
|
auto [sram, sramlen] = args ? std::move(*args->SRAM) : std::make_pair(nullptr, 0);
|
||||||
if (homebrew)
|
if (homebrew)
|
||||||
cart = std::make_unique<CartHomebrew>(std::move(cartrom), cartromsize, cartid, romparams, args ? std::move(args->SDCard) : std::nullopt);
|
cart = std::make_unique<CartHomebrew>(std::move(cartrom), cartromsize, cartid, romparams, args ? std::move(args->SDCard) : std::nullopt);
|
||||||
|
else if (gametitle[0] == 0 && !strncmp("SD/TF-NDS", gametitle + 1, 9) && gamecode == 0x414D5341)
|
||||||
|
cart = std::make_unique<CartR4>(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, args ? std::move(args->SDCard) : std::nullopt);
|
||||||
else if (cartid & 0x08000000)
|
else if (cartid & 0x08000000)
|
||||||
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
|
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
|
||||||
else if (irversion != 0)
|
else if (irversion != 0)
|
||||||
|
|
|
@ -45,6 +45,7 @@ enum CartType
|
||||||
RetailIR = 0x103,
|
RetailIR = 0x103,
|
||||||
RetailBT = 0x104,
|
RetailBT = 0x104,
|
||||||
Homebrew = 0x201,
|
Homebrew = 0x201,
|
||||||
|
UnlicensedR4 = 0x301
|
||||||
};
|
};
|
||||||
|
|
||||||
class NDSCartSlot;
|
class NDSCartSlot;
|
||||||
|
@ -236,19 +237,13 @@ public:
|
||||||
u8 SPIWrite(u8 val, u32 pos, bool last) override;
|
u8 SPIWrite(u8 val, u32 pos, bool last) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI)
|
// CartSD -- any 'cart' with an SD card slot
|
||||||
class CartHomebrew : public CartCommon
|
class CartSD : public CartCommon
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
|
CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
|
||||||
CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
|
CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
|
||||||
~CartHomebrew() override;
|
~CartSD() override;
|
||||||
|
|
||||||
void Reset() override;
|
|
||||||
void SetupDirectBoot(const std::string& romname, NDS& nds) override;
|
|
||||||
|
|
||||||
int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override;
|
|
||||||
void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override;
|
|
||||||
|
|
||||||
[[nodiscard]] const std::optional<FATStorage>& GetSDCard() const noexcept { return SD; }
|
[[nodiscard]] const std::optional<FATStorage>& GetSDCard() const noexcept { return SD; }
|
||||||
void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); }
|
void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); }
|
||||||
|
@ -260,7 +255,7 @@ public:
|
||||||
// it just leaves behind an optional with a moved-from value
|
// it just leaves behind an optional with a moved-from value
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const;
|
void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const;
|
||||||
void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly);
|
void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly);
|
||||||
void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const;
|
void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const;
|
||||||
|
@ -268,6 +263,74 @@ private:
|
||||||
std::optional<FATStorage> SD {};
|
std::optional<FATStorage> SD {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI)
|
||||||
|
class CartHomebrew : public CartSD
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
|
||||||
|
CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
|
||||||
|
~CartHomebrew() override;
|
||||||
|
|
||||||
|
void Reset() override;
|
||||||
|
void SetupDirectBoot(const std::string& romname, NDS& nds) override;
|
||||||
|
|
||||||
|
int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override;
|
||||||
|
void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CartR4 -- unlicensed R4 'cart' (NDSCartR4.cpp)
|
||||||
|
enum CartR4Type
|
||||||
|
{
|
||||||
|
/* non-SDHC carts */
|
||||||
|
CartR4TypeM3Simply = 0,
|
||||||
|
CartR4TypeR4 = 1,
|
||||||
|
/* SDHC carts */
|
||||||
|
CartR4TypeAce3DS = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CartR4Language
|
||||||
|
{
|
||||||
|
CartR4LanguageJapanese = (7 << 3) | 1,
|
||||||
|
CartR4LanguageEnglish = (7 << 3) | 2,
|
||||||
|
CartR4LanguageFrench = (2 << 3) | 2,
|
||||||
|
CartR4LanguageKorean = (4 << 3) | 2,
|
||||||
|
CartR4LanguageSimplifiedChinese = (6 << 3) | 3,
|
||||||
|
CartR4LanguageTraditionalChinese = (7 << 3) | 3
|
||||||
|
};
|
||||||
|
|
||||||
|
class CartR4 : public CartSD
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CartR4(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage,
|
||||||
|
std::optional<FATStorage>&& sdcard = std::nullopt);
|
||||||
|
~CartR4() override;
|
||||||
|
|
||||||
|
void Reset() override;
|
||||||
|
|
||||||
|
void DoSavestate(Savestate* file) override;
|
||||||
|
|
||||||
|
int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override;
|
||||||
|
void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline u32 GetAdjustedSector(u32 sector) const
|
||||||
|
{
|
||||||
|
return R4CartType >= CartR4TypeAce3DS ? sector : sector >> 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 GetEncryptionKey(u16 sector);
|
||||||
|
void ReadSDToBuffer(u32 sector, bool rom);
|
||||||
|
u64 SDFATEntrySectorGet(u32 entry, u32 addr);
|
||||||
|
|
||||||
|
s32 EncryptionKey;
|
||||||
|
u32 FATEntryOffset[2];
|
||||||
|
u8 Buffer[512];
|
||||||
|
u8 InitStatus;
|
||||||
|
CartR4Type R4CartType;
|
||||||
|
CartR4Language CartLanguage;
|
||||||
|
bool BufferInitialized;
|
||||||
|
};
|
||||||
|
|
||||||
class NDSCartSlot
|
class NDSCartSlot
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -0,0 +1,371 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "NDS.h"
|
||||||
|
#include "DSi.h"
|
||||||
|
#include "NDSCart.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
using Platform::Log;
|
||||||
|
using Platform::LogLevel;
|
||||||
|
|
||||||
|
namespace NDSCart
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
Original algorithm discovered by yasu, 2007
|
||||||
|
|
||||||
|
http://hp.vector.co.jp/authors/VA013928/
|
||||||
|
http://www.usay.jp/
|
||||||
|
http://www.yasu.nu/
|
||||||
|
*/
|
||||||
|
static void DecryptR4Sector(u8* dest, u8* src, u16 key1)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 512; i++)
|
||||||
|
{
|
||||||
|
// Derive key2 from key1.
|
||||||
|
u8 key2 = ((key1 >> 7) & 0x80)
|
||||||
|
| ((key1 >> 6) & 0x60)
|
||||||
|
| ((key1 >> 5) & 0x10)
|
||||||
|
| ((key1 >> 4) & 0x0C)
|
||||||
|
| (key1 & 0x03);
|
||||||
|
|
||||||
|
// Decrypt byte.
|
||||||
|
dest[i] = src[i] ^ key2;
|
||||||
|
|
||||||
|
// Derive next key1 from key2.
|
||||||
|
u16 tmp = ((src[i] << 8) ^ key1);
|
||||||
|
u16 tmpXor = 0;
|
||||||
|
for (int ii = 0; ii < 16; ii++)
|
||||||
|
tmpXor ^= (tmp >> ii);
|
||||||
|
|
||||||
|
u16 newKey1 = 0;
|
||||||
|
newKey1 |= ((tmpXor & 0x80) | (tmp & 0x7C)) << 8;
|
||||||
|
newKey1 |= ((tmp ^ (tmpXor >> 14)) << 8) & 0x0300;
|
||||||
|
newKey1 |= (((tmp >> 1) ^ tmp) >> 6) & 0xFC;
|
||||||
|
newKey1 |= ((tmp ^ (tmpXor >> 1)) >> 8) & 0x03;
|
||||||
|
|
||||||
|
key1 = newKey1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CartR4::CartR4(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage,
|
||||||
|
std::optional<FATStorage>&& sdcard)
|
||||||
|
: CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard))
|
||||||
|
{
|
||||||
|
InitStatus = 0;
|
||||||
|
R4CartType = ctype;
|
||||||
|
CartLanguage = clanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartR4::~CartR4()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CartR4::Reset()
|
||||||
|
{
|
||||||
|
CartSD::Reset();
|
||||||
|
|
||||||
|
BufferInitialized = false;
|
||||||
|
EncryptionKey = -1;
|
||||||
|
FATEntryOffset[0] = 0xFFFFFFFF;
|
||||||
|
FATEntryOffset[1] = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
if (!SD)
|
||||||
|
InitStatus = 1;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u8 buffer[512];
|
||||||
|
if (!SD->ReadFile("_DS_MENU.DAT", 0, 512, buffer))
|
||||||
|
InitStatus = 3;
|
||||||
|
else
|
||||||
|
InitStatus = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CartR4::DoSavestate(Savestate* file)
|
||||||
|
{
|
||||||
|
CartCommon::DoSavestate(file);
|
||||||
|
|
||||||
|
file->Var32(&FATEntryOffset[0]);
|
||||||
|
file->Var32(&FATEntryOffset[1]);
|
||||||
|
file->VarArray(Buffer, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Ace3DS/clone behavior is only partially verified.
|
||||||
|
int CartR4::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len)
|
||||||
|
{
|
||||||
|
if (CmdEncMode != 2)
|
||||||
|
return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len);
|
||||||
|
|
||||||
|
switch (cmd[0])
|
||||||
|
{
|
||||||
|
case 0xB0: /* Get card information */
|
||||||
|
{
|
||||||
|
u32 info = 0x75A00000 | (((R4CartType >= 1 ? 4 : 0) | CartLanguage) << 3) | InitStatus;
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = info;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xB4: /* FAT entry */
|
||||||
|
{
|
||||||
|
u8 entryBuffer[512];
|
||||||
|
u32 sector = ((cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]) & (~0x1F);
|
||||||
|
// set FAT entry offset to the starting cluster, to gain a bit of speed
|
||||||
|
SD->ReadSectors(sector >> 9, 1, entryBuffer);
|
||||||
|
u16 fileEntryOffset = sector & 0x1FF;
|
||||||
|
u32 clusterStart = (entryBuffer[fileEntryOffset + 27] << 8)
|
||||||
|
| entryBuffer[fileEntryOffset + 26]
|
||||||
|
| (entryBuffer[fileEntryOffset + 21] << 24)
|
||||||
|
| (entryBuffer[fileEntryOffset + 20] << 16);
|
||||||
|
FATEntryOffset[cmd[4] & 0x01] = clusterStart;
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xB8: /* ? Get chip ID ? */
|
||||||
|
{
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = ChipID;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xB2: /* Save read request */
|
||||||
|
case 0xB6: /* ROM read request */
|
||||||
|
{
|
||||||
|
u32 sector = ((cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]);
|
||||||
|
ReadSDToBuffer(sector, cmd[0] == 0xB6);
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xB9: /* SD read request */
|
||||||
|
{
|
||||||
|
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
if (SD)
|
||||||
|
SD->ReadSectors(GetAdjustedSector(sector), 1, Buffer);
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xBB: /* SD write start */
|
||||||
|
case 0xBD: /* Save write start */
|
||||||
|
return 1;
|
||||||
|
case 0xBC: /* SD write status */
|
||||||
|
case 0xBE: /* Save write status */
|
||||||
|
{
|
||||||
|
if (R4CartType == CartR4TypeAce3DS && cmd[0] == 0xBC)
|
||||||
|
{
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
checksum ^= cmd[i];
|
||||||
|
if (checksum != cmd[7])
|
||||||
|
Log(LogLevel::Warn, "R4: invalid 0xBC command checksum (%d != %d)", cmd[7], checksum);
|
||||||
|
}
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xB7: /* ROM read data */
|
||||||
|
{
|
||||||
|
/* If the buffer has not been initialized yet, emulate ROM. */
|
||||||
|
/* TODO: When does the R4 do this exactly? */
|
||||||
|
if (!BufferInitialized)
|
||||||
|
{
|
||||||
|
u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
memcpy(data, &ROM[addr & (ROMLength-1)], len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Otherwise, fall through. */
|
||||||
|
}
|
||||||
|
case 0xB3: /* Save read data */
|
||||||
|
case 0xBA: /* SD read data */
|
||||||
|
{
|
||||||
|
// TODO: Do these use separate buffers?
|
||||||
|
for (u32 pos = 0; pos < len; pos++)
|
||||||
|
data[pos] = Buffer[pos & 0x1FF];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xBF: /* ROM read decrypted data */
|
||||||
|
{
|
||||||
|
// TODO: Is decryption done using the sector from 0xBF or 0xB6?
|
||||||
|
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
if (len >= 512)
|
||||||
|
DecryptR4Sector(data, Buffer, GetEncryptionKey(sector >> 9));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Log(LogLevel::Warn, "R4: unknown command %02X %02X %02X %02X %02X %02X %02X %02X (%d)\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], len);
|
||||||
|
for (u32 pos = 0; pos < len; pos += 4)
|
||||||
|
*(u32*)&data[pos] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CartR4::ROMCommandFinish(const u8* cmd, u8* data, u32 len)
|
||||||
|
{
|
||||||
|
if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len);
|
||||||
|
|
||||||
|
switch (cmd[0])
|
||||||
|
{
|
||||||
|
case 0xBB: /* SD write start */
|
||||||
|
{
|
||||||
|
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
|
||||||
|
// The official R4 firmware sends a superfluous write to card
|
||||||
|
// (sector 0, byte 1) on boot. TODO: Is this correct?
|
||||||
|
if (GetAdjustedSector(sector) != sector && (sector & 0x1FF)) break;
|
||||||
|
|
||||||
|
if (SD && !SD->IsReadOnly())
|
||||||
|
SD->WriteSectors(GetAdjustedSector(sector), 1, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xBD: /* Save write start */
|
||||||
|
{
|
||||||
|
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
|
||||||
|
|
||||||
|
if (sector & 0x1FF) break;
|
||||||
|
|
||||||
|
if (SD && !SD->IsReadOnly())
|
||||||
|
SD->WriteSectors(
|
||||||
|
SDFATEntrySectorGet(FATEntryOffset[1], sector) >> 9,
|
||||||
|
1, data
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 CartR4::GetEncryptionKey(u16 sector)
|
||||||
|
{
|
||||||
|
if (EncryptionKey == -1)
|
||||||
|
{
|
||||||
|
u8 encryptedBuffer[512];
|
||||||
|
u8 decryptedBuffer[512];
|
||||||
|
SD->ReadFile("_DS_MENU.DAT", 0, 512, encryptedBuffer);
|
||||||
|
for (u32 key = 0; key < 0x10000; key++)
|
||||||
|
{
|
||||||
|
DecryptR4Sector(decryptedBuffer, encryptedBuffer, key);
|
||||||
|
if (decryptedBuffer[12] == '#' && decryptedBuffer[13] == '#'
|
||||||
|
&& decryptedBuffer[14] == '#' && decryptedBuffer[15] == '#')
|
||||||
|
{
|
||||||
|
EncryptionKey = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EncryptionKey != -1)
|
||||||
|
{
|
||||||
|
Log(LogLevel::Warn, "R4: found cartridge key = %04X\n", EncryptionKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EncryptionKey = -2;
|
||||||
|
Log(LogLevel::Warn, "R4: could not find cartridge key\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EncryptionKey ^ sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CartR4::ReadSDToBuffer(u32 sector, bool rom)
|
||||||
|
{
|
||||||
|
if (SD)
|
||||||
|
{
|
||||||
|
if (rom && FATEntryOffset[0] == 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
// On first boot, read from _DS_MENU.DAT.
|
||||||
|
SD->ReadFile("_DS_MENU.DAT", sector & ~0x1FF, 512, Buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default mode.
|
||||||
|
SD->ReadSectors(
|
||||||
|
SDFATEntrySectorGet(FATEntryOffset[rom ? 0 : 1], sector) >> 9,
|
||||||
|
1, Buffer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
BufferInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CartR4::SDFATEntrySectorGet(u32 entry, u32 addr)
|
||||||
|
{
|
||||||
|
u8 buffer[512];
|
||||||
|
u32 bufferSector = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
// Parse FAT header.
|
||||||
|
SD->ReadSectors(0, 1, buffer);
|
||||||
|
u16 bytesPerSector = (buffer[12] << 8) | buffer[11];
|
||||||
|
u8 sectorsPerCluster = buffer[13];
|
||||||
|
u16 firstFatSector = (buffer[15] << 8) | buffer[14];
|
||||||
|
u8 fatTableCount = buffer[16];
|
||||||
|
u32 clustersTotal = SD->GetSectorCount() / sectorsPerCluster;
|
||||||
|
bool isFat32 = clustersTotal >= 65526;
|
||||||
|
|
||||||
|
u32 fatTableSize = (buffer[23] << 8) | buffer[22];
|
||||||
|
if (fatTableSize == 0 && isFat32)
|
||||||
|
fatTableSize = (buffer[39] << 24) | (buffer[38] << 16) | (buffer[37] << 8) | buffer[36];
|
||||||
|
u32 bytesPerCluster = bytesPerSector * sectorsPerCluster;
|
||||||
|
u32 rootDirSectors = 0;
|
||||||
|
if (!isFat32) {
|
||||||
|
u32 rootDirEntries = (buffer[18] << 8) | buffer[17];
|
||||||
|
rootDirSectors = ((rootDirEntries * 32) + (bytesPerSector - 1)) / bytesPerSector;
|
||||||
|
}
|
||||||
|
u32 firstDataSector = firstFatSector + fatTableCount * fatTableSize + rootDirSectors;
|
||||||
|
|
||||||
|
// Parse file entry (done when processing command 0xB4).
|
||||||
|
u32 clusterStart = entry;
|
||||||
|
|
||||||
|
// Parse cluster table.
|
||||||
|
u32 currentCluster = clusterStart;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
currentCluster &= isFat32 ? 0x0FFFFFFF : 0xFFFF;
|
||||||
|
if (addr < bytesPerCluster)
|
||||||
|
{
|
||||||
|
// Read from this cluster.
|
||||||
|
return (u64) (firstDataSector + ((currentCluster - 2) * sectorsPerCluster)) * bytesPerSector + addr;
|
||||||
|
}
|
||||||
|
else if (currentCluster >= 2 && currentCluster <= (isFat32 ? 0x0FFFFFF6 : 0xFFF6))
|
||||||
|
{
|
||||||
|
// Follow into next cluster.
|
||||||
|
u32 nextClusterOffset = firstFatSector * bytesPerSector + currentCluster * (isFat32 ? 4 : 2);
|
||||||
|
u32 nextClusterTableSector = nextClusterOffset >> 9;
|
||||||
|
if (bufferSector != nextClusterTableSector)
|
||||||
|
{
|
||||||
|
SD->ReadSectors(nextClusterTableSector, 1, buffer);
|
||||||
|
bufferSector = nextClusterTableSector;
|
||||||
|
}
|
||||||
|
nextClusterOffset &= 0x1FF;
|
||||||
|
currentCluster = (buffer[nextClusterOffset + 1] << 8) | buffer[nextClusterOffset];
|
||||||
|
if (isFat32)
|
||||||
|
currentCluster |= (buffer[nextClusterOffset + 3] << 24) | (buffer[nextClusterOffset + 2] << 16);
|
||||||
|
addr -= bytesPerCluster;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// End of cluster table.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue