mirror of
https://github.com/libretro/MesenCE.git
synced 2026-11-10 11:11:32 -10:00
* NES: Improve CHR-NVRAM support. Right now, Mesen uses the NES 2.0 CHR-NVRAM field simply to enable battery saves for the volatile CHR-RAM type, so it only cares if it's zero or nonzero. This means that a game using CHR-NVRAM needs to specify volatile CHR-RAM to get the CHR-NVRAM it's actually requesting. Mesen also doesn't currently distinguish between CHR-RAM types in any way; there is a single CHR-RAM memory type and so it's not capable of saving just some of CHR-RAM. The only contemporary game that uses CHR-NVRAM, RacerMate Challenge II, has both types. This change makes Mesen sum the two CHR-RAM types and save all of the data. This is still not correct, as it doesn't make sense to specify both types of CHR-RAM for any mapper other than mapper 168, but it will at least allow correctly-specified headers to work. It also allows RacerMate Challenge II to get the full 64 KiB of CHR-RAM (normally 32 KiB of each type) that it requests when using NES 2.0 ROMs, and it now saves however much of this is CHR-NVRAM (either the 2nd half or all of it). This change also makes the log window print the CHR-NVRAM size as "CHR save RAM" if nonzero. * Make it always print CHR save RAM size for NES 2.0.
171 lines
6.5 KiB
C++
171 lines
6.5 KiB
C++
#include "pch.h"
|
|
#include "NES/Loaders/iNesLoader.h"
|
|
#include "Utilities/CRC32.h"
|
|
#include "Utilities/HexUtilities.h"
|
|
#include "Utilities/StringUtilities.h"
|
|
#include "NES/MapperFactory.h"
|
|
#include "NES/NesHeader.h"
|
|
#include "NES/RomData.h"
|
|
#include "NES/GameDatabase.h"
|
|
#include "Shared/RomInfo.h"
|
|
|
|
void iNesLoader::LoadRom(RomData& romData, vector<uint8_t>& romFile, NesHeader* preloadedHeader, bool databaseEnabled)
|
|
{
|
|
NesHeader header;
|
|
uint8_t* buffer = romFile.data();
|
|
uint32_t dataSize = (uint32_t)romFile.size();
|
|
if(preloadedHeader) {
|
|
header = *preloadedHeader;
|
|
} else {
|
|
memcpy((char*)&header, buffer, sizeof(NesHeader));
|
|
buffer += sizeof(NesHeader);
|
|
dataSize -= sizeof(NesHeader);
|
|
}
|
|
|
|
romData.Info.Format = RomFormat::iNes;
|
|
|
|
//NSF header size
|
|
romData.Info.FilePrgOffset = sizeof(NesHeader);
|
|
|
|
romData.Info.IsNes20Header = (header.GetRomHeaderVersion() == RomHeaderVersion::Nes2_0);
|
|
romData.Info.MapperID = header.GetMapperID();
|
|
romData.Info.SubMapperID = header.GetSubMapper();
|
|
romData.Info.Mirroring = header.GetMirroringType();
|
|
romData.Info.HasBattery = header.HasBattery();
|
|
romData.Info.HasEpsm = header.HasEpsm();
|
|
romData.Info.System = header.GetGameSystem();
|
|
romData.Info.VsType = header.GetVsSystemType();
|
|
romData.Info.VsPpuModel = header.GetVsSystemPpuModel();
|
|
romData.Info.InputType = header.GetInputType();
|
|
romData.Info.HasTrainer = header.HasTrainer();
|
|
romData.Info.Header = header;
|
|
|
|
romData.ChrRamSize = header.GetChrRamSize();
|
|
romData.SaveChrRamSize = header.GetSaveChrRamSize();
|
|
romData.WorkRamSize = header.GetWorkRamSize();
|
|
romData.SaveRamSize = header.GetSaveRamSize();
|
|
|
|
if(romData.Info.HasTrainer) {
|
|
if(dataSize >= 512) {
|
|
//512-byte trainer at $7000-$71FF (stored before PRG data)
|
|
romData.TrainerData.insert(romData.TrainerData.end(), buffer, buffer + 512);
|
|
buffer += 512;
|
|
dataSize -= 512;
|
|
} else {
|
|
romData.Error = true;
|
|
MessageManager::Log("[iNes] Invalid file (file length does not match header information) - load operation cancelled.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
size_t bytesRead = buffer - romFile.data();
|
|
|
|
uint32_t romCrc = CRC32::GetCRC(buffer, romFile.size() - bytesRead);
|
|
romData.Info.Hash.PrgChrCrc32 = romCrc;
|
|
|
|
uint32_t prgSize = 0;
|
|
uint32_t chrSize = 0;
|
|
|
|
if(!databaseEnabled || romData.Info.IsNes20Header || !GameDatabase::GetDbRomSize(romData.Info.Hash.PrgChrCrc32, prgSize, chrSize)) {
|
|
//Fallback on header sizes when game is not in DB (or DB is disabled)
|
|
prgSize = header.GetPrgSize();
|
|
chrSize = header.GetChrSize();
|
|
}
|
|
|
|
if(prgSize == 0) {
|
|
MessageManager::Log("[iNes] Invalid file (PRG size is 0) - load operation cancelled.");
|
|
romData.Error = true;
|
|
}
|
|
|
|
if(prgSize + chrSize > dataSize) {
|
|
//Invalid rom file
|
|
MessageManager::Log("[iNes] Invalid file (file length does not match header information) - load operation cancelled.");
|
|
romData.Error = true;
|
|
} else if(prgSize + chrSize < dataSize) {
|
|
MessageManager::Log("[iNes] Warning: File is larger than excepted (based on the file header).");
|
|
}
|
|
|
|
if(!romData.Error) {
|
|
romData.Info.Hash.PrgCrc32 = CRC32::GetCRC(buffer, prgSize);
|
|
|
|
romData.PrgRom.insert(romData.PrgRom.end(), buffer, buffer + prgSize);
|
|
while(romData.PrgRom.size() < 256) {
|
|
//Ensure the PRG is at least 256 bytes in size by mirroring as needed (mapper code requires it to be 256 bytes at least)
|
|
romData.PrgRom.insert(romData.PrgRom.end(), buffer, buffer + std::min<int>(prgSize, 256 - (int)romData.PrgRom.size()));
|
|
}
|
|
buffer += prgSize;
|
|
romData.ChrRom.insert(romData.ChrRom.end(), buffer, buffer + chrSize);
|
|
|
|
Log("PRG CRC32: 0x" + HexUtilities::ToHex(romData.Info.Hash.PrgCrc32, true));
|
|
Log("PRG+CHR CRC32: 0x" + HexUtilities::ToHex(romData.Info.Hash.PrgChrCrc32, true));
|
|
}
|
|
|
|
if(romData.Info.IsNes20Header) {
|
|
Log("[iNes] NES 2.0 file: Yes");
|
|
}
|
|
Log("[iNes] Mapper: " + std::to_string(romData.Info.MapperID) + " Sub: " + std::to_string(romData.Info.SubMapperID));
|
|
|
|
if(romData.Info.System == GameSystem::VsSystem) {
|
|
string type = "Vs-UniSystem";
|
|
switch(romData.Info.VsType) {
|
|
case VsSystemType::Default: break;
|
|
case VsSystemType::IceClimberProtection: type = "VS-UniSystem (Ice Climbers)"; break;
|
|
case VsSystemType::RaidOnBungelingBayProtection: type = "VS-DualSystem (Raid on Bungeling Bay)"; break;
|
|
case VsSystemType::RbiBaseballProtection: type = "VS-UniSystem (RBI Baseball)"; break;
|
|
case VsSystemType::SuperXeviousProtection: type = "VS-UniSystem (Super Xevious)"; break;
|
|
case VsSystemType::TkoBoxingProtection: type = "VS-UniSystem (TKO Boxing)"; break;
|
|
case VsSystemType::VsDualSystem: type = "VS-DualSystem"; break;
|
|
}
|
|
Log("[iNes] System: " + type);
|
|
}
|
|
|
|
Log("[iNes] PRG ROM: " + StringUtilities::SizeToString(prgSize));
|
|
Log("[iNes] CHR ROM: " + StringUtilities::SizeToString(chrSize));
|
|
if(romData.ChrRamSize > 0 || romData.Info.IsNes20Header) {
|
|
Log("[iNes] CHR RAM: " + StringUtilities::SizeToString(romData.ChrRamSize));
|
|
} else if(chrSize == 0) {
|
|
Log("[iNes] CHR RAM: 8 KB");
|
|
}
|
|
if(romData.SaveChrRamSize > 0 || romData.Info.IsNes20Header) {
|
|
Log("[iNes] CHR Save RAM: " + StringUtilities::SizeToString(romData.SaveChrRamSize));
|
|
}
|
|
if(romData.WorkRamSize > 0 || romData.Info.IsNes20Header) {
|
|
Log("[iNes] Work RAM: " + StringUtilities::SizeToString(romData.WorkRamSize));
|
|
}
|
|
if(romData.SaveRamSize > 0 || romData.Info.IsNes20Header) {
|
|
Log("[iNes] Save RAM: " + StringUtilities::SizeToString(romData.SaveRamSize));
|
|
}
|
|
|
|
string mirroringType = "";
|
|
switch(romData.Info.Mirroring) {
|
|
case MirroringType::Horizontal: mirroringType = "Horizontal"; break;
|
|
case MirroringType::Vertical: mirroringType = "Vertical"; break;
|
|
case MirroringType::ScreenAOnly: mirroringType = "1-Screen (A)"; break;
|
|
case MirroringType::ScreenBOnly: mirroringType = "1-Screen (B)"; break;
|
|
case MirroringType::FourScreens: mirroringType = "Four Screens"; break;
|
|
}
|
|
Log("[iNes] Mirroring: " + mirroringType);
|
|
|
|
Log("[iNes] Battery: " + string(romData.Info.HasBattery ? "Yes" : "No"));
|
|
if(romData.Info.HasTrainer) {
|
|
Log("[iNes] Trainer: Yes");
|
|
}
|
|
if(romData.Info.HasEpsm) {
|
|
Log("[iNes] EPSM: Yes");
|
|
}
|
|
|
|
if(!romData.Info.IsNes20Header) {
|
|
GameDatabase::SetGameInfo(romData.Info.Hash.PrgChrCrc32, romData, databaseEnabled, preloadedHeader != nullptr);
|
|
}
|
|
|
|
if(romData.Info.System == GameSystem::VsSystem) {
|
|
romData.Info.Format = RomFormat::VsSystem;
|
|
if(romData.Info.VsType == VsSystemType::VsDualSystem) {
|
|
romData.Info.Format = RomFormat::VsDualSystem;
|
|
}
|
|
} else if(romData.Info.System == GameSystem::FamicomNetworkSystem) {
|
|
//temporary code
|
|
romData.Info.MapperID = MapperFactory::FamicomNetworkSystemMapperID;
|
|
}
|
|
}
|