mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 13:27:41 -07:00
570 lines
17 KiB
C++
570 lines
17 KiB
C++
/*
|
|
Copyright 2016-2024 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 melonDS
|
|
{
|
|
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;
|
|
|
|
using FirmwareIdentifier = std::array<u8, 4>;
|
|
|
|
constexpr FirmwareIdentifier GENERATED_FIRMWARE_IDENTIFIER = {'M', 'E', 'L', 'N'};
|
|
|
|
|
|
class Firmware
|
|
{
|
|
public:
|
|
|
|
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,
|
|
};
|
|
|
|
/**
|
|
* @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];
|
|
|
|
MacAddress MacAddr;
|
|
|
|
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 RTCClockAdjust;
|
|
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");
|
|
|
|
/**
|
|
* 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& GetHeader() { return *reinterpret_cast<FirmwareHeader*>(FirmwareBuffer); }
|
|
[[nodiscard]] const FirmwareHeader& GetHeader() 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 GetWifiAccessPointOffset() const { return GetUserDataOffset() - 0x400; }
|
|
|
|
/// @return The address of the first basic Wi-fi settings block in the firmware.
|
|
[[nodiscard]] u8* GetWifiAccessPointPosition() { return FirmwareBuffer + GetWifiAccessPointOffset(); }
|
|
[[nodiscard]] const u8* GetWifiAccessPointPosition() const { return FirmwareBuffer + GetWifiAccessPointOffset(); }
|
|
|
|
[[nodiscard]] const std::array<WifiAccessPoint, 3>& GetAccessPoints() const
|
|
{
|
|
// An std::array is a wrapper around a C array, so this cast is fine.
|
|
return *reinterpret_cast<const std::array<WifiAccessPoint, 3>*>(GetWifiAccessPointPosition());
|
|
}
|
|
|
|
[[nodiscard]] std::array<WifiAccessPoint, 3>& GetAccessPoints()
|
|
{
|
|
// An std::array is a wrapper around a C array, so this cast is fine.
|
|
return *reinterpret_cast<std::array<WifiAccessPoint, 3>*>(GetWifiAccessPointPosition());
|
|
}
|
|
|
|
/// @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 GetExtendedAccessPointOffset() const { return GetUserDataOffset() + EXTENDED_WIFI_SETTINGS_OFFSET; }
|
|
[[nodiscard]] u8* GetExtendedAccessPointPosition() { return FirmwareBuffer + GetExtendedAccessPointOffset(); }
|
|
[[nodiscard]] const u8* GetExtendedAccessPointPosition() const { return FirmwareBuffer + GetExtendedAccessPointOffset(); }
|
|
|
|
[[nodiscard]] const std::array<ExtendedWifiAccessPoint, 3>& GetExtendedAccessPoints() const
|
|
{
|
|
// An std::array is a wrapper around a C array, so this cast is fine.
|
|
return *reinterpret_cast<const std::array<ExtendedWifiAccessPoint, 3>*>(GetExtendedAccessPointPosition());
|
|
}
|
|
|
|
[[nodiscard]] std::array<ExtendedWifiAccessPoint, 3>& GetExtendedAccessPoints()
|
|
{
|
|
// An std::array is a wrapper around a C array, so this cast is fine.
|
|
return *reinterpret_cast<std::array<ExtendedWifiAccessPoint, 3>*>(GetExtendedAccessPointPosition());
|
|
}
|
|
|
|
/// @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 GetUserDataOffset() const { return GetHeader().UserSettingsOffset << 3; }
|
|
|
|
/// @return The address of the first user data section in the firmware.
|
|
/// @see UserDataOffset
|
|
[[nodiscard]] u8* GetUserDataPosition() { return FirmwareBuffer + GetUserDataOffset(); }
|
|
[[nodiscard]] const u8* GetUserDataPosition() const { return FirmwareBuffer + GetUserDataOffset(); }
|
|
|
|
|
|
/// @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>& GetUserData() 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>*>(GetUserDataPosition());
|
|
};
|
|
|
|
/**
|
|
* @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>& GetUserData()
|
|
{
|
|
// An std::array is a wrapper around a C array, so this cast is fine.
|
|
return *reinterpret_cast<std::array<union UserData, 2>*>(GetUserDataPosition());
|
|
}
|
|
|
|
/**
|
|
* @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& GetEffectiveUserData() const;
|
|
|
|
/**
|
|
* @return Reference to whichever of the two user data sections
|
|
* has the highest update counter.
|
|
*/
|
|
[[nodiscard]] union UserData& GetEffectiveUserData();
|
|
|
|
/// Fix the given firmware length to an acceptable length
|
|
u32 FixFirmwareLength(u32 originalLength);
|
|
|
|
/// Updates the checksums of all used sections of the firmware.
|
|
void UpdateChecksums();
|
|
private:
|
|
u8* FirmwareBuffer;
|
|
u32 FirmwareBufferLength;
|
|
u32 FirmwareMask;
|
|
};
|
|
|
|
}
|
|
#endif //MELONDS_SPI_FIRMWARE_H
|