/* Copyright 2016-2022 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/. */ #include #include #include #include #ifdef ARCHIVE_SUPPORT_ENABLED #include "ArchiveUtil.h" #endif #include "ROMManager.h" #include "Config.h" #include "Platform.h" #include "NDS.h" #include "DSi.h" namespace ROMManager { int CartType = -1; std::string BaseROMDir = ""; std::string BaseROMName = ""; std::string BaseAssetName = ""; int GBACartType = -1; std::string BaseGBAROMDir = ""; std::string BaseGBAROMName = ""; std::string BaseGBAAssetName = ""; SaveManager* NDSSave = nullptr; SaveManager* GBASave = nullptr; bool SavestateLoaded = false; std::string PreviousSaveFile = ""; ARCodeFile* CheatFile = nullptr; bool CheatsOn = false; int LastSep(std::string path) { int i = path.length() - 1; while (i >= 0) { if (path[i] == '/' || path[i] == '\\') return i; i--; } return -1; } std::string GetAssetPath(bool gba, std::string configpath, std::string ext, std::string file="") { if (configpath.empty()) configpath = gba ? BaseGBAROMDir : BaseROMDir; if (file.empty()) { file = gba ? BaseGBAAssetName : BaseAssetName; if (file.empty()) file = "firmware"; } for (;;) { int i = configpath.length() - 1; if (configpath[i] == '/' || configpath[i] == '\\') configpath = configpath.substr(0, i); else break; } if (!configpath.empty()) configpath += "/"; return configpath + file + ext; } QString VerifyDSBIOS() { FILE* f; long len; f = Platform::OpenLocalFile(Config::BIOS9Path, "rb"); if (!f) return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings."; fseek(f, 0, SEEK_END); len = ftell(f); if (len != 0x1000) { fclose(f); return "DS ARM9 BIOS is not a valid BIOS dump."; } fclose(f); f = Platform::OpenLocalFile(Config::BIOS7Path, "rb"); if (!f) return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings."; fseek(f, 0, SEEK_END); len = ftell(f); if (len != 0x4000) { fclose(f); return "DS ARM7 BIOS is not a valid BIOS dump."; } fclose(f); return ""; } QString VerifyDSiBIOS() { FILE* f; long len; // TODO: check the first 32 bytes f = Platform::OpenLocalFile(Config::DSiBIOS9Path, "rb"); if (!f) return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings."; fseek(f, 0, SEEK_END); len = ftell(f); if (len != 0x10000) { fclose(f); return "DSi ARM9 BIOS is not a valid BIOS dump."; } fclose(f); f = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); if (!f) return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings."; fseek(f, 0, SEEK_END); len = ftell(f); if (len != 0x10000) { fclose(f); return "DSi ARM7 BIOS is not a valid BIOS dump."; } fclose(f); return ""; } QString VerifyDSFirmware() { FILE* f; long len; f = Platform::OpenLocalFile(Config::FirmwarePath, "rb"); if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; fseek(f, 0, SEEK_END); len = ftell(f); if (len == 0x20000) { // 128KB firmware, not bootable fclose(f); // TODO report it somehow? detect in core? return ""; } else if (len != 0x40000 && len != 0x80000) { fclose(f); return "DS firmware is not a valid firmware dump."; } fclose(f); return ""; } QString VerifyDSiFirmware() { FILE* f; long len; f = Platform::OpenLocalFile(Config::DSiFirmwarePath, "rb"); if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; fseek(f, 0, SEEK_END); len = ftell(f); if (len != 0x20000) { // not 128KB // TODO: check whether those work fclose(f); return "DSi firmware is not a valid firmware dump."; } fclose(f); return ""; } QString VerifyDSiNAND() { FILE* f; long len; f = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; // TODO: some basic checks // check that it has the nocash footer, and all fclose(f); return ""; } QString VerifySetup() { QString res; if (Config::ExternalBIOSEnable) { res = VerifyDSBIOS(); if (!res.isEmpty()) return res; } if (Config::ConsoleType == 1) { res = VerifyDSiBIOS(); if (!res.isEmpty()) return res; if (Config::ExternalBIOSEnable) { res = VerifyDSiFirmware(); if (!res.isEmpty()) return res; } res = VerifyDSiNAND(); if (!res.isEmpty()) return res; } else { if (Config::ExternalBIOSEnable) { res = VerifyDSFirmware(); if (!res.isEmpty()) return res; } } return ""; } std::string GetSavestateName(int slot) { std::string ext = ".ml"; ext += (char)('0'+slot); return GetAssetPath(false, Config::SavestatePath, ext); } bool SavestateExists(int slot) { std::string ssfile = GetSavestateName(slot); return Platform::FileExists(ssfile); } bool LoadState(std::string filename) { // backup Savestate* backup = new Savestate("timewarp.mln", true); NDS::DoSavestate(backup); delete backup; bool failed = false; Savestate* state = new Savestate(filename, false); if (state->Error) { delete state; // current state might be crapoed, so restore from sane backup state = new Savestate("timewarp.mln", false); failed = true; } bool res = NDS::DoSavestate(state); delete state; if (!res) { failed = true; state = new Savestate("timewarp.mln", false); NDS::DoSavestate(state); delete state; } if (failed) return false; if (Config::SavestateRelocSRAM && NDSSave) { PreviousSaveFile = NDSSave->GetPath(); std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); NDSSave->SetPath(savefile, true); } SavestateLoaded = true; return true; } bool SaveState(std::string filename) { Savestate* state = new Savestate(filename, true); if (state->Error) { delete state; return false; } NDS::DoSavestate(state); delete state; if (Config::SavestateRelocSRAM && NDSSave) { std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); NDSSave->SetPath(savefile, false); } return true; } void UndoStateLoad() { if (!SavestateLoaded) return; // pray that this works // what do we do if it doesn't??? // but it should work. Savestate* backup = new Savestate("timewarp.mln", false); NDS::DoSavestate(backup); delete backup; if (NDSSave && (!PreviousSaveFile.empty())) { NDSSave->SetPath(PreviousSaveFile, true); } } void UnloadCheats() { if (CheatFile) { delete CheatFile; CheatFile = nullptr; } } void LoadCheats() { UnloadCheats(); std::string filename = GetAssetPath(false, Config::CheatFilePath, ".mch"); // TODO: check for error (malformed cheat file, ...) CheatFile = new ARCodeFile(filename); AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr); } void EnableCheats(bool enable) { CheatsOn = enable; if (CheatFile) AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr); } ARCodeFile* GetCheatFile() { return CheatFile; } void Reset() { NDS::SetConsoleType(Config::ConsoleType); if (Config::ConsoleType == 1) EjectGBACart(); NDS::Reset(); if ((CartType != -1) && NDSSave) { std::string oldsave = NDSSave->GetPath(); std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); if (oldsave != newsave) NDSSave->SetPath(newsave, false); } if ((GBACartType != -1) && GBASave) { std::string oldsave = GBASave->GetPath(); std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); if (oldsave != newsave) GBASave->SetPath(newsave, false); } if (!BaseROMName.empty()) { if (Config::DirectBoot || NDS::NeedsDirectBoot()) { NDS::SetupDirectBoot(BaseROMName); } } } bool LoadBIOS() { NDS::SetConsoleType(Config::ConsoleType); if (NDS::NeedsDirectBoot()) return false; /*if (NDSSave) delete NDSSave; NDSSave = nullptr; CartType = -1; BaseROMDir = ""; BaseROMName = ""; BaseAssetName = "";*/ NDS::Reset(); return true; } bool LoadROM(QStringList filepath, bool reset) { if (filepath.empty()) return false; u8* filedata; u32 filelen; std::string basepath; std::string romname; int num = filepath.count(); if (num == 1) { // regular file std::string filename = filepath.at(0).toStdString(); FILE* f = Platform::OpenFile(filename, "rb", true); if (!f) return false; fseek(f, 0, SEEK_END); long len = ftell(f); if (len > 0x40000000) { fclose(f); return false; } fseek(f, 0, SEEK_SET); filedata = new u8[len]; size_t nread = fread(filedata, (size_t)len, 1, f); if (nread != 1) { fclose(f); delete[] filedata; return false; } fclose(f); filelen = (u32)len; int pos = LastSep(filename); basepath = filename.substr(0, pos); romname = filename.substr(pos+1); } #ifdef ARCHIVE_SUPPORT_ENABLED else if (num == 2) { // file inside archive u32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen); if (lenread < 0) return false; if (!filedata) return false; if (lenread != filelen) { delete[] filedata; return false; } std::string std_archivepath = filepath.at(0).toStdString(); basepath = std_archivepath.substr(0, LastSep(std_archivepath)); std::string std_romname = filepath.at(1).toStdString(); romname = std_romname.substr(LastSep(std_romname)+1); } #endif else return false; if (NDSSave) delete NDSSave; NDSSave = nullptr; BaseROMDir = basepath; BaseROMName = romname; BaseAssetName = romname.substr(0, romname.rfind('.')); if (reset) { NDS::SetConsoleType(Config::ConsoleType); NDS::EjectCart(); NDS::Reset(); } u32 savelen = 0; u8* savedata = nullptr; std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); FILE* sav = Platform::OpenFile(savname, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); savelen = (u32)ftell(sav); fseek(sav, 0, SEEK_SET); savedata = new u8[savelen]; fread(savedata, savelen, 1, sav); fclose(sav); } bool res = NDS::LoadCart(filedata, filelen, savedata, savelen); if (res && reset) { if (Config::DirectBoot || NDS::NeedsDirectBoot()) { NDS::SetupDirectBoot(romname); } } if (res) { CartType = 0; NDSSave = new SaveManager(savname); LoadCheats(); } if (savedata) delete[] savedata; delete[] filedata; return res; } void EjectCart() { if (NDSSave) delete NDSSave; NDSSave = nullptr; UnloadCheats(); NDS::EjectCart(); CartType = -1; BaseROMDir = ""; BaseROMName = ""; BaseAssetName = ""; } bool CartInserted() { return CartType != -1; } QString CartLabel() { if (CartType == -1) return "(none)"; QString ret = QString::fromStdString(BaseROMName); int maxlen = 32; if (ret.length() > maxlen) ret = ret.left(maxlen-6) + "..." + ret.right(3); return ret; } bool LoadGBAROM(QStringList filepath) { if (Config::ConsoleType == 1) return false; if (filepath.empty()) return false; u8* filedata; u32 filelen; std::string basepath; std::string romname; int num = filepath.count(); if (num == 1) { // regular file std::string filename = filepath.at(0).toStdString(); FILE* f = Platform::OpenFile(filename, "rb", true); if (!f) return false; fseek(f, 0, SEEK_END); long len = ftell(f); if (len > 0x40000000) { fclose(f); return false; } fseek(f, 0, SEEK_SET); filedata = new u8[len]; size_t nread = fread(filedata, (size_t)len, 1, f); if (nread != 1) { fclose(f); delete[] filedata; return false; } fclose(f); filelen = (u32)len; int pos = LastSep(filename); basepath = filename.substr(0, pos); romname = filename.substr(pos+1); } #ifdef ARCHIVE_SUPPORT_ENABLED else if (num == 2) { // file inside archive u32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen); if (lenread < 0) return false; if (!filedata) return false; if (lenread != filelen) { delete[] filedata; return false; } std::string std_archivepath = filepath.at(0).toStdString(); basepath = std_archivepath.substr(0, LastSep(std_archivepath)); std::string std_romname = filepath.at(1).toStdString(); romname = std_romname.substr(LastSep(std_romname)+1); } #endif else return false; if (GBASave) delete GBASave; GBASave = nullptr; BaseGBAROMDir = basepath; BaseGBAROMName = romname; BaseGBAAssetName = romname.substr(0, romname.rfind('.')); u32 savelen = 0; u8* savedata = nullptr; std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); FILE* sav = Platform::OpenFile(savname, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); savelen = (u32)ftell(sav); fseek(sav, 0, SEEK_SET); savedata = new u8[savelen]; fread(savedata, savelen, 1, sav); fclose(sav); } bool res = NDS::LoadGBACart(filedata, filelen, savedata, savelen); if (res) { GBACartType = 0; GBASave = new SaveManager(savname); } if (savedata) delete[] savedata; delete[] filedata; return res; } void LoadGBAAddon(int type) { if (Config::ConsoleType == 1) return; if (GBASave) delete GBASave; GBASave = nullptr; NDS::LoadGBAAddon(type); GBACartType = type; BaseGBAROMDir = ""; BaseGBAROMName = ""; BaseGBAAssetName = ""; } void EjectGBACart() { if (GBASave) delete GBASave; GBASave = nullptr; NDS::EjectGBACart(); GBACartType = -1; BaseGBAROMDir = ""; BaseGBAROMName = ""; BaseGBAAssetName = ""; } bool GBACartInserted() { return GBACartType != -1; } QString GBACartLabel() { if (Config::ConsoleType == 1) return "none (DSi)"; switch (GBACartType) { case 0: { QString ret = QString::fromStdString(BaseGBAROMName); int maxlen = 32; if (ret.length() > maxlen) ret = ret.left(maxlen-6) + "..." + ret.right(3); return ret; } case NDS::GBAAddon_RAMExpansion: return "Memory expansion"; } return "(none)"; } void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef) { int index = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { for (int k = 0; k < 8; k++) { for (int l = 0; l < 8; l++) { u8 pal_index = index % 2 ? data[index/2] >> 4 : data[index/2] & 0x0F; u8 r = ((palette[pal_index] >> 0) & 0x1F) * 255 / 31; u8 g = ((palette[pal_index] >> 5) & 0x1F) * 255 / 31; u8 b = ((palette[pal_index] >> 10) & 0x1F) * 255 / 31; u8 a = pal_index ? 255: 0; u32* row = &iconRef[256 * i + 32 * k + 8 * j]; row[l] = (a << 24) | (r << 16) | (g << 8) | b; index++; } } } } } #define SEQ_FLIPV(i) ((i & 0b1000000000000000) >> 15) #define SEQ_FLIPH(i) ((i & 0b0100000000000000) >> 14) #define SEQ_PAL(i) ((i & 0b0011100000000000) >> 11) #define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8) #define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0) void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16], u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64], std::vector &animatedSequenceRef) { for (int i = 0; i < 64; i++) { if (!sequence[i]) break; u32* frame = &animatedTexRef[32 * 32 * i]; ROMIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], frame); if (SEQ_FLIPH(sequence[i])) { for (int x = 0; x < 32; x++) { for (int y = 0; y < 32/2; y++) { std::swap(frame[x * 32 + y], frame[x * 32 + (32 - 1 - y)]); } } } if (SEQ_FLIPV(sequence[i])) { for (int x = 0; x < 32/2; x++) { for (int y = 0; y < 32; y++) { std::swap(frame[x * 32 + y], frame[(32 - 1 - x) * 32 + y]); } } } for (int j = 0; j < SEQ_DUR(sequence[i]); j++) animatedSequenceRef.push_back(i); } } }