Mesen2/Core/NES/GameDatabase.cpp
Sour c77415581f NES: Disable Game Genie bus conflicts emulation when running an unknown iNES 1.0 ROM
Otherwise, Game Genie codes that could have worked on hardware did not work as expected because of the bus conflicts. This could occur when e.g trying to use cheats on a romhack, etc.
2024-07-20 21:39:16 +09:00

335 lines
10 KiB
C++

#include "pch.h"
#include "NES/RomData.h"
#include "NES/GameDatabase.h"
#include "NES/Loaders/UnifLoader.h"
#include "NES/Loaders/UnifBoards.h"
#include "Shared/MessageManager.h"
#include "Utilities/CRC32.h"
#include "Utilities/FolderUtilities.h"
#include "Utilities/StringUtilities.h"
#include "Utilities/HexUtilities.h"
std::unordered_map<uint32_t, GameInfo> GameDatabase::_gameDatabase;
bool GameDatabase::_enabled = true;
bool GameDatabase::_initialized = false;
SimpleLock GameDatabase::_loadLock;
template<typename T>
T GameDatabase::ToInt(string value)
{
if(value.empty()) {
return 0;
}
return std::stoi(value);
}
void GameDatabase::LoadGameDb(vector<string> data)
{
for(string &row : data) {
vector<string> values = StringUtilities::Split(row, ',');
if(values.size() >= 16) {
GameInfo gameInfo;
gameInfo.Crc = (uint32_t)std::stoll(values[0], nullptr, 16);
gameInfo.System = values[1];
gameInfo.Board = values[2];
gameInfo.Pcb = values[3];
gameInfo.Chip = values[4];
gameInfo.MapperID = (uint16_t)ToInt<uint32_t>(values[5]);
gameInfo.PrgRomSize = ToInt<uint32_t>(values[6]) * 1024;
gameInfo.ChrRomSize = ToInt<uint32_t>(values[7]) * 1024;
gameInfo.ChrRamSize = ToInt<uint32_t>(values[8]) * 1024;
gameInfo.WorkRamSize = ToInt<uint32_t>(values[9]) * 1024;
gameInfo.SaveRamSize = ToInt<uint32_t>(values[10]) * 1024;
gameInfo.HasBattery = ToInt<uint32_t>(values[11]) == 0 ? false : true;
gameInfo.Mirroring = values[12];
gameInfo.InputType = (GameInputType)ToInt<uint32_t>(values[13]);
gameInfo.BusConflicts = values[14];
gameInfo.SubmapperID = values[15];
gameInfo.VsType = (VsSystemType)ToInt<uint32_t>(values[16]);
gameInfo.VsPpuModel = (PpuModel)ToInt<uint32_t>(values[17]);
if(gameInfo.MapperID == 65000) {
gameInfo.MapperID = UnifLoader::GetMapperID(gameInfo.Board);
}
_gameDatabase[gameInfo.Crc] = gameInfo;
}
}
MessageManager::Log();
MessageManager::Log("[DB] Initialized - " + std::to_string(_gameDatabase.size()) + " games in DB");
}
void GameDatabase::LoadGameDb(std::istream &db)
{
vector<string> dbData;
while(db.good()) {
string lineContent;
std::getline(db, lineContent);
if(lineContent[lineContent.size() - 1] == '\r') {
lineContent = lineContent.substr(0, lineContent.size() - 1);
}
if(lineContent.empty() || lineContent[0] == '#') {
continue;
}
dbData.push_back(lineContent);
}
LoadGameDb(dbData);
}
void GameDatabase::InitDatabase()
{
if(!_initialized) {
auto lock = _loadLock.AcquireSafe();
if(!_initialized) {
string dbPath = FolderUtilities::CombinePath(FolderUtilities::GetHomeFolder(), "MesenNesDB.txt");
ifstream db(dbPath, ios::in | ios::binary);
LoadGameDb(db);
_initialized = true;
}
}
}
BusConflictType GameDatabase::GetBusConflictType(string busConflictSetting)
{
if(busConflictSetting.compare("Y") == 0) {
return BusConflictType::Yes;
} else if(busConflictSetting.compare("N") == 0) {
return BusConflictType::No;
}
return BusConflictType::Default;
}
GameSystem GameDatabase::GetGameSystem(string system)
{
if(system.compare("NesNtsc") == 0) {
return GameSystem::NesNtsc;
} else if(system.compare("NesPal") == 0) {
return GameSystem::NesPal;
} else if(system.compare("Famicom") == 0) {
return GameSystem::Famicom;
} else if(system.compare("VsSystem") == 0) {
return GameSystem::VsSystem;
} else if(system.compare("Dendy") == 0) {
return GameSystem::Dendy;
} else if(system.compare("Playchoice") == 0) {
return GameSystem::Playchoice;
}
return GameSystem::NesNtsc;
}
uint8_t GameDatabase::GetSubMapper(GameInfo &info)
{
if(!info.SubmapperID.empty()) {
return ToInt<uint8_t>(info.SubmapperID);
}
return 0;
}
bool GameDatabase::GetDbRomSize(uint32_t romCrc, uint32_t &prgSize, uint32_t &chrSize)
{
InitDatabase();
auto result = _gameDatabase.find(romCrc);
if(result != _gameDatabase.end()) {
prgSize = result->second.PrgRomSize;
chrSize = result->second.ChrRomSize;
return true;
}
return false;
}
bool GameDatabase::GetiNesHeader(uint32_t romCrc, NesHeader &nesHeader)
{
GameInfo info = {};
InitDatabase();
auto result = _gameDatabase.find(romCrc);
if(result != _gameDatabase.end()) {
info = result->second;
nesHeader.Byte9 = 0;
if(info.PrgRomSize > 4096*1024) {
uint16_t prgSize = info.PrgRomSize / 0x4000;
nesHeader.PrgCount = prgSize & 0xFF;
nesHeader.Byte9 |= (prgSize & 0xF00) >> 8;
} else {
nesHeader.PrgCount = info.PrgRomSize / 0x4000;
}
if(info.ChrRomSize > 2048*1024) {
uint16_t chrSize = info.ChrRomSize / 0x2000;
nesHeader.ChrCount = chrSize & 0xFF;
nesHeader.Byte9 |= (chrSize & 0xF00) >> 4;
} else {
nesHeader.ChrCount = info.ChrRomSize / 0x2000;
}
nesHeader.Byte6 = (info.MapperID & 0x0F) << 4;
if(info.HasBattery) {
nesHeader.Byte6 |= 0x02;
}
if(info.Mirroring.compare("v") == 0) {
nesHeader.Byte6 |= 0x01;
}
nesHeader.Byte7 = (info.MapperID & 0xF0);
GameSystem system = GetGameSystem(info.System);
if(system == GameSystem::Playchoice) {
nesHeader.Byte7 |= 0x02;
} else if(system == GameSystem::VsSystem) {
nesHeader.Byte7 |= 0x01;
}
//Don't set this, otherwise the header will be used over the game DB data
//nesHeader.Byte7 |= 0x08; //NES 2.0 marker
nesHeader.Byte8 = ((GetSubMapper(info) & 0x0F) << 4) | ((info.MapperID & 0xF00) >> 8);
nesHeader.Byte10 = 0;
if(info.SaveRamSize > 0) {
nesHeader.Byte10 |= ((int)log2(info.SaveRamSize) - 6) << 4;
}
if(info.WorkRamSize > 0) {
nesHeader.Byte10 |= ((int)log2(info.WorkRamSize) - 6);
}
nesHeader.Byte11 = 0;
if(info.ChrRamSize > 0) {
nesHeader.Byte11 |= ((int)log2(info.ChrRamSize) - 6);
}
nesHeader.Byte12 = system == GameSystem::NesPal ? 0x01 : 0;
nesHeader.Byte13 = 0; //VS PPU variant
return true;
}
return false;
}
void GameDatabase::SetGameInfo(uint32_t romCrc, RomData &romData, bool updateRomData, bool forHeaderlessRom)
{
GameInfo info = {};
InitDatabase();
auto result = _gameDatabase.find(romCrc);
bool foundInDatabase = result != _gameDatabase.end();
if(foundInDatabase) {
info = result->second;
MessageManager::Log("[DB] Game found in database");
MessageManager::Log("[DB] Mapper: " + std::to_string(info.MapperID) + " Sub: " + std::to_string(GetSubMapper(info)));
if(info.MapperID < UnifBoards::UnknownBoard) {
MessageManager::Log("[DB] Mapper: " + std::to_string(info.MapperID) + " Sub: " + std::to_string(GetSubMapper(info)));
} else if(info.MapperID == UnifBoards::UnknownBoard) {
MessageManager::DisplayMessage("Error", "UnsupportedMapper", "UNIF: " + info.Board);
}
MessageManager::Log("[DB] System : " + info.System);
if(GetGameSystem(info.System) == GameSystem::VsSystem) {
string type = "VS-UniSystem";
switch(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;
}
MessageManager::Log("[DB] VS System Type: " + type);
}
if(!info.Board.empty()) {
MessageManager::Log("[DB] Board: " + info.Board);
}
if(!info.Chip.empty()) {
MessageManager::Log("[DB] Chip: " + info.Chip);
}
switch(GetBusConflictType(info.BusConflicts)) {
case BusConflictType::Default: break;
case BusConflictType::Yes: MessageManager::Log("[DB] Bus conflicts: Yes"); break;
case BusConflictType::No: MessageManager::Log("[DB] Bus conflicts: No"); break;
}
if(!info.Mirroring.empty()) {
string msg = "[DB] Mirroring: ";
switch(info.Mirroring[0]) {
case 'h': msg += "Horizontal"; break;
case 'v': msg += "Vertical"; break;
case '4': msg += "4 Screens"; break;
case '0': msg += "Screen A only"; break;
case '1': msg += "Screen B only"; break;
}
MessageManager::Log(msg);
}
MessageManager::Log("[DB] PRG ROM: " + std::to_string(info.PrgRomSize / 1024) + " KB");
MessageManager::Log("[DB] CHR ROM: " + std::to_string(info.ChrRomSize / 1024) + " KB");
if(info.ChrRamSize > 0) {
MessageManager::Log("[DB] CHR RAM: " + std::to_string(info.ChrRamSize / 1024) + " KB");
}
if(info.WorkRamSize > 0) {
MessageManager::Log("[DB] Work RAM: " + std::to_string(info.WorkRamSize / 1024) + " KB");
}
if(info.SaveRamSize > 0) {
MessageManager::Log("[DB] Save RAM: " + std::to_string(info.SaveRamSize / 1024) + " KB");
}
MessageManager::Log("[DB] Battery: " + string(info.HasBattery ? "Yes" : "No"));
if(updateRomData) {
MessageManager::Log("[DB] Database info will be used instead of file header.");
romData.Info.IsInDatabase = true;
UpdateRomData(info, romData);
}
#ifdef _DEBUG
MessageManager::DisplayMessage("DB", "Mapper: " + std::to_string(romData.Info.MapperID) + " Sub: " + std::to_string(romData.Info.SubMapperID) + " System: " + info.System);
#endif
} else {
MessageManager::Log("[DB] Game not found in database");
}
romData.Info.DatabaseInfo = info;
}
void GameDatabase::UpdateRomData(GameInfo &info, RomData &romData)
{
romData.Info.MapperID = info.MapperID;
romData.Info.System = GetGameSystem(info.System);
if(romData.Info.System == GameSystem::VsSystem) {
romData.Info.VsType = info.VsType;
romData.Info.VsPpuModel = info.VsPpuModel;
}
romData.Info.InputType = info.InputType;
romData.Info.SubMapperID = GetSubMapper(info);
romData.Info.BusConflicts = GetBusConflictType(info.BusConflicts);
bool isValidatedEntry = !info.SubmapperID.empty();
if(isValidatedEntry || info.ChrRamSize > 0) {
romData.ChrRamSize = info.ChrRamSize;
}
if(isValidatedEntry || info.WorkRamSize > 0) {
romData.WorkRamSize = info.WorkRamSize;
}
if(isValidatedEntry || info.SaveRamSize > 0) {
romData.SaveRamSize = info.SaveRamSize;
}
if(isValidatedEntry) {
romData.Info.HasBattery = info.HasBattery;
} else {
romData.Info.HasBattery |= info.HasBattery;
}
if(!info.Mirroring.empty()) {
switch(info.Mirroring[0]) {
case 'h': romData.Info.Mirroring = MirroringType::Horizontal; break;
case 'v': romData.Info.Mirroring = MirroringType::Vertical; break;
case '4': romData.Info.Mirroring = MirroringType::FourScreens; break;
case '0': romData.Info.Mirroring = MirroringType::ScreenAOnly; break;
case '1': romData.Info.Mirroring = MirroringType::ScreenBOnly; break;
}
}
}