mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
NES: Added support for .qd format FDS roms
This commit is contained in:
parent
031cb7058c
commit
30fd76d9e0
14 changed files with 121 additions and 49 deletions
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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<uint8_t>& diskSide, uint8_t * readBuffer)
|
||||
FdsLoader::FdsLoader(bool useQdFormat) : BaseLoader()
|
||||
{
|
||||
_useQdFormat = useQdFormat;
|
||||
}
|
||||
|
||||
int FdsLoader::GetSideCapacity()
|
||||
{
|
||||
return _useQdFormat ? QdDiskSideCapacity : FdsDiskSideCapacity;
|
||||
}
|
||||
|
||||
void FdsLoader::AddGaps(vector<uint8_t>& 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<uint8_t>& diskSide, uint8_t * readBuffer)
|
|||
vector<uint8_t> FdsLoader::RebuildFdsFile(vector<vector<uint8_t>> diskData, bool needHeader)
|
||||
{
|
||||
vector<uint8_t> 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<uint8_t> FdsLoader::RebuildFdsFile(vector<vector<uint8_t>> diskData, bool
|
|||
for(vector<uint8_t> &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<uint8_t> FdsLoader::RebuildFdsFile(vector<vector<uint8_t>> 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<uint8_t>& romFile, vector<vector<uint8_t>>&
|
|||
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<uint8_t>& romFile, vector<vector<uint8_t>>&
|
|||
|
||||
diskHeaders.push_back(vector<uint8_t>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<uint8_t>& diskSide, uint8_t* readBuffer);
|
||||
void AddGaps(vector<uint8_t>& diskSide, uint8_t* readBuffer, uint32_t bufferSize);
|
||||
|
||||
int GetSideCapacity();
|
||||
|
||||
public:
|
||||
using BaseLoader::BaseLoader;
|
||||
FdsLoader(bool useQdFormat = false);
|
||||
|
||||
vector<uint8_t> RebuildFdsFile(vector<vector<uint8_t>> diskData, bool needHeader);
|
||||
void LoadDiskData(vector<uint8_t>& romFile, vector<vector<uint8_t>> &diskData, vector<vector<uint8_t>> &diskHeaders);
|
||||
|
|
|
@ -191,14 +191,10 @@ void UnifLoader::LoadRom(RomData& romData, vector<uint8_t>& 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ void iNesLoader::LoadRom(RomData& romData, vector<uint8_t>& 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<uint8_t> ipsData)
|
|||
_fdsDiskSides.clear();
|
||||
_fdsDiskHeaders.clear();
|
||||
|
||||
FdsLoader loader;
|
||||
FdsLoader loader(_useQdFormat);
|
||||
vector<uint8_t> patchedData;
|
||||
if(ipsData.size() > 0 && IpsPatcher::PatchBuffer(ipsData, _fdsRawData, patchedData)) {
|
||||
loader.LoadDiskData(patchedData, _fdsDiskSides, _fdsDiskHeaders);
|
||||
|
@ -80,9 +83,9 @@ void Fds::LoadDiskData(vector<uint8_t> ipsData)
|
|||
|
||||
vector<uint8_t> Fds::CreateIpsPatch()
|
||||
{
|
||||
FdsLoader loader;
|
||||
FdsLoader loader(_useQdFormat);
|
||||
bool needHeader = (memcmp(_fdsRawData.data(), "FDS\x1a", 4) == 0);
|
||||
vector<uint8_t> newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader);
|
||||
vector<uint8_t> newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader);
|
||||
return IpsPatcher::CreatePatch(_fdsRawData, newData);
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ private:
|
|||
bool _gapEnded = true;
|
||||
bool _scanningDisk = false;
|
||||
bool _transferComplete = false;
|
||||
bool _useQdFormat = false;
|
||||
|
||||
vector<uint8_t> _fdsRawData;
|
||||
vector<vector<uint8_t>> _fdsDiskSides;
|
||||
|
|
|
@ -65,7 +65,7 @@ public:
|
|||
NesConsole(Emulator* emulator);
|
||||
~NesConsole();
|
||||
|
||||
static vector<string> GetSupportedExtensions() { return { ".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox" }; }
|
||||
static vector<string> GetSupportedExtensions() { return { ".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox", ".qd" }; }
|
||||
static vector<string> GetSupportedSignatures() { return { "NES\x1a", "FDS\x1a", "\x1*NINTENDO-HVC*", "NESM\x1a", "NSFE", "UNIF", "STBX" }; }
|
||||
|
||||
NesCpu* GetCpu() { return _cpu.get(); }
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -710,7 +710,7 @@
|
|||
<Control ID="grpFileAssociations">File Associations</Control>
|
||||
<Control ID="chkSnesRomFiles">SNES roms: .sfc, .smc, .swc, .fig, .bs</Control>
|
||||
<Control ID="chkSnesMusicFiles">SNES music: .spc</Control>
|
||||
<Control ID="chkNesRomFiles">NES roms: .nes, .unif, .fds, .studybox</Control>
|
||||
<Control ID="chkNesRomFiles">NES roms: .nes, .unif, .fds, .qd, .studybox</Control>
|
||||
<Control ID="chkNesMusicFiles">NES music: .nsf, .nsfe</Control>
|
||||
<Control ID="chkGbRomFiles">Game Boy roms: .gb, .gbc, .gbx</Control>
|
||||
<Control ID="chkGbMusicFiles">Game Boy music: .gbs</Control>
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace Mesen.Utilities
|
|||
if(ext == FileDialogHelper.RomExt) {
|
||||
filter.Add(new FilePickerFileType("All ROM files") { Patterns = new List<string>() {
|
||||
"*.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<string>() { "*.sfc", "*.fig", "*.smc", "*.bs", "*.st", "*.spc" } });
|
||||
filter.Add(new FilePickerFileType("NES ROM files") { Patterns = new List<string>() { "*.nes", "*.fds", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe" } });
|
||||
filter.Add(new FilePickerFileType("NES ROM files") { Patterns = new List<string>() { "*.nes", "*.fds", "*.qd", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe" } });
|
||||
filter.Add(new FilePickerFileType("GB ROM files") { Patterns = new List<string>() { "*.gb", "*.gbc", "*.gbx", "*.gbs" } });
|
||||
filter.Add(new FilePickerFileType("GBA ROM files") { Patterns = new List<string>() { "*.gba" } });
|
||||
filter.Add(new FilePickerFileType("PC Engine ROM files") { Patterns = new List<string>() { "*.pce", "*.sgx", "*.cue", "*.hes" } });
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Mesen.Utilities
|
|||
private static HashSet<string> _romExtensions = new HashSet<string>() {
|
||||
".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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "Utilities/CRC32.h"
|
||||
|
||||
const std::initializer_list<string> 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",
|
||||
|
|
Loading…
Add table
Reference in a new issue