741 lines
21 KiB
C++
741 lines
21 KiB
C++
#include "NDS.h"
|
|
#include "NDSCart.h"
|
|
#include "DSi.h"
|
|
#include "DSi_NAND.h"
|
|
#include "DSi_TMD.h"
|
|
#include "GPU3D_OpenGL.h"
|
|
#include "GPU3D_Compute.h"
|
|
#include "CRC32.h"
|
|
#include "FreeBIOS.h"
|
|
#include "SPI.h"
|
|
|
|
#include "BizPlatform/BizFile.h"
|
|
#include "BizTypes.h"
|
|
|
|
extern melonDS::NDS* CurrentNDS;
|
|
|
|
namespace ConsoleCreator
|
|
{
|
|
|
|
struct FirmwareSettings
|
|
{
|
|
bool OverrideSettings;
|
|
int UsernameLength;
|
|
char16_t Username[10];
|
|
int Language;
|
|
int BirthdayMonth;
|
|
int BirthdayDay;
|
|
int Color;
|
|
int MessageLength;
|
|
char16_t Message[26];
|
|
u8 MacAddress[6];
|
|
};
|
|
|
|
template <typename T>
|
|
static std::unique_ptr<T> CreateBiosImage(u8* biosData, u32 biosLength, std::optional<T> biosFallback = std::nullopt)
|
|
{
|
|
auto bios = std::make_unique<T>();
|
|
if (biosData)
|
|
{
|
|
if (biosLength != bios->size())
|
|
{
|
|
throw std::runtime_error("Invalid BIOS size");
|
|
}
|
|
|
|
memcpy(bios->data(), biosData, bios->size());
|
|
}
|
|
else
|
|
{
|
|
if (!biosFallback)
|
|
{
|
|
throw std::runtime_error("Failed to load BIOS");
|
|
}
|
|
|
|
memcpy(bios->data(), biosFallback->data(), bios->size());
|
|
}
|
|
|
|
return std::move(bios);
|
|
}
|
|
|
|
static void SanitizeExternalFirmware(melonDS::Firmware& firmware)
|
|
{
|
|
auto& header = firmware.GetHeader();
|
|
|
|
const bool isDSiFw = header.ConsoleType == melonDS::Firmware::FirmwareConsoleType::DSi;
|
|
const auto defaultHeader = melonDS::Firmware::FirmwareHeader{isDSiFw};
|
|
|
|
// the user data offset won't necessarily be 0x7FE00 & Mask, DSi/iQue use 0x7FC00 & Mask instead
|
|
// but we don't want to crash due to an invalid offset
|
|
const auto maxUserDataOffset = 0x7FE00 & firmware.Mask();
|
|
if (firmware.GetUserDataOffset() > maxUserDataOffset)
|
|
{
|
|
header.UserSettingsOffset = maxUserDataOffset >> 3;
|
|
}
|
|
|
|
if (isDSiFw)
|
|
{
|
|
memset(&header.Bytes[0x22], 0x00, 6);
|
|
memset(&header.Bytes[0x28], 0xFF, 2);
|
|
}
|
|
|
|
memcpy(&header.Bytes[0x2C], &defaultHeader.Bytes[0x2C], 0x136);
|
|
memset(&header.Bytes[0x162], 0xFF, 0x9E);
|
|
|
|
if (isDSiFw)
|
|
{
|
|
header.WifiBoard = defaultHeader.WifiBoard;
|
|
header.WifiFlash = defaultHeader.WifiFlash;
|
|
}
|
|
|
|
header.UpdateChecksum();
|
|
|
|
auto& aps = firmware.GetAccessPoints();
|
|
aps[0] = melonDS::Firmware::WifiAccessPoint{isDSiFw};
|
|
aps[1] = melonDS::Firmware::WifiAccessPoint{};
|
|
aps[2] = melonDS::Firmware::WifiAccessPoint{};
|
|
|
|
if (isDSiFw)
|
|
{
|
|
auto& exAps = firmware.GetExtendedAccessPoints();
|
|
exAps[0] = melonDS::Firmware::ExtendedWifiAccessPoint{};
|
|
exAps[1] = melonDS::Firmware::ExtendedWifiAccessPoint{};
|
|
exAps[2] = melonDS::Firmware::ExtendedWifiAccessPoint{};
|
|
}
|
|
}
|
|
|
|
static void FixFirmwareTouchscreenCalibration(melonDS::Firmware::UserData& userData)
|
|
{
|
|
userData.TouchCalibrationADC1[0] = 0;
|
|
userData.TouchCalibrationADC1[1] = 0;
|
|
userData.TouchCalibrationPixel1[0] = 0;
|
|
userData.TouchCalibrationPixel1[1] = 0;
|
|
userData.TouchCalibrationADC2[0] = 255 << 4;
|
|
userData.TouchCalibrationADC2[1] = 191 << 4;
|
|
userData.TouchCalibrationPixel2[0] = 255;
|
|
userData.TouchCalibrationPixel2[1] = 191;
|
|
}
|
|
|
|
static void SetFirmwareSettings(melonDS::Firmware::UserData& userData, FirmwareSettings& fwSettings)
|
|
{
|
|
memset(userData.Bytes, 0, 0x74);
|
|
|
|
userData.Version = 5;
|
|
FixFirmwareTouchscreenCalibration(userData);
|
|
|
|
userData.NameLength = fwSettings.UsernameLength;
|
|
memcpy(userData.Nickname, fwSettings.Username, sizeof(fwSettings.Username));
|
|
userData.Settings = fwSettings.Language | melonDS::Firmware::BacklightLevel::Max | 0xEC00;
|
|
userData.BirthdayMonth = fwSettings.BirthdayMonth;
|
|
userData.BirthdayDay = fwSettings.BirthdayDay;
|
|
userData.FavoriteColor = fwSettings.Color;
|
|
userData.MessageLength = fwSettings.MessageLength;
|
|
memcpy(userData.Message, fwSettings.Message, sizeof(fwSettings.Message));
|
|
|
|
if (userData.ExtendedSettings.Unknown0 == 1)
|
|
{
|
|
userData.ExtendedSettings.ExtendedLanguage = static_cast<melonDS::Firmware::Language>(fwSettings.Language & melonDS::Firmware::Language::Reserved);
|
|
memset(userData.ExtendedSettings.Unused0, 0, sizeof(userData.ExtendedSettings.Unused0));
|
|
|
|
if (!((1 << static_cast<u8>(userData.ExtendedSettings.ExtendedLanguage)) & userData.ExtendedSettings.SupportedLanguageMask))
|
|
{
|
|
userData.ExtendedSettings.ExtendedLanguage = melonDS::Firmware::Language::English;
|
|
userData.Settings &= ~melonDS::Firmware::Language::Reserved;
|
|
userData.Settings |= melonDS::Firmware::Language::English;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memset(userData.Unused3, 0xFF, sizeof(userData.Unused3));
|
|
}
|
|
|
|
// only extended settings should have Chinese / Korean
|
|
// note that melonDS::Firmware::Language::Reserved is Korean, so it's valid to have language set to that
|
|
if ((userData.Settings & melonDS::Firmware::Language::Reserved) >= melonDS::Firmware::Language::Chinese)
|
|
{
|
|
userData.Settings &= ~melonDS::Firmware::Language::Reserved;
|
|
userData.Settings |= melonDS::Firmware::Language::English;
|
|
}
|
|
}
|
|
|
|
static melonDS::Firmware CreateFirmware(u8* fwData, u32 fwLength, bool dsi, FirmwareSettings& fwSettings)
|
|
{
|
|
melonDS::Firmware firmware{dsi};
|
|
|
|
if (fwData)
|
|
{
|
|
firmware = melonDS::Firmware{fwData, fwLength};
|
|
|
|
if (firmware.Buffer())
|
|
{
|
|
// sanitize header, wifi calibration, and AP points
|
|
SanitizeExternalFirmware(firmware);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fwSettings.OverrideSettings = true;
|
|
}
|
|
|
|
if (!firmware.Buffer())
|
|
{
|
|
throw std::runtime_error("Failed to load firmware!");
|
|
}
|
|
|
|
for (auto& userData : firmware.GetUserData())
|
|
{
|
|
FixFirmwareTouchscreenCalibration(userData);
|
|
userData.UpdateChecksum();
|
|
}
|
|
|
|
if (fwSettings.OverrideSettings)
|
|
{
|
|
for (auto& userData : firmware.GetUserData())
|
|
{
|
|
SetFirmwareSettings(userData, fwSettings);
|
|
userData.UpdateChecksum();
|
|
}
|
|
|
|
melonDS::MacAddress mac;
|
|
static_assert(mac.size() == sizeof(fwSettings.MacAddress));
|
|
memcpy(mac.data(), fwSettings.MacAddress, mac.size());
|
|
auto& header = firmware.GetHeader();
|
|
header.MacAddr = mac;
|
|
header.UpdateChecksum();
|
|
}
|
|
|
|
return firmware;
|
|
}
|
|
|
|
static u8 GetDefaultCountryCode(melonDS::DSi_NAND::ConsoleRegion region)
|
|
{
|
|
// TODO: CountryCode probably should be configurable
|
|
// these defaults are also completely arbitrary
|
|
switch (region)
|
|
{
|
|
case melonDS::DSi_NAND::ConsoleRegion::Japan: return 0x01; // Japan
|
|
case melonDS::DSi_NAND::ConsoleRegion::USA: return 0x31; // United States
|
|
case melonDS::DSi_NAND::ConsoleRegion::Europe: return 0x6E; // United Kingdom
|
|
case melonDS::DSi_NAND::ConsoleRegion::Australia: return 0x41; // Australia
|
|
case melonDS::DSi_NAND::ConsoleRegion::China: return 0xA0; // China
|
|
case melonDS::DSi_NAND::ConsoleRegion::Korea: return 0x88; // Korea
|
|
default: return 0x31; // ???
|
|
}
|
|
}
|
|
|
|
static void SanitizeNandSettings(melonDS::DSi_NAND::DSiFirmwareSystemSettings& settings, melonDS::DSi_NAND::ConsoleRegion region)
|
|
{
|
|
memset(settings.Zero00, 0, sizeof(settings.Zero00));
|
|
settings.Version = 1;
|
|
settings.UpdateCounter = 0;
|
|
memset(settings.Zero01, 0, sizeof(settings.Zero01));
|
|
settings.BelowRAMAreaSize = 0x128;
|
|
// bit 0-1 are unknown (but usually 1)
|
|
// bit 2 indicates language set (?)
|
|
// bit 3 is wifi enable (really wifi LED enable; usually set)
|
|
// bit 24 set will indicate EULA is "agreed" to
|
|
settings.ConfigFlags = 0x0100000F;
|
|
settings.Zero02 = 0;
|
|
settings.CountryCode = GetDefaultCountryCode(region);
|
|
settings.RTCYear = 0;
|
|
settings.RTCOffset = 0;
|
|
memset(settings.Zero3, 0, sizeof(settings.Zero3));
|
|
settings.EULAVersion = 1;
|
|
memset(settings.Zero04, 0, sizeof(settings.Zero04));
|
|
settings.AlarmHour = 0;
|
|
settings.AlarmMinute = 0;
|
|
memset(settings.Zero05, 0, sizeof(settings.Zero05));
|
|
settings.AlarmEnable = false;
|
|
memset(settings.Zero06, 0, sizeof(settings.Zero06));
|
|
settings.Unknown0 = 0;
|
|
settings.Unknown1 = 3; // apparently 2 or 3
|
|
memset(settings.Zero07, 0, sizeof(settings.Zero07));
|
|
settings.SystemMenuMostRecentTitleID.fill(0);
|
|
settings.Unknown2[0] = 0x9C;
|
|
settings.Unknown2[1] = 0x20;
|
|
settings.Unknown2[2] = 0x01;
|
|
settings.Unknown2[3] = 0x02;
|
|
memset(settings.Zero08, 0, sizeof(settings.Zero08));
|
|
settings.Zero09 = 0;
|
|
settings.ParentalControlsFlags = 0;
|
|
memset(settings.Zero10, 0, sizeof(settings.Zero10));
|
|
settings.ParentalControlsRegion = 0;
|
|
settings.ParentalControlsYearsOfAgeRating = 0;
|
|
settings.ParentalControlsSecretQuestion = 0;
|
|
settings.Unknown3 = 0; // apparently 0 or 6 or 7
|
|
memset(settings.Zero11, 0, sizeof(settings.Zero11));
|
|
memset(settings.ParentalControlsPIN, 0, sizeof(settings.ParentalControlsPIN));
|
|
memset(settings.ParentalControlsSecretAnswer, 0, sizeof(settings.ParentalControlsSecretAnswer));
|
|
}
|
|
|
|
static melonDS::DSi_NAND::NANDImage CreateNandImage(
|
|
u8* nandData, u32 nandLength, std::unique_ptr<melonDS::DSiBIOSImage>& arm7Bios,
|
|
FirmwareSettings& fwSettings, bool clearNand,
|
|
u8* dsiWareData, u32 dsiWareLength, u8* tmdData, u32 tmdLength)
|
|
{
|
|
auto nand = melonDS::DSi_NAND::NANDImage{melonDS::Platform::CreateMemoryFile(nandData, nandLength), &arm7Bios->data()[0x8308]};
|
|
if (!nand)
|
|
{
|
|
throw std::runtime_error("Failed to parse DSi NAND!");
|
|
}
|
|
|
|
{
|
|
auto mount = melonDS::DSi_NAND::NANDMount(nand);
|
|
if (!mount)
|
|
{
|
|
throw std::runtime_error("Failed to mount DSi NAND!");
|
|
}
|
|
|
|
melonDS::DSi_NAND::DSiFirmwareSystemSettings settings{};
|
|
if (!mount.ReadUserData(settings))
|
|
{
|
|
throw std::runtime_error("Failed to read DSi NAND user data");
|
|
}
|
|
|
|
// serial data will contain the NAND's region
|
|
melonDS::DSi_NAND::DSiSerialData serialData;
|
|
if (!mount.ReadSerialData(serialData))
|
|
{
|
|
throw std::runtime_error("Failed to obtain serial data!");
|
|
}
|
|
|
|
if (fwSettings.OverrideSettings)
|
|
{
|
|
SanitizeNandSettings(settings, serialData.Region);
|
|
memset(settings.Nickname, 0, sizeof(settings.Nickname));
|
|
memcpy(settings.Nickname, fwSettings.Username, fwSettings.UsernameLength * 2);
|
|
settings.Language = static_cast<melonDS::Firmware::Language>(fwSettings.Language & melonDS::Firmware::Language::Reserved);
|
|
settings.FavoriteColor = fwSettings.Color;
|
|
settings.BirthdayMonth = fwSettings.BirthdayMonth;
|
|
settings.BirthdayDay = fwSettings.BirthdayDay;
|
|
memset(settings.Message, 0, sizeof(settings.Message));
|
|
memcpy(settings.Message, fwSettings.Message, fwSettings.MessageLength * 2);
|
|
|
|
if (!((1 << static_cast<u8>(settings.Language)) & serialData.SupportedLanguages))
|
|
{
|
|
// English is valid among all NANDs
|
|
settings.Language = melonDS::Firmware::Language::English;
|
|
}
|
|
}
|
|
|
|
settings.TouchCalibrationADC1[0] = 0;
|
|
settings.TouchCalibrationADC1[1] = 0;
|
|
settings.TouchCalibrationPixel1[0] = 0;
|
|
settings.TouchCalibrationPixel1[1] = 0;
|
|
settings.TouchCalibrationADC2[0] = 255 << 4;
|
|
settings.TouchCalibrationADC2[1] = 191 << 4;
|
|
settings.TouchCalibrationPixel2[0] = 255;
|
|
settings.TouchCalibrationPixel2[1] = 191;
|
|
|
|
settings.UpdateHash();
|
|
|
|
if (!mount.ApplyUserData(settings))
|
|
{
|
|
throw std::runtime_error("Failed to write DSi NAND user data");
|
|
}
|
|
|
|
if (clearNand)
|
|
{
|
|
// clear out DSiWare
|
|
constexpr u32 DSIWARE_CATEGORY = 0x00030004;
|
|
|
|
std::vector<u32> titlelist;
|
|
mount.ListTitles(DSIWARE_CATEGORY, titlelist);
|
|
|
|
for (auto& title : titlelist)
|
|
{
|
|
mount.DeleteTitle(DSIWARE_CATEGORY, title);
|
|
}
|
|
}
|
|
|
|
if (dsiWareData)
|
|
{
|
|
if (tmdLength != sizeof(melonDS::DSi_TMD::TitleMetadata))
|
|
{
|
|
throw std::runtime_error("TMD is the wrong size!");
|
|
}
|
|
|
|
melonDS::DSi_TMD::TitleMetadata tmd;
|
|
memcpy(&tmd, tmdData, sizeof(melonDS::DSi_TMD::TitleMetadata));
|
|
|
|
if (!mount.ImportTitle(dsiWareData, dsiWareLength, tmd, false))
|
|
{
|
|
throw std::runtime_error("Loading DSiWare failed!");
|
|
}
|
|
|
|
// verify that the imported title is supported by this NAND
|
|
// it will not actually appear otherwise
|
|
auto regionFlags = dsiWareLength > 0x1B0 ? dsiWareData[0x1B0] : 0;
|
|
if (!(regionFlags & (1 << static_cast<u8>(serialData.Region))))
|
|
{
|
|
throw std::runtime_error("Loaded NAND region does not support this DSiWare title!");
|
|
}
|
|
}
|
|
}
|
|
|
|
return nand;
|
|
}
|
|
|
|
enum class GBASaveType
|
|
{
|
|
NONE,
|
|
SRAM,
|
|
EEPROM512,
|
|
EEPROM,
|
|
FLASH512,
|
|
FLASH1M,
|
|
};
|
|
|
|
#include "GBASaveOverrides.h"
|
|
|
|
static GBASaveType FindGbaSaveType(const u8* gbaRomData, size_t gbaRomSize)
|
|
{
|
|
u32 crc = melonDS::CRC32(gbaRomData, gbaRomSize);
|
|
if (auto saveOverride = GbaCrcSaveTypeOverrides.find(crc); saveOverride != GbaCrcSaveTypeOverrides.end())
|
|
{
|
|
return saveOverride->second;
|
|
}
|
|
|
|
if (gbaRomSize >= 0xB0)
|
|
{
|
|
char gameId[4];
|
|
std::memcpy(gameId, &gbaRomData[0xAC], 4);
|
|
if (auto saveOverride = GbaGameIdSaveTypeOverrides.find(std::string(gameId, 4)); saveOverride != GbaGameIdSaveTypeOverrides.end())
|
|
{
|
|
return saveOverride->second;
|
|
}
|
|
}
|
|
|
|
if (memmem(gbaRomData, gbaRomSize, "EEPROM_V", strlen("EEPROM_V")))
|
|
{
|
|
return GBASaveType::EEPROM512;
|
|
}
|
|
|
|
if (memmem(gbaRomData, gbaRomSize, "SRAM_V", strlen("SRAM_V")))
|
|
{
|
|
return GBASaveType::SRAM;
|
|
}
|
|
|
|
if (memmem(gbaRomData, gbaRomSize, "FLASH_V", strlen("FLASH_V"))
|
|
|| memmem(gbaRomData, gbaRomSize, "FLASH512_V", strlen("FLASH512_V")))
|
|
{
|
|
return GBASaveType::FLASH512;
|
|
}
|
|
|
|
if (memmem(gbaRomData, gbaRomSize, "FLASH1M_V", strlen("FLASH1M_V")))
|
|
{
|
|
return GBASaveType::FLASH1M;
|
|
}
|
|
|
|
return GBASaveType::NONE;
|
|
}
|
|
|
|
static std::pair<std::unique_ptr<u8[]>, size_t> CreateBlankGbaSram(const u8* gbaRomData, size_t gbaRomSize)
|
|
{
|
|
auto saveType = FindGbaSaveType(gbaRomData, gbaRomSize);
|
|
|
|
if (saveType == GBASaveType::NONE)
|
|
{
|
|
return std::make_pair(nullptr, 0);
|
|
}
|
|
|
|
size_t size;
|
|
switch (saveType)
|
|
{
|
|
case GBASaveType::SRAM:
|
|
size = 0x8000;
|
|
break;
|
|
case GBASaveType::EEPROM512:
|
|
size = 0x200;
|
|
break;
|
|
case GBASaveType::EEPROM:
|
|
size = 0x2000;
|
|
break;
|
|
case GBASaveType::FLASH512:
|
|
size = 0x10000;
|
|
break;
|
|
case GBASaveType::FLASH1M:
|
|
size = 0x20000;
|
|
break;
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
auto data = std::make_unique<u8[]>(size);
|
|
memset(data.get(), 0xFF, size);
|
|
return std::make_pair(std::move(data), size);
|
|
}
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
struct DSiAutoLoad
|
|
{
|
|
u8 ID[4]; // "TLNC"
|
|
u8 Unknown1; // "usually 01h"
|
|
u8 Length; // starting from PrevTitleId
|
|
u16 CRC16; // covering Length bytes ("18h=norm")
|
|
u8 PrevTitleID[8]; // can be 0 ("anonymous")
|
|
u8 NewTitleID[8];
|
|
u32 Flags; // bit 0: is valid, bit 1-3: boot type ("01h=Cartridge, 02h=Landing, 03h=DSiware"), other bits unknown/unused
|
|
u32 Unused1; // this part is typically still checksummed
|
|
u8 Unused2[0xE0]; // this part isn't checksummed, but is 0 filled on erasing autoload data
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
static_assert(sizeof(DSiAutoLoad) == 0x100, "DSiAutoLoad wrong size");
|
|
|
|
ECL_EXPORT void ResetConsole(melonDS::NDS* nds, bool skipFw, u64 dsiTitleId)
|
|
{
|
|
nds->Reset();
|
|
|
|
if (skipFw || nds->NeedsDirectBoot())
|
|
{
|
|
if (nds->GetNDSCart())
|
|
{
|
|
nds->SetupDirectBoot("nds.rom");
|
|
}
|
|
else
|
|
{
|
|
auto* dsi = static_cast<melonDS::DSi*>(nds);
|
|
|
|
// set warm boot flag
|
|
dsi->I2C.GetBPTWL()->SetBootFlag(true);
|
|
|
|
// setup "auto-load" feature
|
|
DSiAutoLoad dsiAutoLoad;
|
|
memset(&dsiAutoLoad, 0, sizeof(dsiAutoLoad));
|
|
memcpy(dsiAutoLoad.ID, "TLNC", sizeof(dsiAutoLoad.ID));
|
|
dsiAutoLoad.Unknown1 = 0x01;
|
|
dsiAutoLoad.Length = 0x18;
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
dsiAutoLoad.NewTitleID[i] = dsiTitleId & 0xFF;
|
|
dsiTitleId >>= 8;
|
|
}
|
|
|
|
dsiAutoLoad.Flags |= (0x03 << 1) | 0x01;
|
|
dsiAutoLoad.Flags |= (1 << 4); // unknown bit, seems to be required to boot into games (errors otherwise?)
|
|
dsiAutoLoad.CRC16 = melonDS::CRC16((u8*)&dsiAutoLoad.PrevTitleID, dsiAutoLoad.Length, 0xFFFF);
|
|
memcpy(&nds->MainRAM[0x300], &dsiAutoLoad, sizeof(dsiAutoLoad));
|
|
}
|
|
}
|
|
|
|
nds->Start();
|
|
}
|
|
|
|
struct ConsoleCreationArgs
|
|
{
|
|
u8* NdsRomData;
|
|
u32 NdsRomLength;
|
|
|
|
u8* GbaRomData;
|
|
u32 GbaRomLength;
|
|
|
|
u8* Arm9BiosData;
|
|
u32 Arm9BiosLength;
|
|
|
|
u8* Arm7BiosData;
|
|
u32 Arm7BiosLength;
|
|
|
|
u8* FirmwareData;
|
|
u32 FirmwareLength;
|
|
|
|
u8* Arm9iBiosData;
|
|
u32 Arm9iBiosLength;
|
|
|
|
u8* Arm7iBiosData;
|
|
u32 Arm7iBiosLength;
|
|
|
|
u8* NandData;
|
|
u32 NandLength;
|
|
|
|
u8* DsiWareData;
|
|
u32 DsiWareLength;
|
|
|
|
u8* TmdData;
|
|
u32 TmdLength;
|
|
|
|
bool DSi;
|
|
bool ClearNAND;
|
|
bool SkipFW;
|
|
|
|
int BitDepth;
|
|
int Interpolation;
|
|
|
|
int ThreeDeeRenderer;
|
|
bool Threaded3D;
|
|
int ScaleFactor;
|
|
bool BetterPolygons;
|
|
bool HiResCoordinates;
|
|
|
|
int StartYear; // 0-99
|
|
int StartMonth; // 1-12
|
|
int StartDay; // 1-(28/29/30/31 depending on month/year)
|
|
int StartHour; // 0-23
|
|
int StartMinute; // 0-59
|
|
int StartSecond; // 0-59
|
|
|
|
FirmwareSettings FwSettings;
|
|
};
|
|
|
|
ECL_EXPORT melonDS::NDS* CreateConsole(ConsoleCreationArgs* args, char* error)
|
|
{
|
|
try
|
|
{
|
|
std::unique_ptr<melonDS::NDSCart::CartCommon> ndsRom = nullptr;
|
|
if (args->NdsRomData)
|
|
{
|
|
ndsRom = melonDS::NDSCart::ParseROM(args->NdsRomData, args->NdsRomLength, std::nullopt);
|
|
|
|
if (!ndsRom)
|
|
{
|
|
throw std::runtime_error("Failed to parse NDS ROM");
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<melonDS::GBACart::CartCommon> gbaRom = nullptr;
|
|
if (args->GbaRomData)
|
|
{
|
|
auto gbaSram = CreateBlankGbaSram(args->GbaRomData, args->GbaRomLength);
|
|
gbaRom = melonDS::GBACart::ParseROM(args->GbaRomData, args->GbaRomLength, gbaSram.first.get(), gbaSram.second);
|
|
|
|
if (!gbaRom)
|
|
{
|
|
throw std::runtime_error("Failed to parse GBA ROM");
|
|
}
|
|
}
|
|
|
|
auto arm9Bios = CreateBiosImage<melonDS::ARM9BIOSImage>(args->Arm9BiosData, args->Arm9BiosLength, melonDS::bios_arm9_bin);
|
|
auto arm7Bios = CreateBiosImage<melonDS::ARM7BIOSImage>(args->Arm7BiosData, args->Arm7BiosLength, melonDS::bios_arm7_bin);
|
|
auto firmware = CreateFirmware(args->FirmwareData, args->FirmwareLength, args->DSi, args->FwSettings);
|
|
|
|
auto bitDepth = static_cast<melonDS::AudioBitDepth>(args->BitDepth);
|
|
auto interpolation = static_cast<melonDS::AudioInterpolation>(args->Interpolation);
|
|
|
|
std::unique_ptr<melonDS::Renderer3D> renderer3d;
|
|
switch (args->ThreeDeeRenderer)
|
|
{
|
|
case 0:
|
|
{
|
|
auto softRenderer = std::make_unique<melonDS::SoftRenderer>();
|
|
// SetThreaded needs the nds GPU field, so can't do this now
|
|
// softRenderer->SetThreaded(args->Threaded3D, nds->GPU);
|
|
renderer3d = std::move(softRenderer);
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
auto glRenderer = melonDS::GLRenderer::New();
|
|
glRenderer->SetRenderSettings(args->BetterPolygons, args->ScaleFactor);
|
|
renderer3d = std::move(glRenderer);
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
auto computeRenderer = melonDS::ComputeRenderer::New();
|
|
computeRenderer->SetRenderSettings(args->ScaleFactor, args->HiResCoordinates);
|
|
renderer3d = std::move(computeRenderer);
|
|
break;
|
|
}
|
|
default:
|
|
throw std::runtime_error("Unknown 3DRenderer!");
|
|
}
|
|
|
|
int currentShader, shadersCount;
|
|
while (renderer3d->NeedsShaderCompile())
|
|
{
|
|
renderer3d->ShaderCompileStep(currentShader, shadersCount);
|
|
}
|
|
|
|
std::unique_ptr<melonDS::NDS> nds;
|
|
|
|
if (args->DSi)
|
|
{
|
|
auto arm9iBios = CreateBiosImage<melonDS::DSiBIOSImage>(args->Arm9iBiosData, args->Arm9iBiosLength);
|
|
auto arm7iBios = CreateBiosImage<melonDS::DSiBIOSImage>(args->Arm7iBiosData, args->Arm7iBiosLength);
|
|
|
|
// upstream applies this patch to overwrite the reset vector for non-full boots
|
|
static const u8 dsiBiosPatch[] = { 0xFE, 0xFF, 0xFF, 0xEA };
|
|
memcpy(arm9iBios->data(), dsiBiosPatch, sizeof(dsiBiosPatch));
|
|
memcpy(arm7iBios->data(), dsiBiosPatch, sizeof(dsiBiosPatch));
|
|
|
|
auto nandImage = CreateNandImage(
|
|
args->NandData, args->NandLength, arm7iBios,
|
|
args->FwSettings, args->ClearNAND,
|
|
args->DsiWareData, args->DsiWareLength, args->TmdData, args->TmdLength);
|
|
|
|
melonDS::DSiArgs dsiArgs
|
|
{
|
|
std::move(ndsRom),
|
|
std::move(gbaRom),
|
|
std::move(arm9Bios),
|
|
std::move(arm7Bios),
|
|
std::move(firmware),
|
|
std::nullopt,
|
|
bitDepth,
|
|
interpolation,
|
|
std::nullopt,
|
|
std::move(renderer3d),
|
|
// dsi specific args
|
|
std::move(arm9iBios),
|
|
std::move(arm7iBios),
|
|
std::move(nandImage),
|
|
std::nullopt,
|
|
false,
|
|
};
|
|
|
|
nds = std::make_unique<melonDS::DSi>(std::move(dsiArgs));
|
|
}
|
|
else
|
|
{
|
|
melonDS::NDSArgs ndsArgs
|
|
{
|
|
std::move(ndsRom),
|
|
std::move(gbaRom),
|
|
std::move(arm9Bios),
|
|
std::move(arm7Bios),
|
|
std::move(firmware),
|
|
std::nullopt,
|
|
bitDepth,
|
|
interpolation,
|
|
std::nullopt,
|
|
std::move(renderer3d)
|
|
};
|
|
|
|
nds = std::make_unique<melonDS::NDS>(std::move(ndsArgs));
|
|
}
|
|
|
|
if (args->ThreeDeeRenderer == 0)
|
|
{
|
|
auto& softRenderer = static_cast<melonDS::SoftRenderer&>(nds->GetRenderer3D());
|
|
softRenderer.SetThreaded(args->Threaded3D, nds->GPU);
|
|
}
|
|
|
|
nds->RTC.SetDateTime(args->StartYear, args->StartMonth, args->StartDay,
|
|
args->StartHour, args->StartMinute, args->StartSecond);
|
|
|
|
u64 dsiWareId = 0;
|
|
if (args->DsiWareLength >= 0x238)
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
dsiWareId <<= 8;
|
|
dsiWareId |= args->DsiWareData[0x237 - i];
|
|
}
|
|
}
|
|
|
|
ResetConsole(nds.get(), args->SkipFW, dsiWareId);
|
|
|
|
CurrentNDS = nds.release();
|
|
return CurrentNDS;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
strncpy(error, e.what(), 1024);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
}
|