/* 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 #include #include "types.h" #include "Platform.h" namespace SPI_Firmware { u16 CRC16(const u8* data, u32 len, u32 start); using MacAddress = std::array; using IpAddress = std::array; 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; using MacAddress = std::array; 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 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"); 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(FirmwareBuffer); } [[nodiscard]] const FirmwareHeader& Header() const { return *reinterpret_cast(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& AccessPoints() const { // An std::array is a wrapper around a C array, so this cast is fine. return *reinterpret_cast*>(WifiAccessPointPosition()); } [[nodiscard]] std::array& AccessPoints() { // An std::array is a wrapper around a C array, so this cast is fine. return *reinterpret_cast*>(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& ExtendedAccessPoints() const { // An std::array is a wrapper around a C array, so this cast is fine. return *reinterpret_cast*>(ExtendedAccessPointPosition()); } [[nodiscard]] std::array& ExtendedAccessPoints() { // An std::array is a wrapper around a C array, so this cast is fine. return *reinterpret_cast*>(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& UserData() const { // An std::array is a wrapper around a C array, so this cast is fine. return *reinterpret_cast*>(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& UserData() { // An std::array is a wrapper around a C array, so this cast is fine. return *reinterpret_cast*>(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