mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 13:27:41 -07:00
Refactor the core's handling of firmware and BIOS images to rely less on the file system (#1826)
* Introduce firmware-related structs * Fix some indents * Move the generated firmware identifier to a constant * Document the WifiAccessPoint constructors * Add some constants * Remove a stray comment * Implement Firmware::UserData * Add Firmware::Mask * Document Firmware::Buffer * Add a Firmware constructor that uses a FileHandle * Set the default username in UserData * Update the UserData checksum * Forgot to include Platform.h * Remove some redundant assignments in the default Firmware constructor * const-ify CRC16 * Replace the plain Firmware buffer with a Firmware object - Remove some functions that were reimplemented in the Firmware constructors * Fix some crashes due to undefined behavior * Fix the user data initialization - Initialize both user data objects to default - Set both user data objects to the same touch screen calibration * Follow the DS logic in determining which user data section is current * Remove an unneeded include * Remove another unneeded include * Initialize FirmwareMask in Firmware::Firmware * Use the DEFAULT_SSID constant * Add SPI_Firmware::InstallFirmware and SPI_Firmware::RemoveFirmware * Move a logging call to after the file is written * Add a SaveManager for the firmware * Touch up the SPI_Firmware::Firmware declaration * Move all firmware loading and customization to the frontend * Call Platform::WriteFirmware when it's time to write the firmware back to disk * Fix some missing stuff * Remove the Get* functions from SPI_Firmware in favor of GetFirmware() * Implement SPI_Firmware::DeInit in terms of RemoveFirmware * Add Firmware::UpdateChecksums * Fix an incorrect length * Update all checksums in the firmware after setting touch screen calibration data * Use the Firmware object's Position methods * Remove register fields from the Firmware object * Install the firmware before seeing if direct boot is necessary * Install the firmware before calling NDS::Reset in LoadROM * Slight cleanup in ROMManager * Fix the default access point name * Shorten the various getters in Firmware * Add qualifiers for certain uses of firmware types - GCC can get picky if -fpermissive isn't defined * Add an InstallFirmware overload that takes a unique_ptr * Log when firmware is added or removed * Don't clear the firmware in SPI_Firmware::Init - The original code didn't, it just set the pointer to nullptr * Fix a typo * Write back the full firmware if it's not generated * Move the FreeBIOS to an external file * Load wfcsettings.bin into the correct part of the generated firmware blob * Load BIOS files in the frontend, not in the core * Fix logging the firmware ID * Add some utility functions * Mark Firmware's constructors as explicit * Remove obsolete ConfigEntry values * Include <locale> explicitly in ROMManager * Fix up some includes * Add Firmware::IsBootable() * Add a newline to a log entry - Whoops * Log the number of bytes written out in SaveManager * Mark FirmwareHeader's constructor as explicit * Clean up GenerateDefaultFirmware and LoadFirmwareFromFile - Now they return a pair instead of two by-ref values * Refactor SaveManager a little bit - Manage its buffers as unique_ptrs to mitigate leaks - Reallocate the internal buffer if SetPath is asked to reload the file (and the new length is different) * Remove some stray parens * Fix some firmware-related bugs I introduced - Firmware settings are now properly saved to disk (beforehand I misunderstood when the firmware blob was written) - Firmware is no longer overwritten by contents of wfcsettings.bin * Slight cleanup
This commit is contained in:
parent
db963aa002
commit
5bfe51e670
@ -40,9 +40,11 @@ add_library(core STATIC
|
||||
ROMList.h
|
||||
ROMList.cpp
|
||||
FreeBIOS.h
|
||||
FreeBIOS.cpp
|
||||
RTC.cpp
|
||||
Savestate.cpp
|
||||
SPI.cpp
|
||||
SPI_Firmware.cpp
|
||||
SPU.cpp
|
||||
types.h
|
||||
version.h
|
||||
|
64
src/DSi.cpp
64
src/DSi.cpp
@ -548,12 +548,12 @@ void SetupDirectBoot()
|
||||
DSi_NAND::DeInit();
|
||||
}
|
||||
|
||||
u8 nwifiver = SPI_Firmware::GetNWifiVersion();
|
||||
ARM9Write8(0x020005E0, nwifiver);
|
||||
SPI_Firmware::WifiBoard nwifiver = SPI_Firmware::GetFirmware()->Header().WifiBoard;
|
||||
ARM9Write8(0x020005E0, static_cast<u8>(nwifiver));
|
||||
|
||||
// TODO: these should be taken from the wifi firmware in NAND
|
||||
// but, hey, this works too.
|
||||
if (nwifiver == 1)
|
||||
if (nwifiver == SPI_Firmware::WifiBoard::W015)
|
||||
{
|
||||
ARM9Write16(0x020005E2, 0xB57E);
|
||||
ARM9Write32(0x020005E4, 0x00500400);
|
||||
@ -726,64 +726,6 @@ void SoftReset()
|
||||
GPU::DispStat[1] |= (1<<6);
|
||||
}
|
||||
|
||||
bool LoadBIOS()
|
||||
{
|
||||
Platform::FileHandle* f;
|
||||
u32 i;
|
||||
|
||||
memset(ARM9iBIOS, 0, 0x10000);
|
||||
memset(ARM7iBIOS, 0, 0x10000);
|
||||
|
||||
f = Platform::OpenLocalFile(Platform::GetConfigString(Platform::DSi_BIOS9Path), FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM9i BIOS not found\n");
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
((u32*)ARM9iBIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
FileRewind(f);
|
||||
FileRead(ARM9iBIOS, 0x10000, 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM9i BIOS loaded\n");
|
||||
Platform::CloseFile(f);
|
||||
}
|
||||
|
||||
f = Platform::OpenLocalFile(Platform::GetConfigString(Platform::DSi_BIOS7Path), FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM7i BIOS not found\n");
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
((u32*)ARM7iBIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: check if the first 32 bytes are crapoed
|
||||
|
||||
FileRewind(f);
|
||||
FileRead(ARM7iBIOS, 0x10000, 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM7i BIOS loaded\n");
|
||||
CloseFile(f);
|
||||
}
|
||||
|
||||
if (!Platform::GetConfigBool(Platform::DSi_FullBIOSBoot))
|
||||
{
|
||||
// herp
|
||||
*(u32*)&ARM9iBIOS[0] = 0xEAFFFFFE;
|
||||
*(u32*)&ARM7iBIOS[0] = 0xEAFFFFFE;
|
||||
|
||||
// TODO!!!!
|
||||
// hax the upper 32K out of the goddamn DSi
|
||||
// done that :) -pcy
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadNAND()
|
||||
{
|
||||
Log(LogLevel::Info, "Loading DSi NAND\n");
|
||||
|
@ -65,7 +65,6 @@ void SetCartInserted(bool inserted);
|
||||
void SetupDirectBoot();
|
||||
void SoftReset();
|
||||
|
||||
bool LoadBIOS();
|
||||
bool LoadNAND();
|
||||
|
||||
void RunNDMAs(u32 cpu);
|
||||
|
@ -147,6 +147,7 @@ DSi_NWifi::~DSi_NWifi()
|
||||
|
||||
void DSi_NWifi::Reset()
|
||||
{
|
||||
using namespace SPI_Firmware;
|
||||
TransferCmd = 0xFFFFFFFF;
|
||||
RemSize = 0;
|
||||
|
||||
@ -163,26 +164,26 @@ void DSi_NWifi::Reset()
|
||||
for (int i = 0; i < 9; i++)
|
||||
Mailbox[i].Clear();
|
||||
|
||||
u8* mac = SPI_Firmware::GetWifiMAC();
|
||||
MacAddress mac = GetFirmware()->Header().MacAddress;
|
||||
Log(LogLevel::Info, "NWifi MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
u8 type = SPI_Firmware::GetNWifiVersion();
|
||||
WifiBoard type = GetFirmware()->Header().WifiBoard;
|
||||
switch (type)
|
||||
{
|
||||
case 1: // AR6002
|
||||
case WifiBoard::W015: // AR6002
|
||||
ROMID = 0x20000188;
|
||||
ChipID = 0x02000001;
|
||||
HostIntAddr = 0x00500400;
|
||||
break;
|
||||
|
||||
case 2: // AR6013
|
||||
case WifiBoard::W024: // AR6013
|
||||
ROMID = 0x23000024;
|
||||
ChipID = 0x0D000000;
|
||||
HostIntAddr = 0x00520000;
|
||||
break;
|
||||
|
||||
case 3: // AR6014 (3DS)
|
||||
case WifiBoard::W028: // AR6014 (3DS)
|
||||
ROMID = 0x2300006F;
|
||||
ChipID = 0x0D000001;
|
||||
HostIntAddr = 0x00520000;
|
||||
@ -190,7 +191,7 @@ void DSi_NWifi::Reset()
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LogLevel::Warn, "NWifi: unknown hardware type, assuming AR6002\n");
|
||||
Log(LogLevel::Warn, "NWifi: unknown hardware type 0x%x, assuming AR6002\n", static_cast<u8>(type));
|
||||
ROMID = 0x20000188;
|
||||
ChipID = 0x02000001;
|
||||
HostIntAddr = 0x00500400;
|
||||
@ -201,7 +202,7 @@ void DSi_NWifi::Reset()
|
||||
|
||||
*(u32*)&EEPROM[0x000] = 0x300;
|
||||
*(u16*)&EEPROM[0x008] = 0x8348; // TODO: determine properly (country code)
|
||||
memcpy(&EEPROM[0x00A], mac, 6);
|
||||
memcpy(&EEPROM[0x00A], mac.data(), mac.size());
|
||||
*(u32*)&EEPROM[0x010] = 0x60000000;
|
||||
|
||||
memset(&EEPROM[0x03C], 0xFF, 0x70);
|
||||
@ -894,8 +895,9 @@ void DSi_NWifi::HTC_Command()
|
||||
|
||||
case 0x0004: // setup complete
|
||||
{
|
||||
SPI_Firmware::MacAddress mac = SPI_Firmware::GetFirmware()->Header().MacAddress;
|
||||
u8 ready_evt[12];
|
||||
memcpy(&ready_evt[0], SPI_Firmware::GetWifiMAC(), 6);
|
||||
memcpy(&ready_evt[0], &mac, mac.size());
|
||||
ready_evt[6] = 0x02;
|
||||
ready_evt[7] = 0;
|
||||
*(u32*)&ready_evt[8] = 0x2300006C;
|
||||
|
1741
src/FreeBIOS.cpp
Normal file
1741
src/FreeBIOS.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1715
src/FreeBIOS.h
1715
src/FreeBIOS.h
File diff suppressed because it is too large
Load Diff
58
src/NDS.cpp
58
src/NDS.cpp
@ -375,7 +375,7 @@ bool NeedsDirectBoot()
|
||||
return true;
|
||||
|
||||
// DSi/3DS firmwares aren't bootable
|
||||
if (SPI_Firmware::GetFirmwareLength() == 0x20000)
|
||||
if (!SPI_Firmware::GetFirmware()->IsBootable())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@ -524,50 +524,7 @@ void Reset()
|
||||
RunningGame = false;
|
||||
LastSysClockCycles = 0;
|
||||
|
||||
// DS BIOSes are always loaded, even in DSi mode
|
||||
// we need them for DS-compatible mode
|
||||
|
||||
if (Platform::GetConfigBool(Platform::ExternalBIOSEnable))
|
||||
{
|
||||
f = Platform::OpenLocalFile(Platform::GetConfigString(Platform::BIOS9Path), FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM9 BIOS not found\n");
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
((u32*)ARM9BIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
FileRewind(f);
|
||||
FileRead(ARM9BIOS, 0x1000, 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM9 BIOS loaded\n");
|
||||
Platform::CloseFile(f);
|
||||
}
|
||||
|
||||
f = Platform::OpenLocalFile(Platform::GetConfigString(Platform::BIOS7Path), FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM7 BIOS not found\n");
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
((u32*)ARM7BIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
FileRewind(f);
|
||||
FileRead(ARM7BIOS, 0x4000, 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM7 BIOS loaded\n");
|
||||
Platform::CloseFile(f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(ARM9BIOS, bios_arm9_bin, sizeof(bios_arm9_bin));
|
||||
memcpy(ARM7BIOS, bios_arm7_bin, sizeof(bios_arm7_bin));
|
||||
}
|
||||
// BIOS files are now loaded by the frontend
|
||||
|
||||
#ifdef JIT_ENABLED
|
||||
ARMJIT::Reset();
|
||||
@ -575,7 +532,7 @@ void Reset()
|
||||
|
||||
if (ConsoleType == 1)
|
||||
{
|
||||
DSi::LoadBIOS();
|
||||
// BIOS files are now loaded by the frontend
|
||||
|
||||
ARM9ClockShift = 2;
|
||||
MainRAMMask = 0xFFFFFF;
|
||||
@ -1046,6 +1003,15 @@ void LoadBIOS()
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool IsLoadedARM9BIOSBuiltIn()
|
||||
{
|
||||
return memcmp(NDS::ARM9BIOS, bios_arm9_bin, sizeof(NDS::ARM9BIOS)) == 0;
|
||||
}
|
||||
|
||||
bool IsLoadedARM7BIOSBuiltIn()
|
||||
{
|
||||
return memcmp(NDS::ARM7BIOS, bios_arm7_bin, sizeof(NDS::ARM7BIOS)) == 0;
|
||||
}
|
||||
|
||||
u64 NextTarget()
|
||||
{
|
||||
|
@ -240,6 +240,8 @@ void SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth,
|
||||
void SetConsoleType(int type);
|
||||
|
||||
void LoadBIOS();
|
||||
bool IsLoadedARM9BIOSBuiltIn();
|
||||
bool IsLoadedARM7BIOSBuiltIn();
|
||||
|
||||
bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen);
|
||||
void LoadSave(const u8* savedata, u32 savelen);
|
||||
|
@ -24,6 +24,11 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace SPI_Firmware
|
||||
{
|
||||
class Firmware;
|
||||
}
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
|
||||
@ -102,13 +107,6 @@ enum ConfigEntry
|
||||
|
||||
ExternalBIOSEnable,
|
||||
|
||||
BIOS9Path,
|
||||
BIOS7Path,
|
||||
FirmwarePath,
|
||||
|
||||
DSi_BIOS9Path,
|
||||
DSi_BIOS7Path,
|
||||
DSi_FirmwarePath,
|
||||
DSi_NANDPath,
|
||||
|
||||
DLDI_Enable,
|
||||
@ -125,7 +123,7 @@ enum ConfigEntry
|
||||
DSiSD_FolderSync,
|
||||
DSiSD_FolderPath,
|
||||
|
||||
Firm_OverrideSettings,
|
||||
Firm_OverrideSettings [[deprecated("Individual fields can now be overridden")]],
|
||||
Firm_Username,
|
||||
Firm_Language,
|
||||
Firm_BirthdayMonth,
|
||||
@ -333,6 +331,13 @@ void Sleep(u64 usecs);
|
||||
void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen);
|
||||
void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen);
|
||||
|
||||
/// Called when the firmware needs to be written back to storage,
|
||||
/// after one of the supported write commands finishes execution.
|
||||
/// @param firmware The firmware that was just written.
|
||||
/// @param writeoffset The offset of the first byte that was written to firmware.
|
||||
/// @param writelen The number of bytes that were written to firmware.
|
||||
void WriteFirmware(const SPI_Firmware::Firmware& firmware, u32 writeoffset, u32 writelen);
|
||||
|
||||
|
||||
// local multiplayer comm interface
|
||||
// packet type: DS-style TX header (12 bytes) + original 802.11 frame
|
||||
|
481
src/SPI.cpp
481
src/SPI.cpp
@ -19,10 +19,8 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "NDS.h"
|
||||
#include "DSi.h"
|
||||
#include "SPI.h"
|
||||
@ -34,12 +32,7 @@ using namespace Platform;
|
||||
namespace SPI_Firmware
|
||||
{
|
||||
|
||||
std::string FirmwarePath;
|
||||
u8* Firmware;
|
||||
u32 FirmwareLength;
|
||||
u32 FirmwareMask;
|
||||
|
||||
u32 UserSettings;
|
||||
std::unique_ptr<Firmware> Firmware;
|
||||
|
||||
u32 Hold;
|
||||
u8 CurCmd;
|
||||
@ -49,10 +42,9 @@ u8 Data;
|
||||
u8 StatusReg;
|
||||
u32 Addr;
|
||||
|
||||
|
||||
u16 CRC16(u8* data, u32 len, u32 start)
|
||||
u16 CRC16(const u8* data, u32 len, u32 start)
|
||||
{
|
||||
u16 blarg[8] = {0xC0C1, 0xC181, 0xC301, 0xC601, 0xCC01, 0xD801, 0xF001, 0xA001};
|
||||
constexpr u16 blarg[8] = {0xC0C1, 0xC181, 0xC301, 0xC601, 0xCC01, 0xD801, 0xF001, 0xA001};
|
||||
|
||||
for (u32 i = 0; i < len; i++)
|
||||
{
|
||||
@ -75,23 +67,20 @@ u16 CRC16(u8* data, u32 len, u32 start)
|
||||
|
||||
bool VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset)
|
||||
{
|
||||
u16 crc_stored = *(u16*)&Firmware[crcoffset];
|
||||
u16 crc_calced = CRC16(&Firmware[offset], len, start);
|
||||
u16 crc_stored = *(u16*)&Firmware->Buffer()[crcoffset];
|
||||
u16 crc_calced = CRC16(&Firmware->Buffer()[offset], len, start);
|
||||
return (crc_stored == crc_calced);
|
||||
}
|
||||
|
||||
|
||||
bool Init()
|
||||
{
|
||||
FirmwarePath = "";
|
||||
Firmware = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeInit()
|
||||
{
|
||||
if (Firmware) delete[] Firmware;
|
||||
Firmware = nullptr;
|
||||
RemoveFirmware();
|
||||
}
|
||||
|
||||
u32 FixFirmwareLength(u32 originalLength)
|
||||
@ -117,335 +106,43 @@ u32 FixFirmwareLength(u32 originalLength)
|
||||
return originalLength;
|
||||
}
|
||||
|
||||
void LoadDefaultFirmware()
|
||||
{
|
||||
Log(LogLevel::Debug, "Using default firmware image...\n");
|
||||
|
||||
FirmwareLength = 0x20000;
|
||||
Firmware = new u8[FirmwareLength];
|
||||
memset(Firmware, 0xFF, FirmwareLength);
|
||||
FirmwareMask = FirmwareLength - 1;
|
||||
|
||||
memset(Firmware, 0, 0x1D);
|
||||
|
||||
if (NDS::ConsoleType == 1)
|
||||
{
|
||||
Firmware[0x1D] = 0x57; // DSi
|
||||
Firmware[0x2F] = 0x0F;
|
||||
Firmware[0x1FD] = 0x01;
|
||||
Firmware[0x1FE] = 0x20;
|
||||
Firmware[0x2FF] = 0x80; // boot0: use NAND as stage2 medium
|
||||
|
||||
// these need to be zero (part of the stage2 firmware signature!)
|
||||
memset(&Firmware[0x22], 0, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
Firmware[0x1D] = 0x20; // DS Lite (TODO: make configurable?)
|
||||
Firmware[0x2F] = 0x06;
|
||||
}
|
||||
|
||||
// wifi calibration
|
||||
|
||||
const u8 defaultmac[6] = {0x00, 0x09, 0xBF, 0x11, 0x22, 0x33};
|
||||
const u8 bbinit[0x69] =
|
||||
{
|
||||
0x03, 0x17, 0x40, 0x00, 0x1B, 0x6C, 0x48, 0x80, 0x38, 0x00, 0x35, 0x07, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0xBB, 0x01, 0x24, 0x7F,
|
||||
0x5A, 0x01, 0x3F, 0x01, 0x3F, 0x36, 0x1D, 0x00, 0x78, 0x35, 0x55, 0x12, 0x34, 0x1C, 0x00, 0x01,
|
||||
0x0E, 0x38, 0x03, 0x70, 0xC5, 0x2A, 0x0A, 0x08, 0x04, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFE,
|
||||
0xFE, 0xFE, 0xFE, 0xFC, 0xFC, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xF8, 0xF8, 0xF6, 0x00, 0x12, 0x14,
|
||||
0x12, 0x41, 0x23, 0x03, 0x04, 0x70, 0x35, 0x0E, 0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x12, 0x28, 0x1C
|
||||
};
|
||||
const u8 rfinit[0x29] =
|
||||
{
|
||||
0x31, 0x4C, 0x4F, 0x21, 0x00, 0x10, 0xB0, 0x08, 0xFA, 0x15, 0x26, 0xE6, 0xC1, 0x01, 0x0E, 0x50,
|
||||
0x05, 0x00, 0x6D, 0x12, 0x00, 0x00, 0x01, 0xFF, 0x0E, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x02, 0x00, 0x00
|
||||
};
|
||||
const u8 chandata[0x3C] =
|
||||
{
|
||||
0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x16,
|
||||
0x26, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1E, 0x1F, 0x1E, 0x1F, 0x18,
|
||||
0x01, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D,
|
||||
0x02, 0x6C, 0x71, 0x76, 0x5B, 0x40, 0x45, 0x4A, 0x2F, 0x34, 0x39, 0x3E, 0x03, 0x08, 0x14
|
||||
};
|
||||
|
||||
*(u16*)&Firmware[0x2C] = 0x138;
|
||||
Firmware[0x2E] = 0;
|
||||
*(u32*)&Firmware[0x30] = 0xFFFFFFFF;
|
||||
*(u16*)&Firmware[0x34] = 0x00FF;
|
||||
memcpy(&Firmware[0x36], defaultmac, 6);
|
||||
*(u16*)&Firmware[0x3C] = 0x3FFE;
|
||||
*(u16*)&Firmware[0x3E] = 0xFFFF;
|
||||
Firmware[0x40] = 0x03;
|
||||
Firmware[0x41] = 0x94;
|
||||
Firmware[0x42] = 0x29;
|
||||
Firmware[0x43] = 0x02;
|
||||
*(u16*)&Firmware[0x44] = 0x0002;
|
||||
*(u16*)&Firmware[0x46] = 0x0017;
|
||||
*(u16*)&Firmware[0x48] = 0x0026;
|
||||
*(u16*)&Firmware[0x4A] = 0x1818;
|
||||
*(u16*)&Firmware[0x4C] = 0x0048;
|
||||
*(u16*)&Firmware[0x4E] = 0x4840;
|
||||
*(u16*)&Firmware[0x50] = 0x0058;
|
||||
*(u16*)&Firmware[0x52] = 0x0042;
|
||||
*(u16*)&Firmware[0x54] = 0x0146;
|
||||
*(u16*)&Firmware[0x56] = 0x8064;
|
||||
*(u16*)&Firmware[0x58] = 0xE6E6;
|
||||
*(u16*)&Firmware[0x5A] = 0x2443;
|
||||
*(u16*)&Firmware[0x5C] = 0x000E;
|
||||
*(u16*)&Firmware[0x5E] = 0x0001;
|
||||
*(u16*)&Firmware[0x60] = 0x0001;
|
||||
*(u16*)&Firmware[0x62] = 0x0402;
|
||||
memcpy(&Firmware[0x64], bbinit, 0x69);
|
||||
Firmware[0xCD] = 0;
|
||||
memcpy(&Firmware[0xCE], rfinit, 0x29);
|
||||
Firmware[0xF7] = 0x02;
|
||||
memcpy(&Firmware[0xF8], chandata, 0x3C);
|
||||
|
||||
*(u16*)&Firmware[0x2A] = CRC16(&Firmware[0x2C], *(u16*)&Firmware[0x2C], 0x0000);
|
||||
|
||||
// user data
|
||||
|
||||
u32 userdata = 0x7FE00 & FirmwareMask;
|
||||
*(u16*)&Firmware[0x20] = userdata >> 3;
|
||||
|
||||
memset(Firmware + userdata, 0, 0x74);
|
||||
Firmware[userdata+0x00] = 5; // version
|
||||
Firmware[userdata+0x03] = 1;
|
||||
Firmware[userdata+0x04] = 1;
|
||||
*(u16*)&Firmware[userdata+0x64] = 0x0031;
|
||||
|
||||
*(u16*)&Firmware[userdata+0x72] = CRC16(&Firmware[userdata], 0x70, 0xFFFF);
|
||||
|
||||
// wifi access points
|
||||
// TODO: WFC ID??
|
||||
|
||||
std::string wfcsettings = Platform::GetConfigString(ConfigEntry::WifiSettingsPath);
|
||||
|
||||
FileHandle* f = Platform::OpenLocalFile(wfcsettings + Platform::InstanceFileSuffix(), FileMode::Read);
|
||||
if (!f) f = Platform::OpenLocalFile(wfcsettings, FileMode::Read);
|
||||
if (f)
|
||||
{
|
||||
u32 apdata = userdata - 0xA00;
|
||||
FileRead(&Firmware[apdata], 0x900, 1, f);
|
||||
CloseFile(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 apdata = userdata - 0x400;
|
||||
memset(&Firmware[apdata], 0, 0x300);
|
||||
|
||||
strcpy((char*)&Firmware[apdata+0x40], "melonAP");
|
||||
if (NDS::ConsoleType == 1) *(u16*)&Firmware[apdata+0xEA] = 1400;
|
||||
Firmware[apdata+0xEF] = 0x01;
|
||||
*(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000);
|
||||
|
||||
apdata += 0x100;
|
||||
Firmware[apdata+0xE7] = 0xFF;
|
||||
Firmware[apdata+0xEF] = 0x01;
|
||||
*(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000);
|
||||
|
||||
apdata += 0x100;
|
||||
Firmware[apdata+0xE7] = 0xFF;
|
||||
Firmware[apdata+0xEF] = 0x01;
|
||||
*(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000);
|
||||
|
||||
if (NDS::ConsoleType == 1)
|
||||
{
|
||||
apdata = userdata - 0xA00;
|
||||
Firmware[apdata+0xE7] = 0xFF;
|
||||
*(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000);
|
||||
|
||||
apdata += 0x200;
|
||||
Firmware[apdata+0xE7] = 0xFF;
|
||||
*(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000);
|
||||
|
||||
apdata += 0x200;
|
||||
Firmware[apdata+0xE7] = 0xFF;
|
||||
*(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadFirmwareFromFile(FileHandle* f, bool makecopy)
|
||||
{
|
||||
FirmwareLength = FixFirmwareLength(FileLength(f));
|
||||
|
||||
Firmware = new u8[FirmwareLength];
|
||||
|
||||
FileRewind(f);
|
||||
FileRead(Firmware, 1, FirmwareLength, f);
|
||||
|
||||
// take a backup
|
||||
std::string fwBackupPath;
|
||||
if (!makecopy) fwBackupPath = FirmwarePath + ".bak";
|
||||
else fwBackupPath = FirmwarePath;
|
||||
FileHandle* bf = Platform::OpenLocalFile(fwBackupPath, FileMode::Read);
|
||||
if (!bf)
|
||||
{
|
||||
bf = Platform::OpenLocalFile(fwBackupPath, FileMode::Write);
|
||||
if (bf)
|
||||
{
|
||||
FileWrite(Firmware, 1, FirmwareLength, bf);
|
||||
CloseFile(bf);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Error, "Could not write firmware backup!\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseFile(bf);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadUserSettingsFromConfig()
|
||||
{
|
||||
// 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);
|
||||
memcpy(Firmware + UserSettings + 0x06, username.data(), usernameLength * sizeof(char16_t));
|
||||
Firmware[UserSettings+0x1A] = usernameLength;
|
||||
|
||||
// setting language
|
||||
Firmware[UserSettings+0x64] = Platform::GetConfigInt(Platform::Firm_Language);
|
||||
|
||||
// setting up color
|
||||
Firmware[UserSettings+0x02] = Platform::GetConfigInt(Platform::Firm_Color);
|
||||
|
||||
// setting up birthday
|
||||
Firmware[UserSettings+0x03] = Platform::GetConfigInt(Platform::Firm_BirthdayMonth);
|
||||
Firmware[UserSettings+0x04] = 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);
|
||||
memcpy(Firmware + UserSettings + 0x1C, message.data(), messageLength * sizeof(char16_t));
|
||||
Firmware[UserSettings+0x50] = messageLength;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (Firmware) delete[] Firmware;
|
||||
Firmware = nullptr;
|
||||
FirmwarePath = "";
|
||||
bool firmoverride = false;
|
||||
|
||||
if (Platform::GetConfigBool(Platform::ExternalBIOSEnable))
|
||||
if (!Firmware)
|
||||
{
|
||||
if (NDS::ConsoleType == 1)
|
||||
FirmwarePath = Platform::GetConfigString(Platform::DSi_FirmwarePath);
|
||||
else
|
||||
FirmwarePath = Platform::GetConfigString(Platform::FirmwarePath);
|
||||
|
||||
Log(LogLevel::Debug, "SPI firmware: loading from file %s\n", FirmwarePath.c_str());
|
||||
|
||||
bool makecopy = false;
|
||||
std::string origpath = FirmwarePath;
|
||||
FirmwarePath += Platform::InstanceFileSuffix();
|
||||
|
||||
FileHandle* f = Platform::OpenLocalFile(FirmwarePath, FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
f = Platform::OpenLocalFile(origpath, FileMode::Read);
|
||||
makecopy = true;
|
||||
}
|
||||
if (!f)
|
||||
{
|
||||
Log(LogLevel::Warn,"Firmware not found! Generating default firmware.\n");
|
||||
FirmwarePath = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadFirmwareFromFile(f, makecopy);
|
||||
CloseFile(f);
|
||||
}
|
||||
Log(LogLevel::Warn, "SPI firmware: no firmware loaded! Using default\n");
|
||||
Firmware = std::make_unique<class Firmware>(NDS::ConsoleType);
|
||||
}
|
||||
|
||||
if (FirmwarePath.empty())
|
||||
{
|
||||
LoadDefaultFirmware();
|
||||
firmoverride = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
firmoverride = Platform::GetConfigBool(Platform::Firm_OverrideSettings);
|
||||
}
|
||||
|
||||
FirmwareMask = FirmwareLength - 1;
|
||||
|
||||
u32 userdata = 0x7FE00 & FirmwareMask;
|
||||
if (*(u16*)&Firmware[userdata+0x170] == ((*(u16*)&Firmware[userdata+0x70] + 1) & 0x7F))
|
||||
{
|
||||
if (VerifyCRC16(0xFFFF, userdata+0x100, 0x70, userdata+0x172))
|
||||
userdata += 0x100;
|
||||
}
|
||||
|
||||
UserSettings = userdata;
|
||||
|
||||
if (firmoverride)
|
||||
LoadUserSettingsFromConfig();
|
||||
|
||||
// fix touchscreen coords
|
||||
*(u16*)&Firmware[userdata+0x58] = 0;
|
||||
*(u16*)&Firmware[userdata+0x5A] = 0;
|
||||
Firmware[userdata+0x5C] = 0;
|
||||
Firmware[userdata+0x5D] = 0;
|
||||
*(u16*)&Firmware[userdata+0x5E] = 255<<4;
|
||||
*(u16*)&Firmware[userdata+0x60] = 191<<4;
|
||||
Firmware[userdata+0x62] = 255;
|
||||
Firmware[userdata+0x63] = 191;
|
||||
for (UserData& u : Firmware->UserData())
|
||||
{
|
||||
u.TouchCalibrationADC1[0] = 0;
|
||||
u.TouchCalibrationADC1[1] = 0;
|
||||
u.TouchCalibrationPixel1[0] = 0;
|
||||
u.TouchCalibrationPixel1[1] = 0;
|
||||
u.TouchCalibrationADC2[0] = 255<<4;
|
||||
u.TouchCalibrationADC2[1] = 191<<4;
|
||||
u.TouchCalibrationPixel2[0] = 255;
|
||||
u.TouchCalibrationPixel2[1] = 191;
|
||||
}
|
||||
|
||||
Firmware->UpdateChecksums();
|
||||
|
||||
// disable autoboot
|
||||
//Firmware[userdata+0x64] &= 0xBF;
|
||||
|
||||
*(u16*)&Firmware[userdata+0x72] = CRC16(&Firmware[userdata], 0x70, 0xFFFF);
|
||||
|
||||
//if (firmoverride)
|
||||
{
|
||||
u8 mac[6];
|
||||
bool rep = false;
|
||||
|
||||
memcpy(mac, &Firmware[0x36], 6);
|
||||
|
||||
if (firmoverride)
|
||||
rep = Platform::GetConfigArray(Platform::Firm_MAC, mac);
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
{
|
||||
rep = true;
|
||||
mac[3] += inst;
|
||||
mac[4] += inst*0x44;
|
||||
mac[5] += inst*0x10;
|
||||
}
|
||||
|
||||
if (rep)
|
||||
{
|
||||
mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC
|
||||
memcpy(&Firmware[0x36], mac, 6);
|
||||
|
||||
*(u16*)&Firmware[0x2A] = CRC16(&Firmware[0x2C], *(u16*)&Firmware[0x2C], 0x0000);
|
||||
}
|
||||
}
|
||||
|
||||
Log(LogLevel::Info, "MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
|
||||
Firmware[0x36], Firmware[0x37], Firmware[0x38],
|
||||
Firmware[0x39], Firmware[0x3A], Firmware[0x3B]);
|
||||
MacAddress mac = Firmware->Header().MacAddress;
|
||||
Log(LogLevel::Info, "MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
// verify shit
|
||||
Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&Firmware[0x2C], 0x2A)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: AP1 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FA00&FirmwareMask, 0xFE, 0x7FAFE&FirmwareMask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: AP2 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FB00&FirmwareMask, 0xFE, 0x7FBFE&FirmwareMask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: AP3 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FC00&FirmwareMask, 0xFE, 0x7FCFE&FirmwareMask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: USER0 CRC16 = %s\n", VerifyCRC16(0xFFFF, 0x7FE00&FirmwareMask, 0x70, 0x7FE72&FirmwareMask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: USER1 CRC16 = %s\n", VerifyCRC16(0xFFFF, 0x7FF00&FirmwareMask, 0x70, 0x7FF72&FirmwareMask)?"GOOD":"BAD");
|
||||
u32 mask = Firmware->Mask();
|
||||
Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&Firmware->Buffer()[0x2C], 0x2A)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: AP1 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FA00&mask, 0xFE, 0x7FAFE&mask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: AP2 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FB00&mask, 0xFE, 0x7FBFE&mask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: AP3 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FC00&mask, 0xFE, 0x7FCFE&mask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: USER0 CRC16 = %s\n", VerifyCRC16(0xFFFF, 0x7FE00&mask, 0x70, 0x7FE72&mask)?"GOOD":"BAD");
|
||||
Log(LogLevel::Debug, "FW: USER1 CRC16 = %s\n", VerifyCRC16(0xFFFF, 0x7FF00&mask, 0x70, 0x7FF72&mask)?"GOOD":"BAD");
|
||||
|
||||
Hold = 0;
|
||||
CurCmd = 0;
|
||||
@ -471,36 +168,85 @@ void DoSavestate(Savestate* file)
|
||||
|
||||
void SetupDirectBoot(bool dsi)
|
||||
{
|
||||
const FirmwareHeader& header = Firmware->Header();
|
||||
const UserData& userdata = Firmware->EffectiveUserData();
|
||||
if (dsi)
|
||||
{
|
||||
for (u32 i = 0; i < 6; i += 2)
|
||||
DSi::ARM9Write16(0x02FFFCF4, *(u16*)&Firmware[0x36+i]); // MAC address
|
||||
DSi::ARM9Write16(0x02FFFCF4, *(u16*)&header.MacAddress[i]); // MAC address
|
||||
|
||||
// checkme
|
||||
DSi::ARM9Write16(0x02FFFCFA, *(u16*)&Firmware[0x3C]); // enabled channels
|
||||
DSi::ARM9Write16(0x02FFFCFA, header.EnabledChannels); // enabled channels
|
||||
|
||||
for (u32 i = 0; i < 0x70; i += 4)
|
||||
DSi::ARM9Write32(0x02FFFC80+i, *(u32*)&Firmware[UserSettings+i]);
|
||||
DSi::ARM9Write32(0x02FFFC80+i, *(u32*)&userdata.Bytes[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
NDS::ARM9Write32(0x027FF864, 0);
|
||||
NDS::ARM9Write32(0x027FF868, *(u16*)&Firmware[0x20] << 3); // user settings offset
|
||||
NDS::ARM9Write32(0x027FF868, header.UserSettingsOffset << 3); // user settings offset
|
||||
|
||||
NDS::ARM9Write16(0x027FF874, *(u16*)&Firmware[0x26]); // CRC16 for data/gfx
|
||||
NDS::ARM9Write16(0x027FF876, *(u16*)&Firmware[0x04]); // CRC16 for GUI/wifi code
|
||||
NDS::ARM9Write16(0x027FF874, header.DataGfxChecksum); // CRC16 for data/gfx
|
||||
NDS::ARM9Write16(0x027FF876, header.GUIWifiCodeChecksum); // CRC16 for GUI/wifi code
|
||||
|
||||
for (u32 i = 0; i < 0x70; i += 4)
|
||||
NDS::ARM9Write32(0x027FFC80+i, *(u32*)&Firmware[UserSettings+i]);
|
||||
NDS::ARM9Write32(0x027FFC80+i, *(u32*)&userdata.Bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetFirmwareLength() { return FirmwareLength; }
|
||||
u8 GetConsoleType() { return Firmware[0x1D]; }
|
||||
u8 GetWifiVersion() { return Firmware[0x2F]; }
|
||||
u8 GetNWifiVersion() { return Firmware[0x1FD]; } // for DSi; will return 0xFF on a DS
|
||||
u8 GetRFVersion() { return Firmware[0x40]; }
|
||||
u8* GetWifiMAC() { return &Firmware[0x36]; }
|
||||
const class Firmware* GetFirmware()
|
||||
{
|
||||
return Firmware.get();
|
||||
}
|
||||
|
||||
bool IsLoadedFirmwareBuiltIn()
|
||||
{
|
||||
return Firmware->Header().Identifier == GENERATED_FIRMWARE_IDENTIFIER;
|
||||
}
|
||||
|
||||
bool InstallFirmware(class Firmware&& firmware)
|
||||
{
|
||||
if (!firmware.Buffer())
|
||||
{
|
||||
Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Firmware = std::make_unique<class Firmware>(std::move(firmware));
|
||||
|
||||
FirmwareIdentifier id = Firmware->Header().Identifier;
|
||||
Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InstallFirmware(std::unique_ptr<class Firmware>&& firmware)
|
||||
{
|
||||
if (!firmware)
|
||||
{
|
||||
Log(LogLevel::Error, "SPI firmware: firmware is null!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!firmware->Buffer())
|
||||
{
|
||||
Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Firmware = std::move(firmware);
|
||||
|
||||
FirmwareIdentifier id = Firmware->Header().Identifier;
|
||||
Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveFirmware()
|
||||
{
|
||||
Firmware.reset();
|
||||
Log(LogLevel::Debug, "Removed installed firmware (if any)\n");
|
||||
}
|
||||
|
||||
u8 Read()
|
||||
{
|
||||
@ -539,7 +285,7 @@ void Write(u8 val, u32 hold)
|
||||
}
|
||||
else
|
||||
{
|
||||
Data = Firmware[Addr & FirmwareMask];
|
||||
Data = Firmware->Buffer()[Addr & Firmware->Mask()];
|
||||
Addr++;
|
||||
}
|
||||
|
||||
@ -565,14 +311,14 @@ void Write(u8 val, u32 hold)
|
||||
{
|
||||
// TODO: what happens if you write too many bytes? (max 256, they say)
|
||||
if (DataPos < 4)
|
||||
{
|
||||
{ // If we're in the middle of writing the address...
|
||||
Addr <<= 8;
|
||||
Addr |= val;
|
||||
Data = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Firmware[Addr & FirmwareMask] = val;
|
||||
Firmware->Buffer()[Addr & Firmware->Mask()] = val;
|
||||
Data = val;
|
||||
Addr++;
|
||||
}
|
||||
@ -601,31 +347,14 @@ void Write(u8 val, u32 hold)
|
||||
}
|
||||
|
||||
if (!hold && (CurCmd == 0x02 || CurCmd == 0x0A))
|
||||
{
|
||||
if (!FirmwarePath.empty())
|
||||
{
|
||||
FileHandle* f = Platform::OpenLocalFile(FirmwarePath, FileMode::ReadWriteExisting);
|
||||
if (f)
|
||||
{
|
||||
u32 cutoff = ((NDS::ConsoleType==1) ? 0x7F400 : 0x7FA00) & FirmwareMask;
|
||||
FileSeek(f, cutoff, FileSeekOrigin::Start);
|
||||
FileWrite(&Firmware[cutoff], FirmwareLength-cutoff, 1, f);
|
||||
CloseFile(f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string wfcfile = Platform::GetConfigString(ConfigEntry::WifiSettingsPath);
|
||||
if (Platform::InstanceID() > 0) wfcfile += Platform::InstanceFileSuffix();
|
||||
{ // If the SPI firmware chip just finished a write...
|
||||
// We only notify the frontend of changes to the Wi-fi/userdata settings region
|
||||
// (although it might still decide to flush the whole thing)
|
||||
u32 wifioffset = Firmware->WifiAccessPointOffset();
|
||||
|
||||
FileHandle* f = Platform::OpenLocalFile(wfcfile, FileMode::Write);
|
||||
if (f)
|
||||
{
|
||||
u32 cutoff = 0x7F400 & FirmwareMask;
|
||||
FileWrite(&Firmware[cutoff], 0x900, 1, f);
|
||||
CloseFile(f);
|
||||
}
|
||||
}
|
||||
// Request that the start of the Wi-fi/userdata settings region
|
||||
// through the end of the firmware blob be flushed to disk
|
||||
Platform::WriteFirmware(*Firmware, wifioffset, Firmware->Length() - wifioffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
22
src/SPI.h
22
src/SPI.h
@ -19,22 +19,32 @@
|
||||
#ifndef SPI_H
|
||||
#define SPI_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <string.h>
|
||||
|
||||
#include "Savestate.h"
|
||||
#include "SPI_Firmware.h"
|
||||
|
||||
namespace SPI_Firmware
|
||||
{
|
||||
|
||||
u16 CRC16(const u8* data, u32 len, u32 start);
|
||||
void SetupDirectBoot(bool dsi);
|
||||
|
||||
u32 FixFirmwareLength(u32 originalLength);
|
||||
|
||||
u32 GetFirmwareLength();
|
||||
u8 GetConsoleType();
|
||||
u8 GetWifiVersion();
|
||||
u8 GetNWifiVersion();
|
||||
u8 GetRFVersion();
|
||||
u8* GetWifiMAC();
|
||||
/// @return A pointer to the installed firmware blob if one exists, otherwise \c nullptr.
|
||||
/// @warning The pointer refers to memory that melonDS owns. Do not deallocate it yourself.
|
||||
/// @see InstallFirmware
|
||||
const Firmware* GetFirmware();
|
||||
|
||||
bool IsLoadedFirmwareBuiltIn();
|
||||
bool InstallFirmware(Firmware&& firmware);
|
||||
bool InstallFirmware(std::unique_ptr<Firmware>&& firmware);
|
||||
void RemoveFirmware();
|
||||
}
|
||||
|
||||
namespace SPI_Powerman
|
||||
|
376
src/SPI_Firmware.cpp
Normal file
376
src/SPI_Firmware.cpp
Normal file
@ -0,0 +1,376 @@
|
||||
/*
|
||||
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 "SPI_Firmware.h"
|
||||
#include "SPI.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
constexpr u8 BBINIT[0x69]
|
||||
{
|
||||
0x03, 0x17, 0x40, 0x00, 0x1B, 0x6C, 0x48, 0x80, 0x38, 0x00, 0x35, 0x07, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0xBB, 0x01, 0x24, 0x7F,
|
||||
0x5A, 0x01, 0x3F, 0x01, 0x3F, 0x36, 0x1D, 0x00, 0x78, 0x35, 0x55, 0x12, 0x34, 0x1C, 0x00, 0x01,
|
||||
0x0E, 0x38, 0x03, 0x70, 0xC5, 0x2A, 0x0A, 0x08, 0x04, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFE,
|
||||
0xFE, 0xFE, 0xFE, 0xFC, 0xFC, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xF8, 0xF8, 0xF6, 0x00, 0x12, 0x14,
|
||||
0x12, 0x41, 0x23, 0x03, 0x04, 0x70, 0x35, 0x0E, 0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x12, 0x28, 0x1C
|
||||
};
|
||||
|
||||
constexpr u8 RFINIT[0x29]
|
||||
{
|
||||
0x31, 0x4C, 0x4F, 0x21, 0x00, 0x10, 0xB0, 0x08, 0xFA, 0x15, 0x26, 0xE6, 0xC1, 0x01, 0x0E, 0x50,
|
||||
0x05, 0x00, 0x6D, 0x12, 0x00, 0x00, 0x01, 0xFF, 0x0E, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x02, 0x00, 0x00
|
||||
};
|
||||
|
||||
constexpr u8 CHANDATA[0x3C]
|
||||
{
|
||||
0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x16,
|
||||
0x26, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1E, 0x1F, 0x1E, 0x1F, 0x18,
|
||||
0x01, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D,
|
||||
0x02, 0x6C, 0x71, 0x76, 0x5B, 0x40, 0x45, 0x4A, 0x2F, 0x34, 0x39, 0x3E, 0x03, 0x08, 0x14
|
||||
};
|
||||
|
||||
constexpr u8 DEFAULT_UNUSED3[6] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
|
||||
|
||||
SPI_Firmware::WifiAccessPoint::WifiAccessPoint()
|
||||
{
|
||||
memset(Bytes, 0, sizeof(Bytes));
|
||||
Status = AccessPointStatus::NotConfigured;
|
||||
ConnectionConfigured = 0x01;
|
||||
UpdateChecksum();
|
||||
}
|
||||
|
||||
SPI_Firmware::WifiAccessPoint::WifiAccessPoint(int consoletype)
|
||||
{
|
||||
memset(Bytes, 0, sizeof(Bytes));
|
||||
strncpy(SSID, DEFAULT_SSID, sizeof(SSID));
|
||||
if (consoletype == 1) Mtu = 1400;
|
||||
Status = AccessPointStatus::Normal;
|
||||
ConnectionConfigured = 0x01;
|
||||
UpdateChecksum();
|
||||
}
|
||||
|
||||
void SPI_Firmware::WifiAccessPoint::UpdateChecksum()
|
||||
{
|
||||
Checksum = CRC16(Bytes, 0xFE, 0x0000);
|
||||
}
|
||||
|
||||
SPI_Firmware::ExtendedWifiAccessPoint::ExtendedWifiAccessPoint()
|
||||
{
|
||||
Data.Base = WifiAccessPoint();
|
||||
|
||||
UpdateChecksum();
|
||||
}
|
||||
|
||||
void SPI_Firmware::ExtendedWifiAccessPoint::UpdateChecksum()
|
||||
{
|
||||
Data.Base.UpdateChecksum();
|
||||
Data.ExtendedChecksum = CRC16(&Bytes[0x100], 0xFE, 0x0000);
|
||||
}
|
||||
|
||||
SPI_Firmware::FirmwareHeader::FirmwareHeader(int consoletype)
|
||||
{
|
||||
if (consoletype == 1)
|
||||
{
|
||||
ConsoleType = FirmwareConsoleType::DSi;
|
||||
WifiVersion = WifiVersion::W015;
|
||||
WifiBoard = WifiBoard::W015;
|
||||
WifiFlash = 0x20;
|
||||
|
||||
// these need to be zero (part of the stage2 firmware signature!)
|
||||
memset(&Bytes[0x22], 0, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsoleType = FirmwareConsoleType::DSLite; // TODO: make configurable?
|
||||
WifiVersion = WifiVersion::W006;
|
||||
}
|
||||
Identifier = GENERATED_FIRMWARE_IDENTIFIER;
|
||||
|
||||
|
||||
// wifi calibration
|
||||
WifiConfigLength = 0x138;
|
||||
Unused1 = 0;
|
||||
memcpy(&Unused3, DEFAULT_UNUSED3, sizeof(DEFAULT_UNUSED3));
|
||||
MacAddress = DEFAULT_MAC;
|
||||
EnabledChannels = 0x3FFE;
|
||||
memset(&Unknown2, 0xFF, sizeof(Unknown2));
|
||||
RFChipType = RFChipType::Type3;
|
||||
RFBitsPerEntry = 0x94;
|
||||
RFEntries = 0x29;
|
||||
Unknown3 = 0x02;
|
||||
*(u16*)&Bytes[0x44] = 0x0002;
|
||||
*(u16*)&Bytes[0x46] = 0x0017;
|
||||
*(u16*)&Bytes[0x48] = 0x0026;
|
||||
*(u16*)&Bytes[0x4A] = 0x1818;
|
||||
*(u16*)&Bytes[0x4C] = 0x0048;
|
||||
*(u16*)&Bytes[0x4E] = 0x4840;
|
||||
*(u16*)&Bytes[0x50] = 0x0058;
|
||||
*(u16*)&Bytes[0x52] = 0x0042;
|
||||
*(u16*)&Bytes[0x54] = 0x0146;
|
||||
*(u16*)&Bytes[0x56] = 0x8064;
|
||||
*(u16*)&Bytes[0x58] = 0xE6E6;
|
||||
*(u16*)&Bytes[0x5A] = 0x2443;
|
||||
*(u16*)&Bytes[0x5C] = 0x000E;
|
||||
*(u16*)&Bytes[0x5E] = 0x0001;
|
||||
*(u16*)&Bytes[0x60] = 0x0001;
|
||||
*(u16*)&Bytes[0x62] = 0x0402;
|
||||
memcpy(&InitialBBValues, BBINIT, sizeof(BBINIT));
|
||||
Unused4 = 0;
|
||||
memcpy(&Type3Config.InitialRFValues, RFINIT, sizeof(RFINIT));
|
||||
Type3Config.BBIndicesPerChannel = 0x02;
|
||||
memcpy(&Bytes[0xF8], CHANDATA, sizeof(CHANDATA));
|
||||
memset(&Type3Config.Unused0, 0xFF, sizeof(Type3Config.Unused0));
|
||||
|
||||
UpdateChecksum();
|
||||
}
|
||||
|
||||
|
||||
void SPI_Firmware::FirmwareHeader::UpdateChecksum()
|
||||
{
|
||||
WifiConfigChecksum = SPI_Firmware::CRC16(&Bytes[0x2C], WifiConfigLength, 0x0000);
|
||||
}
|
||||
|
||||
SPI_Firmware::UserData::UserData()
|
||||
{
|
||||
memset(Bytes, 0, 0x74);
|
||||
Version = 5;
|
||||
BirthdayMonth = 1;
|
||||
BirthdayDay = 1;
|
||||
Settings = Language::English | BacklightLevel::Max; // NOLINT(*-suspicious-enum-usage)
|
||||
memcpy(Nickname, DEFAULT_USERNAME.data(), DEFAULT_USERNAME.size() * sizeof(std::u16string_view::value_type));
|
||||
NameLength = DEFAULT_USERNAME.size();
|
||||
Checksum = CRC16(Bytes, 0x70, 0xFFFF);
|
||||
}
|
||||
|
||||
void SPI_Firmware::UserData::UpdateChecksum()
|
||||
{
|
||||
Checksum = CRC16(Bytes, 0x70, 0xFFFF);
|
||||
if (ExtendedSettings.Unknown0 == 0x01)
|
||||
{
|
||||
ExtendedSettings.Checksum = CRC16(&Bytes[0x74], 0x8A, 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware::Firmware(int consoletype)
|
||||
{
|
||||
FirmwareBufferLength = DEFAULT_FIRMWARE_LENGTH;
|
||||
FirmwareBuffer = new u8[FirmwareBufferLength];
|
||||
memset(FirmwareBuffer, 0xFF, FirmwareBufferLength);
|
||||
FirmwareMask = FirmwareBufferLength - 1;
|
||||
|
||||
memset(FirmwareBuffer, 0, 0x1D);
|
||||
FirmwareHeader& header = *reinterpret_cast<FirmwareHeader*>(FirmwareBuffer);
|
||||
header = FirmwareHeader(consoletype);
|
||||
FirmwareBuffer[0x2FF] = 0x80; // boot0: use NAND as stage2 medium
|
||||
|
||||
// user data
|
||||
header.UserSettingsOffset = (0x7FE00 & FirmwareMask) >> 3;
|
||||
|
||||
std::array<union UserData, 2>& settings = *reinterpret_cast<std::array<union UserData, 2>*>(UserDataPosition());
|
||||
settings = {
|
||||
SPI_Firmware::UserData(),
|
||||
SPI_Firmware::UserData(),
|
||||
};
|
||||
|
||||
// wifi access points
|
||||
// TODO: WFC ID??
|
||||
|
||||
std::array<WifiAccessPoint, 3>& accesspoints = *reinterpret_cast<std::array<WifiAccessPoint, 3>*>(WifiAccessPointPosition());
|
||||
|
||||
accesspoints = {
|
||||
WifiAccessPoint(consoletype),
|
||||
WifiAccessPoint(),
|
||||
WifiAccessPoint(),
|
||||
};
|
||||
|
||||
if (consoletype == 1)
|
||||
{
|
||||
std::array<ExtendedWifiAccessPoint, 3>& extendedaccesspoints = *reinterpret_cast<std::array<ExtendedWifiAccessPoint, 3>*>(ExtendedAccessPointPosition());
|
||||
|
||||
extendedaccesspoints = {
|
||||
ExtendedWifiAccessPoint(),
|
||||
ExtendedWifiAccessPoint(),
|
||||
ExtendedWifiAccessPoint(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware::Firmware(Platform::FileHandle* file) : FirmwareBuffer(nullptr), FirmwareBufferLength(0), FirmwareMask(0)
|
||||
{
|
||||
if (file)
|
||||
{
|
||||
u64 length = Platform::FileLength(file);
|
||||
if (length > 0)
|
||||
{
|
||||
FirmwareBufferLength = FixFirmwareLength(length);
|
||||
FirmwareBuffer = new u8[FirmwareBufferLength];
|
||||
FirmwareMask = FirmwareBufferLength - 1;
|
||||
|
||||
memset(FirmwareBuffer, 0, FirmwareBufferLength);
|
||||
Platform::FileRewind(file);
|
||||
if (!Platform::FileRead(FirmwareBuffer, length, 1, file))
|
||||
{
|
||||
delete[] FirmwareBuffer;
|
||||
FirmwareBuffer = nullptr;
|
||||
FirmwareBufferLength = 0;
|
||||
FirmwareMask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Platform::FileRewind(file);
|
||||
}
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware::Firmware(const u8* data, u32 length) : FirmwareBuffer(nullptr), FirmwareBufferLength(FixFirmwareLength(length))
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
FirmwareBuffer = new u8[FirmwareBufferLength];
|
||||
memcpy(FirmwareBuffer, data, FirmwareBufferLength);
|
||||
FirmwareMask = FirmwareBufferLength - 1;
|
||||
}
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware::Firmware(const Firmware& other) : FirmwareBuffer(nullptr), FirmwareBufferLength(other.FirmwareBufferLength)
|
||||
{
|
||||
FirmwareBuffer = new u8[FirmwareBufferLength];
|
||||
memcpy(FirmwareBuffer, other.FirmwareBuffer, FirmwareBufferLength);
|
||||
FirmwareMask = other.FirmwareMask;
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware::Firmware(Firmware&& other) noexcept
|
||||
{
|
||||
FirmwareBuffer = other.FirmwareBuffer;
|
||||
FirmwareBufferLength = other.FirmwareBufferLength;
|
||||
FirmwareMask = other.FirmwareMask;
|
||||
other.FirmwareBuffer = nullptr;
|
||||
other.FirmwareBufferLength = 0;
|
||||
other.FirmwareMask = 0;
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware& SPI_Firmware::Firmware::operator=(const Firmware& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
delete[] FirmwareBuffer;
|
||||
FirmwareBufferLength = other.FirmwareBufferLength;
|
||||
FirmwareBuffer = new u8[other.FirmwareBufferLength];
|
||||
memcpy(FirmwareBuffer, other.FirmwareBuffer, other.FirmwareBufferLength);
|
||||
FirmwareMask = other.FirmwareMask;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware& SPI_Firmware::Firmware::operator=(Firmware&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
delete[] FirmwareBuffer;
|
||||
FirmwareBuffer = other.FirmwareBuffer;
|
||||
FirmwareBufferLength = other.FirmwareBufferLength;
|
||||
FirmwareMask = other.FirmwareMask;
|
||||
other.FirmwareBuffer = nullptr;
|
||||
other.FirmwareBufferLength = 0;
|
||||
other.FirmwareMask = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
SPI_Firmware::Firmware::~Firmware()
|
||||
{
|
||||
delete[] FirmwareBuffer;
|
||||
}
|
||||
|
||||
bool SPI_Firmware::Firmware::IsBootable() const
|
||||
{
|
||||
return
|
||||
FirmwareBufferLength != DEFAULT_FIRMWARE_LENGTH &&
|
||||
Header().Identifier != GENERATED_FIRMWARE_IDENTIFIER
|
||||
;
|
||||
}
|
||||
|
||||
const SPI_Firmware::UserData& SPI_Firmware::Firmware::EffectiveUserData() const {
|
||||
const std::array<union UserData, 2>& userdata = UserData();
|
||||
bool userdata0ChecksumOk = userdata[0].ChecksumValid();
|
||||
bool userdata1ChecksumOk = userdata[1].ChecksumValid();
|
||||
|
||||
if (userdata0ChecksumOk && userdata1ChecksumOk)
|
||||
{
|
||||
return userdata[0].UpdateCounter > userdata[1].UpdateCounter ? userdata[0] : userdata[1];
|
||||
}
|
||||
else if (userdata0ChecksumOk)
|
||||
{
|
||||
return userdata[0];
|
||||
}
|
||||
else if (userdata1ChecksumOk)
|
||||
{
|
||||
return userdata[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return userdata[0];
|
||||
}
|
||||
}
|
||||
|
||||
SPI_Firmware::UserData& SPI_Firmware::Firmware::EffectiveUserData() {
|
||||
std::array<union UserData, 2>& userdata = UserData();
|
||||
bool userdata0ChecksumOk = userdata[0].ChecksumValid();
|
||||
bool userdata1ChecksumOk = userdata[1].ChecksumValid();
|
||||
|
||||
if (userdata0ChecksumOk && userdata1ChecksumOk)
|
||||
{
|
||||
return userdata[0].UpdateCounter > userdata[1].UpdateCounter ? userdata[0] : userdata[1];
|
||||
}
|
||||
else if (userdata0ChecksumOk)
|
||||
{
|
||||
return userdata[0];
|
||||
}
|
||||
else if (userdata1ChecksumOk)
|
||||
{
|
||||
return userdata[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return userdata[0];
|
||||
}
|
||||
}
|
||||
|
||||
void SPI_Firmware::Firmware::UpdateChecksums()
|
||||
{
|
||||
Header().UpdateChecksum();
|
||||
|
||||
for (SPI_Firmware::WifiAccessPoint& ap : AccessPoints())
|
||||
{
|
||||
ap.UpdateChecksum();
|
||||
}
|
||||
|
||||
for (SPI_Firmware::ExtendedWifiAccessPoint& eap : ExtendedAccessPoints())
|
||||
{
|
||||
eap.UpdateChecksum();
|
||||
}
|
||||
|
||||
for (SPI_Firmware::UserData& u : UserData())
|
||||
{
|
||||
u.UpdateChecksum();
|
||||
}
|
||||
}
|
563
src/SPI_Firmware.h
Normal file
563
src/SPI_Firmware.h
Normal file
@ -0,0 +1,563 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#ifndef MELONDS_SPI_FIRMWARE_H
|
||||
#define MELONDS_SPI_FIRMWARE_H
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include "types.h"
|
||||
#include "Platform.h"
|
||||
|
||||
namespace SPI_Firmware
|
||||
{
|
||||
|
||||
u16 CRC16(const u8* data, u32 len, u32 start);
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
using IpAddress = std::array<u8, 4>;
|
||||
|
||||
constexpr unsigned DEFAULT_FIRMWARE_LENGTH = 0x20000;
|
||||
constexpr MacAddress DEFAULT_MAC = {0x00, 0x09, 0xBF, 0x11, 0x22, 0x33};
|
||||
constexpr unsigned MAX_SSID_LENGTH = 32;
|
||||
constexpr std::u16string_view DEFAULT_USERNAME = u"melonDS";
|
||||
constexpr const char* const DEFAULT_SSID = "melonAP";
|
||||
|
||||
/**
|
||||
* The position of the first extended Wi-fi settings block in the DSi firmware,
|
||||
* relative to the position of the first user settings block.
|
||||
* The generated firmware also uses this offset.
|
||||
*/
|
||||
constexpr int EXTENDED_WIFI_SETTINGS_OFFSET = -0xA00;
|
||||
|
||||
enum class WepMode : u8
|
||||
{
|
||||
None = 0,
|
||||
Hex5 = 1,
|
||||
Hex13 = 2,
|
||||
Hex16 = 3,
|
||||
Ascii5 = 5,
|
||||
Ascii13 = 6,
|
||||
Ascii16 = 7,
|
||||
};
|
||||
|
||||
enum class WpaMode : u8
|
||||
{
|
||||
Normal = 0,
|
||||
WPA_WPA2 = 0x10,
|
||||
WPS_WPA = 0x13,
|
||||
Unused = 0xff,
|
||||
};
|
||||
|
||||
enum class WpaSecurity : u8
|
||||
{
|
||||
None = 0,
|
||||
WPA_TKIP = 4,
|
||||
WPA2_TKIP = 5,
|
||||
WPA_AES = 6,
|
||||
WPA2_AES = 7,
|
||||
};
|
||||
|
||||
enum class AccessPointStatus : u8
|
||||
{
|
||||
Normal = 0,
|
||||
Aoss = 1,
|
||||
NotConfigured = 0xff
|
||||
};
|
||||
|
||||
/**
|
||||
* @see https://problemkaputt.de/gbatek.htm#dsfirmwarewifiinternetaccesspoints
|
||||
*/
|
||||
union WifiAccessPoint
|
||||
{
|
||||
/**
|
||||
* Constructs an unconfigured access point.
|
||||
*/
|
||||
WifiAccessPoint();
|
||||
|
||||
/**
|
||||
* Constructs an access point configured with melonDS's defaults.
|
||||
*/
|
||||
explicit WifiAccessPoint(int consoletype);
|
||||
void UpdateChecksum();
|
||||
u8 Bytes[256];
|
||||
struct
|
||||
{
|
||||
char ProxyUsername[32];
|
||||
char ProxyPassword[32];
|
||||
char SSID[32];
|
||||
char SSIDWEP64[32];
|
||||
u8 WEPKey1[16];
|
||||
u8 WEPKey2[16];
|
||||
u8 WEPKey3[16];
|
||||
u8 WEPKey4[16];
|
||||
IpAddress Address;
|
||||
IpAddress Gateway;
|
||||
IpAddress PrimaryDns;
|
||||
IpAddress SecondaryDns;
|
||||
u8 SubnetMask;
|
||||
u8 Unknown0[21];
|
||||
enum WepMode WepMode;
|
||||
AccessPointStatus Status;
|
||||
u8 SSIDLength;
|
||||
u8 Unknown1;
|
||||
u16 Mtu;
|
||||
u8 Unknown2[3];
|
||||
u8 ConnectionConfigured;
|
||||
u8 NintendoWFCID[6];
|
||||
u8 Unknown3[8];
|
||||
u16 Checksum;
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(WifiAccessPoint) == 256, "WifiAccessPoint should be 256 bytes");
|
||||
|
||||
union ExtendedWifiAccessPoint
|
||||
{
|
||||
ExtendedWifiAccessPoint();
|
||||
void UpdateChecksum();
|
||||
u8 Bytes[512];
|
||||
struct
|
||||
{
|
||||
WifiAccessPoint Base;
|
||||
|
||||
// DSi-specific entries now
|
||||
u8 PrecomputedPSK[32];
|
||||
char WPAPassword[64];
|
||||
char Unused0[33];
|
||||
WpaSecurity Security;
|
||||
bool ProxyEnabled;
|
||||
bool ProxyAuthentication;
|
||||
char ProxyName[48];
|
||||
u8 Unused1[52];
|
||||
u16 ProxyPort;
|
||||
u8 Unused2[20];
|
||||
u16 ExtendedChecksum;
|
||||
} Data;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ExtendedWifiAccessPoint) == 512, "WifiAccessPoint should be 512 bytes");
|
||||
|
||||
|
||||
enum class FirmwareConsoleType : u8
|
||||
{
|
||||
DS = 0xFF,
|
||||
DSLite = 0x20,
|
||||
DSi = 0x57,
|
||||
iQueDS = 0x43,
|
||||
iQueDSLite = 0x63,
|
||||
};
|
||||
|
||||
enum class WifiVersion : u8
|
||||
{
|
||||
V1_4 = 0,
|
||||
V5 = 3,
|
||||
V6_7 = 5,
|
||||
W006 = 6,
|
||||
W015 = 15,
|
||||
W024 = 24,
|
||||
N3DS = 34,
|
||||
};
|
||||
|
||||
enum RFChipType : u8
|
||||
{
|
||||
Type2 = 0x2,
|
||||
Type3 = 0x3,
|
||||
};
|
||||
|
||||
enum class WifiBoard : u8
|
||||
{
|
||||
W015 = 0x1,
|
||||
W024 = 0x2,
|
||||
W028 = 0x3,
|
||||
Unused = 0xff,
|
||||
};
|
||||
|
||||
enum Language : u8
|
||||
{
|
||||
Japanese = 0,
|
||||
English = 1,
|
||||
French = 2,
|
||||
German = 3,
|
||||
Italian = 4,
|
||||
Spanish = 5,
|
||||
Chinese = 6,
|
||||
Reserved = 7,
|
||||
};
|
||||
|
||||
enum GBAScreen : u8
|
||||
{
|
||||
Upper = 0,
|
||||
Lower = (1 << 3),
|
||||
};
|
||||
|
||||
enum BacklightLevel : u8
|
||||
{
|
||||
Low = 0,
|
||||
Medium = 1 << 4,
|
||||
High = 2 << 4,
|
||||
Max = 3 << 4
|
||||
};
|
||||
|
||||
enum BootMenu : u8
|
||||
{
|
||||
Manual = 0,
|
||||
Autostart = 1 << 6,
|
||||
};
|
||||
|
||||
using FirmwareIdentifier = std::array<u8, 4>;
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
|
||||
constexpr FirmwareIdentifier GENERATED_FIRMWARE_IDENTIFIER = {'M', 'E', 'L', 'N'};
|
||||
|
||||
/**
|
||||
* @note GBATek says the header is actually 511 bytes;
|
||||
* this header struct is 512 bytes due to padding,
|
||||
* but the last byte is just the first byte of the firmware's code.
|
||||
* It doesn't affect the offset of any of the fields,
|
||||
* so leaving that last byte in there is harmless.
|
||||
* @see https://problemkaputt.de/gbatek.htm#dsfirmwareheader
|
||||
* @see https://problemkaputt.de/gbatek.htm#dsfirmwarewificalibrationdata
|
||||
*/
|
||||
union FirmwareHeader
|
||||
{
|
||||
explicit FirmwareHeader(int consoletype);
|
||||
void UpdateChecksum();
|
||||
u8 Bytes[512];
|
||||
struct
|
||||
{
|
||||
u16 ARM9GUICodeOffset;
|
||||
u16 ARM7WifiCodeOffset;
|
||||
u16 GUIWifiCodeChecksum;
|
||||
u16 BootCodeChecksum;
|
||||
|
||||
FirmwareIdentifier Identifier;
|
||||
|
||||
u16 ARM9BootCodeROMAddress;
|
||||
u16 ARM9BootCodeRAMAddress;
|
||||
u16 ARM7BootCodeRAMAddress;
|
||||
u16 ARM7BootCodeROMAddress;
|
||||
u16 ShiftAmounts;
|
||||
u16 DataGfxRomAddress;
|
||||
|
||||
u8 BuildMinute;
|
||||
u8 BuildHour;
|
||||
u8 BuildDay;
|
||||
u8 BuildMonth;
|
||||
u8 BuildYear;
|
||||
|
||||
FirmwareConsoleType ConsoleType;
|
||||
|
||||
u8 Unused0[2];
|
||||
|
||||
u16 UserSettingsOffset;
|
||||
u8 Unknown0[2];
|
||||
u8 Unknown1[2];
|
||||
u16 DataGfxChecksum;
|
||||
u8 Unused2[2];
|
||||
|
||||
// Begin wi-fi settings
|
||||
u16 WifiConfigChecksum;
|
||||
u16 WifiConfigLength;
|
||||
u8 Unused1;
|
||||
enum WifiVersion WifiVersion;
|
||||
|
||||
u8 Unused3[6];
|
||||
|
||||
SPI_Firmware::MacAddress MacAddress;
|
||||
|
||||
u16 EnabledChannels;
|
||||
|
||||
u8 Unknown2[2];
|
||||
|
||||
enum RFChipType RFChipType;
|
||||
u8 RFBitsPerEntry;
|
||||
u8 RFEntries;
|
||||
u8 Unknown3;
|
||||
|
||||
u8 InitialValues[32];
|
||||
u8 InitialBBValues[105];
|
||||
u8 Unused4;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
u8 InitialRFValues[36];
|
||||
u8 InitialRF56Values[84];
|
||||
u8 InitialBB1EValues[14];
|
||||
u8 InitialRf9Values[14];
|
||||
} Type2Config;
|
||||
|
||||
struct
|
||||
{
|
||||
u8 InitialRFValues[41];
|
||||
u8 BBIndicesPerChannel;
|
||||
u8 BBIndex1;
|
||||
u8 BBData1[14];
|
||||
u8 BBIndex2;
|
||||
u8 BBData2[14];
|
||||
u8 RFIndex1;
|
||||
u8 RFData1[14];
|
||||
u8 RFIndex2;
|
||||
u8 RFData2[14];
|
||||
u8 Unused0[46];
|
||||
} Type3Config;
|
||||
};
|
||||
|
||||
u8 Unknown4;
|
||||
u8 Unused5;
|
||||
u8 Unused6[153];
|
||||
enum WifiBoard WifiBoard;
|
||||
u8 WifiFlash;
|
||||
u8 Unused7;
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(FirmwareHeader) == 512, "FirmwareHeader should be 512 bytes");
|
||||
|
||||
struct ExtendedUserSettings
|
||||
{
|
||||
char ID[8];
|
||||
u16 Checksum;
|
||||
u16 ChecksumLength;
|
||||
u8 Version;
|
||||
u8 UpdateCount;
|
||||
u8 BootMenuFlags;
|
||||
u8 GBABorder;
|
||||
u16 TemperatureCalibration0;
|
||||
u16 TemperatureCalibration1;
|
||||
u16 TemperatureCalibrationDegrees;
|
||||
u8 TemperatureFlags;
|
||||
u8 BacklightIntensity;
|
||||
u32 DateCenturyOffset;
|
||||
u8 DateMonthRecovery;
|
||||
u8 DateDayRecovery;
|
||||
u8 DateYearRecovery;
|
||||
u8 DateTimeFlags;
|
||||
char DateSeparator;
|
||||
char TimeSeparator;
|
||||
char DecimalSeparator;
|
||||
char ThousandsSeparator;
|
||||
u8 DaylightSavingsTimeNth;
|
||||
u8 DaylightSavingsTimeDay;
|
||||
u8 DaylightSavingsTimeOfMonth;
|
||||
u8 DaylightSavingsTimeFlags;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ExtendedUserSettings) == 0x28, "ExtendedUserSettings should be 40 bytes");
|
||||
|
||||
union UserData
|
||||
{
|
||||
UserData();
|
||||
void UpdateChecksum();
|
||||
[[nodiscard]] bool ChecksumValid() const
|
||||
{
|
||||
bool baseChecksumOk = Checksum == CRC16(Bytes, 0x70, 0xFFFF);
|
||||
bool extendedChecksumOk = Bytes[0x74] != 1 || ExtendedSettings.Checksum == CRC16(Bytes + 0x74, 0x8A, 0xFFFF);
|
||||
// For our purposes, the extended checksum is OK if we're not using extended data
|
||||
|
||||
return baseChecksumOk && extendedChecksumOk;
|
||||
}
|
||||
|
||||
u8 Bytes[256];
|
||||
struct
|
||||
{
|
||||
u16 Version;
|
||||
u8 FavoriteColor;
|
||||
u8 BirthdayMonth;
|
||||
u8 BirthdayDay;
|
||||
u8 Unused0;
|
||||
char16_t Nickname[10];
|
||||
u16 NameLength;
|
||||
char16_t Message[26];
|
||||
u16 MessageLength;
|
||||
u8 AlarmHour;
|
||||
u8 AlarmMinute;
|
||||
u8 Unknown0[2];
|
||||
u8 AlarmFlags;
|
||||
u8 Unused1;
|
||||
u16 TouchCalibrationADC1[2];
|
||||
u8 TouchCalibrationPixel1[2];
|
||||
u16 TouchCalibrationADC2[2];
|
||||
u8 TouchCalibrationPixel2[2];
|
||||
u16 Settings;
|
||||
u8 Year;
|
||||
u8 Unknown1;
|
||||
u32 RTCOffset;
|
||||
u8 Unused2[4];
|
||||
u16 UpdateCounter;
|
||||
u16 Checksum;
|
||||
union
|
||||
{
|
||||
u8 Unused3[0x8C];
|
||||
struct
|
||||
{
|
||||
u8 Unknown0;
|
||||
Language ExtendedLanguage; // padded
|
||||
u16 SupportedLanguageMask;
|
||||
u8 Unused0[0x86];
|
||||
u16 Checksum;
|
||||
} ExtendedSettings;
|
||||
};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(UserData) == 256, "UserData should be 256 bytes");
|
||||
|
||||
class Firmware
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructs a default firmware blob
|
||||
* filled with data necessary for booting and configuring NDS games.
|
||||
* The Wi-fi section has one access point configured with melonDS's defaults.
|
||||
* Will not contain executable code.
|
||||
* @param consoletype Console type to use. 1 for DSi, 0 for NDS.
|
||||
*/
|
||||
explicit Firmware(int consoletype);
|
||||
|
||||
/**
|
||||
* Loads a firmware blob from the given file.
|
||||
* Will rewind the file's stream offset to its initial position when finished.
|
||||
*/
|
||||
explicit Firmware(Platform::FileHandle* file);
|
||||
|
||||
/**
|
||||
* Constructs a firmware blob from a copy of the given data.
|
||||
* @param data Buffer containing the firmware data.
|
||||
* @param length Length of the buffer in bytes.
|
||||
* If too short, the firmware will be padded with zeroes.
|
||||
* If too long, the extra data will be ignored.
|
||||
*/
|
||||
Firmware(const u8* data, u32 length);
|
||||
Firmware(const Firmware& other);
|
||||
Firmware(Firmware&& other) noexcept;
|
||||
Firmware& operator=(const Firmware& other);
|
||||
Firmware& operator=(Firmware&& other) noexcept;
|
||||
~Firmware();
|
||||
|
||||
[[nodiscard]] FirmwareHeader& Header() { return *reinterpret_cast<FirmwareHeader*>(FirmwareBuffer); }
|
||||
[[nodiscard]] const FirmwareHeader& Header() const { return *reinterpret_cast<const FirmwareHeader*>(FirmwareBuffer); }
|
||||
|
||||
/// @return The offset of the first basic Wi-fi settings block in the firmware
|
||||
/// (not the extended Wi-fi settings block used by the DSi).
|
||||
/// @see WifiAccessPointPosition
|
||||
[[nodiscard]] u32 WifiAccessPointOffset() const { return UserDataOffset() - 0x400; }
|
||||
|
||||
/// @return The address of the first basic Wi-fi settings block in the firmware.
|
||||
[[nodiscard]] u8* WifiAccessPointPosition() { return FirmwareBuffer + WifiAccessPointOffset(); }
|
||||
[[nodiscard]] const u8* WifiAccessPointPosition() const { return FirmwareBuffer + WifiAccessPointOffset(); }
|
||||
|
||||
[[nodiscard]] const std::array<WifiAccessPoint, 3>& AccessPoints() const
|
||||
{
|
||||
// An std::array is a wrapper around a C array, so this cast is fine.
|
||||
return *reinterpret_cast<const std::array<WifiAccessPoint, 3>*>(WifiAccessPointPosition());
|
||||
}
|
||||
|
||||
[[nodiscard]] std::array<WifiAccessPoint, 3>& AccessPoints()
|
||||
{
|
||||
// An std::array is a wrapper around a C array, so this cast is fine.
|
||||
return *reinterpret_cast<std::array<WifiAccessPoint, 3>*>(WifiAccessPointPosition());
|
||||
}
|
||||
|
||||
/// @returns \c true if this firmware image contains bootable code.
|
||||
/// @note Non-bootable firmware can still be valid;
|
||||
/// DSi firmware is non-bootable for instance.
|
||||
/// If this firmware is not bootable, then melonDS should use direct-boot mode.
|
||||
[[nodiscard]] bool IsBootable() const;
|
||||
|
||||
/// @return The address of the first extended Wi-fi settings block in the firmware.
|
||||
/// @warning Only meaningful if this is DSi firmware.
|
||||
[[nodiscard]] u32 ExtendedAccessPointOffset() const { return UserDataOffset() + EXTENDED_WIFI_SETTINGS_OFFSET; }
|
||||
[[nodiscard]] u8* ExtendedAccessPointPosition() { return FirmwareBuffer + ExtendedAccessPointOffset(); }
|
||||
[[nodiscard]] const u8* ExtendedAccessPointPosition() const { return FirmwareBuffer + ExtendedAccessPointOffset(); }
|
||||
|
||||
[[nodiscard]] const std::array<ExtendedWifiAccessPoint, 3>& ExtendedAccessPoints() const
|
||||
{
|
||||
// An std::array is a wrapper around a C array, so this cast is fine.
|
||||
return *reinterpret_cast<const std::array<ExtendedWifiAccessPoint, 3>*>(ExtendedAccessPointPosition());
|
||||
}
|
||||
|
||||
[[nodiscard]] std::array<ExtendedWifiAccessPoint, 3>& ExtendedAccessPoints()
|
||||
{
|
||||
// An std::array is a wrapper around a C array, so this cast is fine.
|
||||
return *reinterpret_cast<std::array<ExtendedWifiAccessPoint, 3>*>(ExtendedAccessPointPosition());
|
||||
}
|
||||
|
||||
/// @return The pointer to the firmware buffer,
|
||||
/// or \c nullptr if this object is invalid
|
||||
/// (e.g. it was moved from, or its constructor failed).
|
||||
[[nodiscard]] u8* Buffer() { return FirmwareBuffer; }
|
||||
[[nodiscard]] const u8* Buffer() const { return FirmwareBuffer; }
|
||||
|
||||
[[nodiscard]] u32 Length() const { return FirmwareBufferLength; }
|
||||
[[nodiscard]] u32 Mask() const { return FirmwareMask; }
|
||||
|
||||
/// @return The offset of the first user data section in the firmware.
|
||||
/// @see UserDataPosition
|
||||
[[nodiscard]] u32 UserDataOffset() const { return Header().UserSettingsOffset << 3; }
|
||||
|
||||
/// @return The address of the first user data section in the firmware.
|
||||
/// @see UserDataOffset
|
||||
[[nodiscard]] u8* UserDataPosition() { return FirmwareBuffer + UserDataOffset(); }
|
||||
[[nodiscard]] const u8* UserDataPosition() const { return FirmwareBuffer + UserDataOffset(); }
|
||||
|
||||
|
||||
/// @return Reference to the two user data sections.
|
||||
/// @note Either \c UserData object could be the "effective" one,
|
||||
/// so prefer using \c EffectiveUserData() if you're not modifying both.
|
||||
[[nodiscard]] const std::array<union UserData, 2>& UserData() const
|
||||
{
|
||||
// An std::array is a wrapper around a C array, so this cast is fine.
|
||||
return *reinterpret_cast<const std::array<union UserData, 2>*>(UserDataPosition());
|
||||
};
|
||||
|
||||
/**
|
||||
* @return Reference to the two user data sections.
|
||||
* @note Either \c UserData object could be the "effective" one,
|
||||
* so prefer using \c EffectiveUserData() if you're not modifying both.
|
||||
* @warning Remember to call UserData::UpdateChecksum() after modifying any of its fields.
|
||||
*/
|
||||
[[nodiscard]] std::array<union UserData, 2>& UserData()
|
||||
{
|
||||
// An std::array is a wrapper around a C array, so this cast is fine.
|
||||
return *reinterpret_cast<std::array<union UserData, 2>*>(UserDataPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Reference to whichever of the two user data sections
|
||||
* will be used by the firmware.
|
||||
* Specifically, the firmware will use whichever one has the valid checksum
|
||||
* (or the newer one if they're both valid).
|
||||
*/
|
||||
[[nodiscard]] const union UserData& EffectiveUserData() const;
|
||||
|
||||
/**
|
||||
* @return Reference to whichever of the two user data sections
|
||||
* has the highest update counter.
|
||||
*/
|
||||
[[nodiscard]] union UserData& EffectiveUserData();
|
||||
|
||||
/// Updates the checksums of all used sections of the firmware.
|
||||
void UpdateChecksums();
|
||||
private:
|
||||
u8* FirmwareBuffer;
|
||||
u32 FirmwareBufferLength;
|
||||
u32 FirmwareMask;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //MELONDS_SPI_FIRMWARE_H
|
11
src/Wifi.cpp
11
src/Wifi.cpp
@ -176,6 +176,7 @@ void DeInit()
|
||||
|
||||
void Reset()
|
||||
{
|
||||
using namespace SPI_Firmware;
|
||||
memset(RAM, 0, 0x2000);
|
||||
memset(IO, 0, 0x1000);
|
||||
|
||||
@ -215,15 +216,15 @@ void Reset()
|
||||
}
|
||||
#undef BBREG_FIXED
|
||||
|
||||
RFVersion = SPI_Firmware::GetRFVersion();
|
||||
RFVersion = GetFirmware()->Header().RFChipType;
|
||||
memset(RFRegs, 0, 4*0x40);
|
||||
|
||||
u8 console = SPI_Firmware::GetConsoleType();
|
||||
if (console == 0xFF)
|
||||
FirmwareConsoleType console = GetFirmware()->Header().ConsoleType;
|
||||
if (console == FirmwareConsoleType::DS)
|
||||
IOPORT(0x000) = 0x1440;
|
||||
else if (console == 0x20)
|
||||
else if (console == FirmwareConsoleType::DSLite)
|
||||
IOPORT(0x000) = 0xC340;
|
||||
else if (NDS::ConsoleType == 1 && console == 0x57)
|
||||
else if (NDS::ConsoleType == 1 && console == FirmwareConsoleType::DSi)
|
||||
IOPORT(0x000) = 0xC340; // DSi has the modern DS-wifi variant
|
||||
else
|
||||
{
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "LAN_PCap.h"
|
||||
#include "LocalMP.h"
|
||||
#include "OSD.h"
|
||||
#include "SPI_Firmware.h"
|
||||
|
||||
#ifdef __WIN32__
|
||||
#define fseek _fseeki64
|
||||
@ -249,13 +250,6 @@ std::string GetConfigString(ConfigEntry entry)
|
||||
{
|
||||
switch (entry)
|
||||
{
|
||||
case BIOS9Path: return Config::BIOS9Path;
|
||||
case BIOS7Path: return Config::BIOS7Path;
|
||||
case FirmwarePath: return Config::FirmwarePath;
|
||||
|
||||
case DSi_BIOS9Path: return Config::DSiBIOS9Path;
|
||||
case DSi_BIOS7Path: return Config::DSiBIOS7Path;
|
||||
case DSi_FirmwarePath: return Config::DSiFirmwarePath;
|
||||
case DSi_NANDPath: return Config::DSiNANDPath;
|
||||
|
||||
case DLDI_ImagePath: return Config::DLDISDPath;
|
||||
@ -584,7 +578,36 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen
|
||||
ROMManager::GBASave->RequestFlush(savedata, savelen, writeoffset, writelen);
|
||||
}
|
||||
|
||||
void WriteFirmware(const SPI_Firmware::Firmware& firmware, u32 writeoffset, u32 writelen)
|
||||
{
|
||||
if (!ROMManager::FirmwareSave)
|
||||
return;
|
||||
|
||||
if (firmware.Header().Identifier != SPI_Firmware::GENERATED_FIRMWARE_IDENTIFIER)
|
||||
{ // If this is not the default built-in firmware...
|
||||
// ...then write the whole thing back.
|
||||
ROMManager::FirmwareSave->RequestFlush(firmware.Buffer(), firmware.Length(), writeoffset, writelen);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 eapstart = firmware.ExtendedAccessPointOffset();
|
||||
u32 eapend = eapstart + sizeof(firmware.ExtendedAccessPoints());
|
||||
|
||||
u32 apstart = firmware.WifiAccessPointOffset();
|
||||
u32 apend = apstart + sizeof(firmware.AccessPoints());
|
||||
|
||||
// assert that the extended access points come just before the regular ones
|
||||
assert(eapend == apstart);
|
||||
|
||||
if (eapstart <= writeoffset && writeoffset < apend)
|
||||
{ // If we're writing to the access points...
|
||||
const u8* buffer = firmware.ExtendedAccessPointPosition();
|
||||
u32 length = sizeof(firmware.ExtendedAccessPoints()) + sizeof(firmware.AccessPoints());
|
||||
ROMManager::FirmwareSave->RequestFlush(buffer, length, writeoffset - eapstart, writelen);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool MP_Init()
|
||||
{
|
||||
|
@ -16,9 +16,14 @@
|
||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <fstream>
|
||||
@ -35,7 +40,13 @@
|
||||
#include "DSi.h"
|
||||
#include "SPI.h"
|
||||
#include "DSi_I2C.h"
|
||||
#include "FreeBIOS.h"
|
||||
|
||||
using std::make_unique;
|
||||
using std::pair;
|
||||
using std::string;
|
||||
using std::tie;
|
||||
using std::unique_ptr;
|
||||
using namespace Platform;
|
||||
|
||||
namespace ROMManager
|
||||
@ -53,6 +64,7 @@ std::string BaseGBAAssetName = "";
|
||||
|
||||
SaveManager* NDSSave = nullptr;
|
||||
SaveManager* GBASave = nullptr;
|
||||
std::unique_ptr<SaveManager> FirmwareSave = nullptr;
|
||||
|
||||
std::unique_ptr<Savestate> BackupState = nullptr;
|
||||
bool SavestateLoaded = false;
|
||||
@ -462,6 +474,94 @@ void LoadCheats()
|
||||
AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr);
|
||||
}
|
||||
|
||||
void LoadBIOSFiles()
|
||||
{
|
||||
if (Config::ExternalBIOSEnable)
|
||||
{
|
||||
if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read))
|
||||
{
|
||||
FileRewind(f);
|
||||
FileRead(NDS::ARM9BIOS, sizeof(NDS::ARM9BIOS), 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM9 BIOS loaded from %s\n", Config::BIOS9Path.c_str());
|
||||
Platform::CloseFile(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM9 BIOS not found\n");
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
((u32*)NDS::ARM9BIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS7Path, FileMode::Read))
|
||||
{
|
||||
FileRead(NDS::ARM7BIOS, sizeof(NDS::ARM7BIOS), 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM7 BIOS loaded from\n", Config::BIOS7Path.c_str());
|
||||
Platform::CloseFile(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM7 BIOS not found\n");
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
((u32*)NDS::ARM7BIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Info, "Using built-in ARM7 and ARM9 BIOSes\n");
|
||||
memcpy(NDS::ARM9BIOS, bios_arm9_bin, sizeof(bios_arm9_bin));
|
||||
memcpy(NDS::ARM7BIOS, bios_arm7_bin, sizeof(bios_arm7_bin));
|
||||
}
|
||||
|
||||
if (Config::ConsoleType == 1)
|
||||
{
|
||||
if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS9Path, FileMode::Read))
|
||||
{
|
||||
FileRead(DSi::ARM9iBIOS, sizeof(DSi::ARM9iBIOS), 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str());
|
||||
Platform::CloseFile(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM9i BIOS not found\n");
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
((u32*)DSi::ARM9iBIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read))
|
||||
{
|
||||
// TODO: check if the first 32 bytes are crapoed
|
||||
FileRead(DSi::ARM7iBIOS, sizeof(DSi::ARM7iBIOS), 1, f);
|
||||
|
||||
Log(LogLevel::Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str());
|
||||
CloseFile(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Warn, "ARM7i BIOS not found\n");
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
((u32*)DSi::ARM7iBIOS)[i] = 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
if (!Config::DSiFullBIOSBoot)
|
||||
{
|
||||
// herp
|
||||
*(u32*)&DSi::ARM9iBIOS[0] = 0xEAFFFFFE;
|
||||
*(u32*)&DSi::ARM7iBIOS[0] = 0xEAFFFFFE;
|
||||
|
||||
// TODO!!!!
|
||||
// hax the upper 32K out of the goddamn DSi
|
||||
// done that :) -pcy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnableCheats(bool enable)
|
||||
{
|
||||
CheatsOn = enable;
|
||||
@ -492,6 +592,7 @@ void Reset()
|
||||
{
|
||||
NDS::SetConsoleType(Config::ConsoleType);
|
||||
if (Config::ConsoleType == 1) EjectGBACart();
|
||||
LoadBIOSFiles();
|
||||
NDS::Reset();
|
||||
SetBatteryLevels();
|
||||
|
||||
@ -513,6 +614,28 @@ void Reset()
|
||||
GBASave->SetPath(newsave, false);
|
||||
}
|
||||
|
||||
if (FirmwareSave)
|
||||
{
|
||||
std::string oldsave = FirmwareSave->GetPath();
|
||||
string newsave;
|
||||
if (Config::ExternalBIOSEnable)
|
||||
{
|
||||
if (Config::ConsoleType == 1)
|
||||
newsave = Config::DSiFirmwarePath + Platform::InstanceFileSuffix();
|
||||
else
|
||||
newsave = Config::FirmwarePath + Platform::InstanceFileSuffix();
|
||||
}
|
||||
else
|
||||
{
|
||||
newsave = Config::WifiSettingsPath + Platform::InstanceFileSuffix();
|
||||
}
|
||||
|
||||
if (oldsave != newsave)
|
||||
{ // If the player toggled the ConsoleType or ExternalBIOSEnable...
|
||||
FirmwareSave->SetPath(newsave, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!BaseROMName.empty())
|
||||
{
|
||||
if (Config::DirectBoot || NDS::NeedsDirectBoot())
|
||||
@ -527,6 +650,11 @@ bool LoadBIOS()
|
||||
{
|
||||
NDS::SetConsoleType(Config::ConsoleType);
|
||||
|
||||
LoadBIOSFiles();
|
||||
|
||||
if (!InstallFirmware())
|
||||
return false;
|
||||
|
||||
if (NDS::NeedsDirectBoot())
|
||||
return false;
|
||||
|
||||
@ -638,6 +766,222 @@ void ClearBackupState()
|
||||
}
|
||||
}
|
||||
|
||||
// We want both the firmware object and the path that was used to load it,
|
||||
// since we'll need to give it to the save manager later
|
||||
pair<unique_ptr<SPI_Firmware::Firmware>, string> LoadFirmwareFromFile()
|
||||
{
|
||||
string loadedpath;
|
||||
unique_ptr<SPI_Firmware::Firmware> firmware = nullptr;
|
||||
string firmwarepath = Config::ConsoleType == 0 ? Config::FirmwarePath : Config::DSiFirmwarePath;
|
||||
|
||||
Log(LogLevel::Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str());
|
||||
|
||||
string firmwareinstancepath = firmwarepath + Platform::InstanceFileSuffix();
|
||||
|
||||
loadedpath = firmwareinstancepath;
|
||||
FileHandle* f = Platform::OpenLocalFile(firmwareinstancepath, FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
loadedpath = firmwarepath;
|
||||
f = Platform::OpenLocalFile(firmwarepath, FileMode::Read);
|
||||
}
|
||||
|
||||
if (f)
|
||||
{
|
||||
firmware = make_unique<SPI_Firmware::Firmware>(f);
|
||||
if (!firmware->Buffer())
|
||||
{
|
||||
Log(LogLevel::Warn, "Couldn't read firmware file!\n");
|
||||
firmware = nullptr;
|
||||
loadedpath = "";
|
||||
}
|
||||
|
||||
CloseFile(f);
|
||||
}
|
||||
|
||||
return std::make_pair(std::move(firmware), loadedpath);
|
||||
}
|
||||
|
||||
pair<unique_ptr<SPI_Firmware::Firmware>, string> GenerateDefaultFirmware()
|
||||
{
|
||||
using namespace SPI_Firmware;
|
||||
// Construct the default firmware...
|
||||
string settingspath;
|
||||
std::unique_ptr<Firmware> firmware = std::make_unique<Firmware>(Config::ConsoleType);
|
||||
assert(firmware->Buffer() != nullptr);
|
||||
|
||||
// Try to open the instanced Wi-fi settings, falling back to the regular Wi-fi settings if they don't exist.
|
||||
// We don't need to save the whole firmware, just the part that may actually change.
|
||||
std::string wfcsettingspath = Platform::GetConfigString(ConfigEntry::WifiSettingsPath);
|
||||
settingspath = wfcsettingspath + Platform::InstanceFileSuffix();
|
||||
FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read);
|
||||
if (!f)
|
||||
{
|
||||
settingspath = wfcsettingspath;
|
||||
f = Platform::OpenLocalFile(settingspath, FileMode::Read);
|
||||
}
|
||||
|
||||
// If using generated firmware, we keep the wi-fi settings on the host disk separately.
|
||||
// Wi-fi access point data includes Nintendo WFC settings,
|
||||
// and if we didn't keep them then the player would have to reset them in each session.
|
||||
if (f)
|
||||
{ // If we have Wi-fi settings to load...
|
||||
constexpr unsigned TOTAL_WFC_SETTINGS_SIZE = 3 * (sizeof(WifiAccessPoint) + sizeof(ExtendedWifiAccessPoint));
|
||||
|
||||
// The access point and extended access point segments might
|
||||
// be in different locations depending on the firmware revision,
|
||||
// but our generated firmware always keeps them next to each other.
|
||||
// (Extended access points first, then regular ones.)
|
||||
|
||||
if (!FileRead(firmware->ExtendedAccessPointPosition(), TOTAL_WFC_SETTINGS_SIZE, 1, f))
|
||||
{ // If we couldn't read the Wi-fi settings from this file...
|
||||
Platform::Log(Platform::LogLevel::Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", wfcsettingspath.c_str());
|
||||
|
||||
firmware->AccessPoints() = {
|
||||
WifiAccessPoint(Config::ConsoleType),
|
||||
WifiAccessPoint(),
|
||||
WifiAccessPoint(),
|
||||
};
|
||||
|
||||
firmware->ExtendedAccessPoints() = {
|
||||
ExtendedWifiAccessPoint(),
|
||||
ExtendedWifiAccessPoint(),
|
||||
ExtendedWifiAccessPoint(),
|
||||
};
|
||||
}
|
||||
|
||||
firmware->UpdateChecksums();
|
||||
|
||||
CloseFile(f);
|
||||
}
|
||||
|
||||
// If we don't have Wi-fi settings to load,
|
||||
// then the defaults will have already been populated by the constructor.
|
||||
return std::make_pair(std::move(firmware), std::move(wfcsettingspath));
|
||||
}
|
||||
|
||||
void LoadUserSettingsFromConfig(SPI_Firmware::Firmware& firmware)
|
||||
{
|
||||
using namespace SPI_Firmware;
|
||||
UserData& currentData = firmware.EffectiveUserData();
|
||||
|
||||
// setting up username
|
||||
std::string orig_username = Platform::GetConfigString(Platform::Firm_Username);
|
||||
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);
|
||||
size_t usernameLength = std::min(username.length(), (size_t) 10);
|
||||
currentData.NameLength = usernameLength;
|
||||
memcpy(currentData.Nickname, username.data(), usernameLength * sizeof(char16_t));
|
||||
}
|
||||
|
||||
auto language = static_cast<Language>(Platform::GetConfigInt(Platform::Firm_Language));
|
||||
if (language != Language::Reserved)
|
||||
{ // If the frontend specifies a language (rather than using the existing value)...
|
||||
currentData.Settings &= ~Language::Reserved; // ..clear the existing language...
|
||||
currentData.Settings |= language; // ...and set the new one.
|
||||
}
|
||||
|
||||
// setting up color
|
||||
u8 favoritecolor = Platform::GetConfigInt(Platform::Firm_Color);
|
||||
if (favoritecolor != 0xFF)
|
||||
{
|
||||
currentData.FavoriteColor = favoritecolor;
|
||||
}
|
||||
|
||||
u8 birthmonth = Platform::GetConfigInt(Platform::Firm_BirthdayMonth);
|
||||
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);
|
||||
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);
|
||||
if (!orig_message.empty())
|
||||
{
|
||||
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);
|
||||
currentData.MessageLength = messageLength;
|
||||
memcpy(currentData.Message, message.data(), messageLength * sizeof(char16_t));
|
||||
}
|
||||
|
||||
MacAddress mac;
|
||||
bool rep = false;
|
||||
auto& header = firmware.Header();
|
||||
|
||||
memcpy(&mac, header.MacAddress.data(), sizeof(MacAddress));
|
||||
|
||||
|
||||
MacAddress configuredMac;
|
||||
rep = Platform::GetConfigArray(Platform::Firm_MAC, &configuredMac);
|
||||
rep &= (configuredMac != MacAddress());
|
||||
|
||||
if (rep)
|
||||
{
|
||||
mac = configuredMac;
|
||||
}
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
{
|
||||
rep = true;
|
||||
mac[3] += inst;
|
||||
mac[4] += inst*0x44;
|
||||
mac[5] += inst*0x10;
|
||||
}
|
||||
|
||||
if (rep)
|
||||
{
|
||||
mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC
|
||||
header.MacAddress = mac;
|
||||
header.UpdateChecksum();
|
||||
}
|
||||
|
||||
firmware.UpdateChecksums();
|
||||
}
|
||||
|
||||
bool InstallFirmware()
|
||||
{
|
||||
using namespace SPI_Firmware;
|
||||
FirmwareSave.reset();
|
||||
unique_ptr<Firmware> firmware;
|
||||
string firmwarepath;
|
||||
bool generated = false;
|
||||
|
||||
if (Config::ExternalBIOSEnable)
|
||||
{ // If we want to try loading a firmware dump...
|
||||
|
||||
tie(firmware, firmwarepath) = LoadFirmwareFromFile();
|
||||
if (!firmware)
|
||||
{ // Try to load the configured firmware dump. If that fails...
|
||||
Log(LogLevel::Warn, "Firmware not found! Generating default firmware.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!firmware)
|
||||
{ // If we haven't yet loaded firmware (either because the load failed or we want to use the default...)
|
||||
tie(firmware, firmwarepath) = GenerateDefaultFirmware();
|
||||
}
|
||||
|
||||
if (!firmware)
|
||||
return false;
|
||||
|
||||
if (Config::FirmwareOverrideSettings)
|
||||
{
|
||||
LoadUserSettingsFromConfig(*firmware);
|
||||
}
|
||||
|
||||
FirmwareSave = std::make_unique<SaveManager>(firmwarepath);
|
||||
|
||||
return InstallFirmware(std::move(firmware));
|
||||
}
|
||||
|
||||
bool LoadROM(QStringList filepath, bool reset)
|
||||
{
|
||||
if (filepath.empty()) return false;
|
||||
@ -733,10 +1077,16 @@ bool LoadROM(QStringList filepath, bool reset)
|
||||
BaseROMName = romname;
|
||||
BaseAssetName = romname.substr(0, romname.rfind('.'));
|
||||
|
||||
if (!InstallFirmware())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reset)
|
||||
{
|
||||
NDS::SetConsoleType(Config::ConsoleType);
|
||||
NDS::EjectCart();
|
||||
LoadBIOSFiles();
|
||||
NDS::Reset();
|
||||
SetBatteryLevels();
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "AREngine.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ROMManager
|
||||
@ -31,12 +32,14 @@ namespace ROMManager
|
||||
|
||||
extern SaveManager* NDSSave;
|
||||
extern SaveManager* GBASave;
|
||||
extern std::unique_ptr<SaveManager> FirmwareSave;
|
||||
|
||||
QString VerifySetup();
|
||||
void Reset();
|
||||
bool LoadBIOS();
|
||||
void ClearBackupState();
|
||||
|
||||
bool InstallFirmware();
|
||||
bool LoadROM(QStringList filepath, bool reset);
|
||||
void EjectCart();
|
||||
bool CartInserted();
|
||||
|
@ -58,11 +58,11 @@ SaveManager::~SaveManager()
|
||||
FlushSecondaryBuffer();
|
||||
}
|
||||
|
||||
if (SecondaryBuffer) delete[] SecondaryBuffer;
|
||||
SecondaryBuffer = nullptr;
|
||||
|
||||
delete SecondaryBufferLock;
|
||||
|
||||
if (Buffer) delete[] Buffer;
|
||||
Buffer = nullptr;
|
||||
}
|
||||
|
||||
std::string SaveManager::GetPath()
|
||||
@ -75,11 +75,17 @@ void SaveManager::SetPath(const std::string& path, bool reload)
|
||||
Path = path;
|
||||
|
||||
if (reload)
|
||||
{
|
||||
FileHandle* f = Platform::OpenFile(Path, FileMode::Read);
|
||||
if (f)
|
||||
{ // If we should load whatever file is at the new path...
|
||||
|
||||
if (FileHandle* f = Platform::OpenFile(Path, FileMode::Read))
|
||||
{
|
||||
FileRead(Buffer, 1, Length, f);
|
||||
if (u32 length = Platform::FileLength(f); length != Length)
|
||||
{ // If the new file is a different size, we need to re-allocate the buffer.
|
||||
Length = length;
|
||||
Buffer = std::make_unique<u8[]>(Length);
|
||||
}
|
||||
|
||||
FileRead(Buffer.get(), 1, Length, f);
|
||||
CloseFile(f);
|
||||
}
|
||||
}
|
||||
@ -91,12 +97,10 @@ void SaveManager::RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset,
|
||||
{
|
||||
if (Length != savelen)
|
||||
{
|
||||
if (Buffer) delete[] Buffer;
|
||||
|
||||
Length = savelen;
|
||||
Buffer = new u8[Length];
|
||||
Buffer = std::make_unique<u8[]>(Length);
|
||||
|
||||
memcpy(Buffer, savedata, Length);
|
||||
memcpy(Buffer.get(), savedata, Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -127,13 +131,11 @@ void SaveManager::CheckFlush()
|
||||
|
||||
if (SecondaryBufferLength != Length)
|
||||
{
|
||||
if (SecondaryBuffer) delete[] SecondaryBuffer;
|
||||
|
||||
SecondaryBufferLength = Length;
|
||||
SecondaryBuffer = new u8[SecondaryBufferLength];
|
||||
SecondaryBuffer = std::make_unique<u8[]>(SecondaryBufferLength);
|
||||
}
|
||||
|
||||
memcpy(SecondaryBuffer, Buffer, Length);
|
||||
memcpy(SecondaryBuffer.get(), Buffer.get(), Length);
|
||||
|
||||
FlushRequested = false;
|
||||
FlushVersion++;
|
||||
@ -172,15 +174,15 @@ void SaveManager::FlushSecondaryBuffer(u8* dst, u32 dstLength)
|
||||
SecondaryBufferLock->lock();
|
||||
if (dst)
|
||||
{
|
||||
memcpy(dst, SecondaryBuffer, SecondaryBufferLength);
|
||||
memcpy(dst, SecondaryBuffer.get(), SecondaryBufferLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileHandle* f = Platform::OpenFile(Path, FileMode::Write);
|
||||
if (f)
|
||||
{
|
||||
Log(LogLevel::Info, "SaveManager: Written\n");
|
||||
FileWrite(SecondaryBuffer, SecondaryBufferLength, 1, f);
|
||||
FileWrite(SecondaryBuffer.get(), SecondaryBufferLength, 1, f);
|
||||
Log(LogLevel::Info, "SaveManager: Wrote %u bytes to %s\n", SecondaryBufferLength, Path.c_str());
|
||||
CloseFile(f);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
|
||||
@ -51,12 +52,12 @@ private:
|
||||
|
||||
std::atomic_bool Running;
|
||||
|
||||
u8* Buffer;
|
||||
std::unique_ptr<u8[]> Buffer;
|
||||
u32 Length;
|
||||
bool FlushRequested;
|
||||
|
||||
QMutex* SecondaryBufferLock;
|
||||
u8* SecondaryBuffer;
|
||||
std::unique_ptr<u8[]> SecondaryBuffer;
|
||||
u32 SecondaryBufferLength;
|
||||
|
||||
time_t TimeAtLastFlushRequest;
|
||||
|
@ -513,6 +513,9 @@ void EmuThread::run()
|
||||
if (ROMManager::GBASave)
|
||||
ROMManager::GBASave->CheckFlush();
|
||||
|
||||
if (ROMManager::FirmwareSave)
|
||||
ROMManager::FirmwareSave->CheckFlush();
|
||||
|
||||
if (!oglContext)
|
||||
{
|
||||
FrontBufferLock.lock();
|
||||
|
Loading…
Reference in New Issue
Block a user