melonDS/src/SPI_Firmware.h
2024-06-15 17:01:19 +02:00

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