diff --git a/Core/NES/Loaders/BaseLoader.h b/Core/NES/Loaders/BaseLoader.h index a6ae101d..52d57ff0 100644 --- a/Core/NES/Loaders/BaseLoader.h +++ b/Core/NES/Loaders/BaseLoader.h @@ -5,18 +5,13 @@ class BaseLoader { protected: - bool _checkOnly; - void Log(string message) { - if(!_checkOnly) { - MessageManager::Log(message); - } + MessageManager::Log(message); } public: - BaseLoader(bool checkOnly = false) + BaseLoader() { - _checkOnly = checkOnly; } }; \ No newline at end of file diff --git a/Core/NES/Loaders/FdsLoader.cpp b/Core/NES/Loaders/FdsLoader.cpp index 0086575a..787167a2 100644 --- a/Core/NES/Loaders/FdsLoader.cpp +++ b/Core/NES/Loaders/FdsLoader.cpp @@ -10,37 +10,78 @@ #include "Utilities/FolderUtilities.h" #include "Utilities/CRC32.h" #include "Utilities/sha1.h" +#include "Utilities/HexUtilities.h" +#include "Utilities/StringUtilities.h" -void FdsLoader::AddGaps(vector& diskSide, uint8_t * readBuffer) +FdsLoader::FdsLoader(bool useQdFormat) : BaseLoader() +{ + _useQdFormat = useQdFormat; +} + +int FdsLoader::GetSideCapacity() +{ + return _useQdFormat ? QdDiskSideCapacity : FdsDiskSideCapacity; +} + +void FdsLoader::AddGaps(vector& diskSide, uint8_t* readBuffer, uint32_t bufferSize) { //Start image with 28300 bits of gap diskSide.insert(diskSide.end(), 28300 / 8, 0); - for(size_t j = 0; j < FdsDiskSideCapacity;) { - uint8_t blockType = readBuffer[j]; + auto read = [&](int i) -> uint8_t { + if(i >= 0 && i < bufferSize) { + return readBuffer[i]; + } + return 0; + }; + + for(int j = 0; j < GetSideCapacity();) { + uint8_t blockType = read(j); uint32_t blockLength = 1; switch(blockType) { case 1: blockLength = 56; break; //Disk header case 2: blockLength = 2; break; //File count - case 3: blockLength = 16; break; //File header - case 4: blockLength = 1 + readBuffer[j - 3] + readBuffer[j - 2] * 0x100; break; + + case 3: + //File header + //Log("ID: $" + HexUtilities::ToHex(read(j + 1)) + " - $" + HexUtilities::ToHex(read(j + 2)) + " - " + StringUtilities::GetString(readBuffer + j + 3, 8)); + //Log("Size: $" + HexUtilities::ToHex(read(j + 0x0D) | (read(j+0x0E) << 8))); + //Log("Load address: $" + HexUtilities::ToHex(read(j + 0x0B) | (read(j + 0x0C) << 8))); + blockLength = 16; + break; + + case 4: + if(_useQdFormat) { + blockLength = 1 + (read(j - 5) | (read(j - 4) << 8)); + } else { + blockLength = 1 + (read(j - 3) | (read(j - 2) << 8)); + } + break; + default: return; //End parsing when we encounter an invalid block type (This is what Nestopia apppears to do) } - if(blockType == 0) { - diskSide.push_back(blockType); - } else { - diskSide.push_back(0x80); - diskSide.insert(diskSide.end(), &readBuffer[j], &readBuffer[j] + blockLength); + if(_useQdFormat) { + //Use CRC values from QD file + blockLength += 2; + } + if(j + blockLength >= bufferSize) { + return; + } + + diskSide.push_back(0x80); + diskSide.insert(diskSide.end(), &readBuffer[j], &readBuffer[j] + blockLength); + + if(!_useQdFormat) { //Fake CRC value diskSide.push_back(0x4D); diskSide.push_back(0x62); - - //Insert 976 bits of gap after a block - diskSide.insert(diskSide.end(), 976 / 8, 0); } + //Insert 976 bits of gap after a block + diskSide.insert(diskSide.end(), 976 / 8, 0); + j += blockLength; } } @@ -48,7 +89,7 @@ void FdsLoader::AddGaps(vector& diskSide, uint8_t * readBuffer) vector FdsLoader::RebuildFdsFile(vector> diskData, bool needHeader) { vector output; - output.reserve(diskData.size() * FdsDiskSideCapacity + 16); + output.reserve(diskData.size() * GetSideCapacity() + 16); if(needHeader) { uint8_t header[16] = { 'F', 'D', 'S', '\x1a', (uint8_t)diskData.size(), '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; @@ -58,7 +99,7 @@ vector FdsLoader::RebuildFdsFile(vector> diskData, bool for(vector &diskSide : diskData) { bool inGap = true; size_t i = 0, len = diskSide.size(); - size_t gapNeeded = FdsDiskSideCapacity; + size_t gapNeeded = GetSideCapacity(); uint32_t fileSize = 0; while(i < len) { if(inGap) { @@ -74,10 +115,18 @@ vector FdsLoader::RebuildFdsFile(vector> diskData, bool case 3: blockLength = 16; fileSize = diskSide[i + 13] + diskSide[i + 14] * 0x100; break; //File header case 4: blockLength = 1 + fileSize; break; } + + if(_useQdFormat) { + blockLength += 2; + } + output.insert(output.end(), &diskSide[i], &diskSide[i] + blockLength); gapNeeded -= blockLength; i += blockLength; - i += 2; //Skip CRC after block + + if(!_useQdFormat) { + i += 2; //Skip CRC after block + } inGap = true; } @@ -97,7 +146,7 @@ void FdsLoader::LoadDiskData(vector& romFile, vector>& numberOfSides = romFile[4]; fileOffset = 16; } else { - numberOfSides = (uint8_t)(romFile.size() / 65500); + numberOfSides = (uint8_t)(romFile.size() / GetSideCapacity()); } for(uint32_t i = 0; i < numberOfSides; i++) { @@ -106,12 +155,12 @@ void FdsLoader::LoadDiskData(vector& romFile, vector>& diskHeaders.push_back(vector(romFile.data() + fileOffset + 1, romFile.data() + fileOffset + 57)); - AddGaps(fdsDiskImage, &romFile[fileOffset]); - fileOffset += FdsDiskSideCapacity; + AddGaps(fdsDiskImage, &romFile[fileOffset], romFile.size() - fileOffset); + fileOffset += GetSideCapacity(); - //Ensure the image is 65500 bytes - if(fdsDiskImage.size() < FdsDiskSideCapacity) { - fdsDiskImage.resize(FdsDiskSideCapacity); + //Ensure the image is at least the expected size of a disk + if(fdsDiskImage.size() < GetSideCapacity()) { + fdsDiskImage.resize(GetSideCapacity()); } } } diff --git a/Core/NES/Loaders/FdsLoader.h b/Core/NES/Loaders/FdsLoader.h index d0dae5ce..aec4914c 100644 --- a/Core/NES/Loaders/FdsLoader.h +++ b/Core/NES/Loaders/FdsLoader.h @@ -8,12 +8,17 @@ class FdsLoader : public BaseLoader { private: static constexpr size_t FdsDiskSideCapacity = 65500; + static constexpr size_t QdDiskSideCapacity = 65536; + + bool _useQdFormat = false; private: - void AddGaps(vector& diskSide, uint8_t* readBuffer); + void AddGaps(vector& diskSide, uint8_t* readBuffer, uint32_t bufferSize); + + int GetSideCapacity(); public: - using BaseLoader::BaseLoader; + FdsLoader(bool useQdFormat = false); vector RebuildFdsFile(vector> diskData, bool needHeader); void LoadDiskData(vector& romFile, vector> &diskData, vector> &diskHeaders); diff --git a/Core/NES/Loaders/UnifLoader.cpp b/Core/NES/Loaders/UnifLoader.cpp index 0276f2b8..61dc74fd 100644 --- a/Core/NES/Loaders/UnifLoader.cpp +++ b/Core/NES/Loaders/UnifLoader.cpp @@ -191,14 +191,10 @@ void UnifLoader::LoadRom(RomData& romData, vector& romFile, bool databa Log("[UNIF] Mirroring: " + mirroringType); Log("[UNIF] Battery: " + string(romData.Info.HasBattery ? "Yes" : "No")); - if(!_checkOnly) { - GameDatabase::SetGameInfo(romData.Info.Hash.PrgChrCrc32, romData, databaseEnabled, false); - } + GameDatabase::SetGameInfo(romData.Info.Hash.PrgChrCrc32, romData, databaseEnabled, false); if(romData.Info.MapperID == UnifBoards::UnknownBoard) { - if(!_checkOnly) { - MessageManager::DisplayMessage("Error", "UnsupportedMapper", "UNIF: " + _mapperName); - } + MessageManager::DisplayMessage("Error", "UnsupportedMapper", "UNIF: " + _mapperName); romData.Error = true; } } diff --git a/Core/NES/Loaders/iNesLoader.cpp b/Core/NES/Loaders/iNesLoader.cpp index f5f537aa..448ed152 100644 --- a/Core/NES/Loaders/iNesLoader.cpp +++ b/Core/NES/Loaders/iNesLoader.cpp @@ -142,7 +142,7 @@ void iNesLoader::LoadRom(RomData& romData, vector& romFile, NesHeader * Log("[iNes] EPSM: Yes"); } - if(!_checkOnly && !romData.Info.IsNes20Header) { + if(!romData.Info.IsNes20Header) { GameDatabase::SetGameInfo(romData.Info.Hash.PrgChrCrc32, romData, databaseEnabled, preloadedHeader != nullptr); } diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 2c6f4f0e..58670c98 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -15,6 +15,7 @@ #include "Shared/FirmwareHelper.h" #include "Utilities/Patches/IpsPatcher.h" #include "Utilities/Serializer.h" +#include "Utilities/StringUtilities.h" void Fds::InitMapper() { @@ -48,8 +49,10 @@ void Fds::InitMapper(RomData &romData) _fdsDiskSides = romData.FdsDiskData; _fdsDiskHeaders = romData.FdsDiskHeaders; _fdsRawData = romData.RawData; + string filename = StringUtilities::ToLower(romData.Info.Filename); + _useQdFormat = StringUtilities::EndsWith(filename, ".qd"); - FdsLoader loader; + FdsLoader loader(_useQdFormat); loader.LoadDiskData(_fdsRawData, _orgDiskSides, _orgDiskHeaders); //Apply save data (saved as an IPS file), if found @@ -69,7 +72,7 @@ void Fds::LoadDiskData(vector ipsData) _fdsDiskSides.clear(); _fdsDiskHeaders.clear(); - FdsLoader loader; + FdsLoader loader(_useQdFormat); vector patchedData; if(ipsData.size() > 0 && IpsPatcher::PatchBuffer(ipsData, _fdsRawData, patchedData)) { loader.LoadDiskData(patchedData, _fdsDiskSides, _fdsDiskHeaders); @@ -80,9 +83,9 @@ void Fds::LoadDiskData(vector ipsData) vector Fds::CreateIpsPatch() { - FdsLoader loader; + FdsLoader loader(_useQdFormat); bool needHeader = (memcmp(_fdsRawData.data(), "FDS\x1a", 4) == 0); - vector newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader); + vector newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader); return IpsPatcher::CreatePatch(_fdsRawData, newData); } diff --git a/Core/NES/Mappers/FDS/Fds.h b/Core/NES/Mappers/FDS/Fds.h index 3d42bd9a..e9ef12bd 100644 --- a/Core/NES/Mappers/FDS/Fds.h +++ b/Core/NES/Mappers/FDS/Fds.h @@ -67,6 +67,7 @@ private: bool _gapEnded = true; bool _scanningDisk = false; bool _transferComplete = false; + bool _useQdFormat = false; vector _fdsRawData; vector> _fdsDiskSides; diff --git a/Core/NES/NesConsole.h b/Core/NES/NesConsole.h index 2c67b34b..a906d1b9 100644 --- a/Core/NES/NesConsole.h +++ b/Core/NES/NesConsole.h @@ -65,7 +65,7 @@ public: NesConsole(Emulator* emulator); ~NesConsole(); - static vector GetSupportedExtensions() { return { ".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox" }; } + static vector GetSupportedExtensions() { return { ".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox", ".qd" }; } static vector GetSupportedSignatures() { return { "NES\x1a", "FDS\x1a", "\x1*NINTENDO-HVC*", "NESM\x1a", "NSFE", "UNIF", "STBX" }; } NesCpu* GetCpu() { return _cpu.get(); } diff --git a/UI/Config/FileAssociationHelper.cs b/UI/Config/FileAssociationHelper.cs index be20eb15..bcd40ceb 100644 --- a/UI/Config/FileAssociationHelper.cs +++ b/UI/Config/FileAssociationHelper.cs @@ -70,6 +70,7 @@ namespace Mesen.Config CreateMimeType("x-mesen-nes", "nes", "NES ROM", mimeTypes, cfg.AssociateNesRomFiles); CreateMimeType("x-mesen-fds", "fds", "FDS ROM", mimeTypes, cfg.AssociateNesRomFiles); + CreateMimeType("x-mesen-qd", "qd", "FDS ROM (QD format)", mimeTypes, cfg.AssociateNesRomFiles); CreateMimeType("x-mesen-studybox", "studybox", "Studybox ROM (Famicom)", mimeTypes, cfg.AssociateNesRomFiles); CreateMimeType("x-mesen-unif", "unf", "NES ROM", mimeTypes, cfg.AssociateNesRomFiles); @@ -191,6 +192,7 @@ namespace Mesen.Config FileAssociationHelper.UpdateFileAssociation("nes", cfg.AssociateNesRomFiles); FileAssociationHelper.UpdateFileAssociation("fds", cfg.AssociateNesRomFiles); + FileAssociationHelper.UpdateFileAssociation("qd", cfg.AssociateNesRomFiles); FileAssociationHelper.UpdateFileAssociation("unf", cfg.AssociateNesRomFiles); FileAssociationHelper.UpdateFileAssociation("studybox", cfg.AssociateNesRomFiles); diff --git a/UI/Localization/resources.en.xml b/UI/Localization/resources.en.xml index 3b286f3d..20ae3d28 100644 --- a/UI/Localization/resources.en.xml +++ b/UI/Localization/resources.en.xml @@ -710,7 +710,7 @@ File Associations SNES roms: .sfc, .smc, .swc, .fig, .bs SNES music: .spc - NES roms: .nes, .unif, .fds, .studybox + NES roms: .nes, .unif, .fds, .qd, .studybox NES music: .nsf, .nsfe Game Boy roms: .gb, .gbc, .gbx Game Boy music: .gbs diff --git a/UI/Utilities/FileDialogHelper.cs b/UI/Utilities/FileDialogHelper.cs index 564d22ab..c47e1898 100644 --- a/UI/Utilities/FileDialogHelper.cs +++ b/UI/Utilities/FileDialogHelper.cs @@ -53,7 +53,7 @@ namespace Mesen.Utilities if(ext == FileDialogHelper.RomExt) { filter.Add(new FilePickerFileType("All ROM files") { Patterns = new List() { "*.sfc", "*.fig", "*.smc", "*.bs", "*.st", "*.spc", - "*.nes", "*.fds", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe", + "*.nes", "*.fds", "*.qd", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe", "*.gb", "*.gbc", "*.gbx", "*.gbs", "*.pce", "*.sgx", "*.cue", "*.hes", "*.sms", "*.gg", "*.sg", "*.col", @@ -62,7 +62,7 @@ namespace Mesen.Utilities "*.zip", "*.7z" } }); filter.Add(new FilePickerFileType("SNES ROM files") { Patterns = new List() { "*.sfc", "*.fig", "*.smc", "*.bs", "*.st", "*.spc" } }); - filter.Add(new FilePickerFileType("NES ROM files") { Patterns = new List() { "*.nes", "*.fds", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe" } }); + filter.Add(new FilePickerFileType("NES ROM files") { Patterns = new List() { "*.nes", "*.fds", "*.qd", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe" } }); filter.Add(new FilePickerFileType("GB ROM files") { Patterns = new List() { "*.gb", "*.gbc", "*.gbx", "*.gbs" } }); filter.Add(new FilePickerFileType("GBA ROM files") { Patterns = new List() { "*.gba" } }); filter.Add(new FilePickerFileType("PC Engine ROM files") { Patterns = new List() { "*.pce", "*.sgx", "*.cue", "*.hes" } }); diff --git a/UI/Utilities/FolderHelper.cs b/UI/Utilities/FolderHelper.cs index bdebbe98..0d64c644 100644 --- a/UI/Utilities/FolderHelper.cs +++ b/UI/Utilities/FolderHelper.cs @@ -13,7 +13,7 @@ namespace Mesen.Utilities private static HashSet _romExtensions = new HashSet() { ".sfc", ".smc", ".fig", ".swc", ".bs", ".st", ".gb", ".gbc", ".gbx", - ".nes", ".unif", ".unf", ".fds", ".studybox", + ".nes", ".unif", ".unf", ".fds", ".qd", ".studybox", ".pce", ".sgx", ".cue", ".sms", ".gg", ".sg", ".col", ".gba", diff --git a/Utilities/StringUtilities.h b/Utilities/StringUtilities.h index 8e4e04fa..22997bc7 100644 --- a/Utilities/StringUtilities.h +++ b/Utilities/StringUtilities.h @@ -65,6 +65,10 @@ public: static bool StartsWith(string& str, const char* content) { size_t length = strlen(content); + if(str.size() < length) { + return false; + } + for(size_t i = 0; i < length; i++) { if(str[i] != content[i]) { return false; @@ -72,7 +76,24 @@ public: } return true; } - + + static bool EndsWith(string& str, const char* content) + { + size_t length = strlen(content); + if(str.size() < length) { + return false; + } + + size_t startPos = str.size() - length; + for(size_t i = startPos; i < str.size(); i++) { + if(str[i] != content[i - startPos]) { + return false; + } + } + + return true; + } + static bool Contains(string& str, const char* content) { size_t length = strlen(content); diff --git a/Utilities/VirtualFile.cpp b/Utilities/VirtualFile.cpp index a8dfb8f3..99a1e837 100644 --- a/Utilities/VirtualFile.cpp +++ b/Utilities/VirtualFile.cpp @@ -12,7 +12,7 @@ #include "Utilities/CRC32.h" const std::initializer_list VirtualFile::RomExtensions = { - ".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox", + ".nes", ".fds", ".qd", ".unif", ".unf", ".nsf", ".nsfe", ".studybox", ".sfc", ".swc", ".fig", ".smc", ".bs", ".st", ".spc", ".gb", ".gbc", ".gbx", ".gbs", ".pce", ".sgx", ".cue", ".hes",