mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-06-28 09:59:41 -06: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:

committed by
GitHub

parent
db963aa002
commit
5bfe51e670
@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user