Play-/Source/ui_shared/ArcadeUtils.cpp

479 lines
16 KiB
C++
Raw Normal View History

2022-09-29 16:02:50 -04:00
#include "ArcadeUtils.h"
#include <nlohmann/json.hpp>
#include "string_format.h"
#include "PathUtils.h"
2022-09-29 16:02:50 -04:00
#include "StdStreamUtils.h"
#include "PS2VM.h"
#include "PS2VM_Preferences.h"
2022-09-30 16:59:19 -04:00
#include "DiskUtils.h"
2022-09-29 16:02:50 -04:00
#include "AppConfig.h"
#include "BootablesDbClient.h"
2022-09-29 16:02:50 -04:00
#include "BootablesProcesses.h"
2023-01-03 21:18:18 -05:00
#include "hdd/HddDefs.h"
#include "discimages/ChdImageStream.h"
2022-09-29 16:02:50 -04:00
#include "iop/ioman/McDumpDevice.h"
2023-01-03 21:18:18 -05:00
#include "iop/ioman/HardDiskDumpDevice.h"
2022-10-03 18:46:38 -04:00
#include "iop/Iop_NamcoArcade.h"
2022-10-10 17:10:28 -04:00
#include "iop/namco_arcade/Iop_NamcoAcCdvd.h"
2022-10-20 20:15:48 -04:00
#include "iop/namco_arcade/Iop_NamcoAcRam.h"
#include "iop/namco_arcade/Iop_NamcoPadMan.h"
2023-12-07 18:59:38 -05:00
#include "iop/namco_sys147/Iop_NamcoNANDDevice.h"
#include "iop/namco_sys147/Iop_NamcoSys147.h"
2022-09-29 16:02:50 -04:00
struct ARCADE_MACHINE_DEF
{
enum class INPUT_MODE
{
DEFAULT,
LIGHTGUN,
DRUM,
DRIVE,
};
2022-09-30 18:20:56 -04:00
struct PATCH
{
uint32 address = 0;
uint32 value = 0;
};
2022-09-29 16:02:50 -04:00
std::string id;
2023-02-20 20:07:21 -05:00
std::string parent;
2023-12-07 18:59:38 -05:00
std::string driver;
std::string name;
2022-09-29 16:02:50 -04:00
std::string dongleFileName;
2022-09-30 16:59:19 -04:00
std::string cdvdFileName;
2023-01-03 21:18:18 -05:00
std::string hddFileName;
2023-12-07 18:59:38 -05:00
std::string nandFileName;
std::map<unsigned int, PS2::CControllerInfo::BUTTON> buttons;
INPUT_MODE inputMode = INPUT_MODE::DEFAULT;
2023-05-30 21:00:01 -04:00
std::array<float, 4> lightGunXform = {65535, 0, 65535, 0};
uint32 eeFreqScaleNumerator = 1;
uint32 eeFreqScaleDenominator = 1;
2022-09-29 16:02:50 -04:00
std::string boot;
2022-09-30 18:20:56 -04:00
std::vector<PATCH> patches;
2022-09-29 16:02:50 -04:00
};
2023-05-30 21:00:01 -04:00
// clang-format off
static const std::pair<const char*, PS2::CControllerInfo::BUTTON> g_buttonValues[] =
{
{ "dpad_up", PS2::CControllerInfo::DPAD_UP },
{ "dpad_down", PS2::CControllerInfo::DPAD_DOWN },
{ "dpad_left", PS2::CControllerInfo::DPAD_LEFT },
{ "dpad_right", PS2::CControllerInfo::DPAD_RIGHT },
{ "select", PS2::CControllerInfo::SELECT },
{ "start", PS2::CControllerInfo::START },
{ "square", PS2::CControllerInfo::SQUARE },
{ "triangle", PS2::CControllerInfo::TRIANGLE },
{ "cross", PS2::CControllerInfo::CROSS },
{ "circle", PS2::CControllerInfo::CIRCLE },
{ "l1", PS2::CControllerInfo::L1 },
{ "l2", PS2::CControllerInfo::L2 },
{ "l3", PS2::CControllerInfo::L3 },
{ "r1", PS2::CControllerInfo::R1 },
{ "r2", PS2::CControllerInfo::R2 },
{ "r3", PS2::CControllerInfo::R3 }
};
static const std::pair<const char*, ARCADE_MACHINE_DEF::INPUT_MODE> g_inputModeValues[] =
{
{ "default", ARCADE_MACHINE_DEF::INPUT_MODE::DEFAULT },
{ "lightgun", ARCADE_MACHINE_DEF::INPUT_MODE::LIGHTGUN },
{ "drum", ARCADE_MACHINE_DEF::INPUT_MODE::DRUM },
{ "drive", ARCADE_MACHINE_DEF::INPUT_MODE::DRIVE },
};
2023-05-30 21:00:01 -04:00
// clang-format on
template <typename ValueType>
ValueType ParseEnumValue(const char* valueName, const std::pair<const char*, ValueType>* beginIterator, const std::pair<const char*, ValueType>* endIterator)
{
auto valueIterator = std::find_if(beginIterator, endIterator,
[&](const auto& valuePair) { return strcmp(valuePair.first, valueName) == 0; });
if(valueIterator == endIterator)
{
throw std::runtime_error(string_format("Unknown enum name '%s'.", valueName));
}
return valueIterator->second;
}
2022-09-30 18:20:56 -04:00
uint32 ParseHexStringValue(const std::string& value)
{
uint32 result = 0;
int scanCount = sscanf(value.c_str(), "0x%x", &result);
assert(scanCount == 1);
return result;
}
2022-09-29 16:02:50 -04:00
ARCADE_MACHINE_DEF ReadArcadeMachineDefinition(const fs::path& arcadeDefPath)
{
auto parseButtons =
2023-05-30 21:00:01 -04:00
[](const nlohmann::json& buttonsObject) {
auto buttonMap = buttonsObject.get<std::map<std::string, std::string>>();
decltype(ARCADE_MACHINE_DEF::buttons) buttons;
for(const auto& buttonPair : buttonMap)
{
char* endPtr = nullptr;
const char* buttonNumber = buttonPair.first.c_str();
const char* buttonName = buttonPair.second.c_str();
int number = strtol(buttonPair.first.c_str(), &endPtr, 10);
if(endPtr == buttonPair.first.c_str())
{
throw std::runtime_error(string_format("Failed to parse button number '%s'.", buttonNumber));
}
buttons[number] = ParseEnumValue(buttonName, std::begin(g_buttonValues), std::end(g_buttonValues));
2023-05-30 21:00:01 -04:00
}
return buttons;
};
2022-09-30 18:20:56 -04:00
auto parsePatches =
[](const nlohmann::json& patchesArray) {
std::vector<ARCADE_MACHINE_DEF::PATCH> patches;
for(const auto& jsonPatch : patchesArray)
{
ARCADE_MACHINE_DEF::PATCH patch;
2022-10-04 17:12:04 -04:00
if(!jsonPatch.contains("address") || !jsonPatch.contains("value")) continue;
2022-09-30 18:20:56 -04:00
patch.address = ParseHexStringValue(jsonPatch["address"]);
patch.value = ParseHexStringValue(jsonPatch["value"]);
patches.emplace_back(std::move(patch));
}
return patches;
};
2022-09-29 16:02:50 -04:00
auto defString =
[&arcadeDefPath]() {
2023-08-25 16:08:52 -04:00
try
{
auto defStream = Framework::CreateInputStdStream(arcadeDefPath.native());
return defStream.ReadString();
}
catch(...)
{
throw std::runtime_error(string_format("Failed to read arcade definition file located at '%s'.",
fs::absolute(arcadeDefPath).string().c_str()));
}
2022-09-29 16:02:50 -04:00
}();
auto defJson = nlohmann::json::parse(defString);
ARCADE_MACHINE_DEF def;
def.id = defJson["id"];
2023-02-20 20:07:21 -05:00
if(defJson.contains("parent"))
{
def.parent = defJson["parent"];
}
2023-12-07 18:59:38 -05:00
if(defJson.contains("driver"))
{
def.driver = defJson["driver"];
}
def.name = defJson["name"];
2023-12-07 18:59:38 -05:00
if(defJson.contains("dongle"))
{
def.dongleFileName = defJson["dongle"]["name"];
}
2022-09-30 16:59:19 -04:00
if(defJson.contains("cdvd"))
{
def.cdvdFileName = defJson["cdvd"]["name"];
}
2023-01-03 21:18:18 -05:00
if(defJson.contains("hdd"))
{
def.hddFileName = defJson["hdd"]["name"];
}
2023-12-07 18:59:38 -05:00
if(defJson.contains("nand"))
{
def.nandFileName = defJson["nand"]["name"];
}
if(defJson.contains("buttons"))
{
def.buttons = parseButtons(defJson["buttons"]);
}
if(defJson.contains("inputMode"))
{
std::string inputModeString = defJson["inputMode"];
def.inputMode = ParseEnumValue(inputModeString.c_str(), std::begin(g_inputModeValues), std::end(g_inputModeValues));
}
if(defJson.contains("lightGunXform"))
{
auto lightGunXformArray = defJson["lightGunXform"];
if(lightGunXformArray.is_array() && (lightGunXformArray.size() >= 4))
{
for(int i = 0; i < 4; i++)
{
def.lightGunXform[i] = lightGunXformArray[i];
}
}
}
if(defJson.contains("eeFrequencyScale"))
{
auto eeFreqScaleArray = defJson["eeFrequencyScale"];
if(eeFreqScaleArray.is_array() && (eeFreqScaleArray.size() >= 2))
{
def.eeFreqScaleNumerator = eeFreqScaleArray[0];
def.eeFreqScaleDenominator = eeFreqScaleArray[1];
}
}
2022-09-29 16:02:50 -04:00
def.boot = defJson["boot"];
2022-09-30 18:20:56 -04:00
if(defJson.contains("patches"))
{
def.patches = parsePatches(defJson["patches"]);
}
2022-09-29 16:02:50 -04:00
return def;
}
2023-12-07 18:59:38 -05:00
void PrepareNamcoSys147Environment(CPS2VM* virtualMachine, const ARCADE_MACHINE_DEF& def)
{
auto baseId = def.parent.empty() ? def.id : def.parent;
fs::path arcadeRomPath = CAppConfig::GetInstance().GetPreferencePath(PREF_PS2_ARCADEROMS_DIRECTORY);
fs::path nandPath = arcadeRomPath / baseId / def.nandFileName;
if(!fs::exists(nandPath))
{
throw std::runtime_error(string_format("Failed to find '%s' in game's directory.", def.nandFileName.c_str()));
}
static std::pair<const char*, uint32> mounts[] =
{
std::make_pair("atfile0", 0x6000),
std::make_pair("atfile1", 0x10000),
std::make_pair("atfile2", 0x20000),
std::make_pair("atfile3", 0x30000),
std::make_pair("atfile4", 0x40000),
std::make_pair("atfile5", 0x50000),
std::make_pair("atfile6", 0x60000)
};
auto iopBios = dynamic_cast<CIopBios*>(virtualMachine->m_iop->m_bios.get());
for(const auto& mount : mounts)
{
iopBios->GetIoman()->RegisterDevice(mount.first, std::make_shared<Iop::Namco::CNamcoNANDDevice>( std::make_unique<Framework::CStdStream>(nandPath.string().c_str(), "rb"), mount.second));
}
//auto acRam = std::make_shared<Iop::Namco::CAcRam>(virtualMachine->m_iop->m_ram);
//iopBios->RegisterModule(acRam);
//iopBios->RegisterHleModuleReplacement("Arcade_Ext._Memory", acRam);
{
auto sys147Module = std::make_shared<Iop::Namco::CSys147>(*iopBios->GetSifman());
iopBios->RegisterModule(sys147Module);
iopBios->RegisterHleModuleReplacement("S147LINK", sys147Module);
virtualMachine->m_pad->InsertListener(sys147Module.get());
}
virtualMachine->m_ee->m_os->BootFromVirtualPath(def.boot.c_str(), {});
}
void PrepareArcadeEnvironment(CPS2VM* virtualMachine, const ARCADE_MACHINE_DEF& def)
2022-09-29 16:02:50 -04:00
{
2023-02-20 20:07:21 -05:00
auto baseId = def.parent.empty() ? def.id : def.parent;
auto romArchiveFileName = string_format("%s.zip", baseId.c_str());
2022-09-29 16:02:50 -04:00
fs::path arcadeRomPath = CAppConfig::GetInstance().GetPreferencePath(PREF_PS2_ARCADEROMS_DIRECTORY);
fs::path arcadeRomArchivePath = arcadeRomPath / romArchiveFileName;
if(!fs::exists(arcadeRomArchivePath))
{
throw std::runtime_error(string_format("Failed to find '%s' in arcade ROMs directory.", romArchiveFileName.c_str()));
}
2023-01-03 21:18:18 -05:00
//Mount CDVD
2022-09-30 16:59:19 -04:00
if(!def.cdvdFileName.empty())
{
2023-02-20 20:07:21 -05:00
fs::path cdvdPath = arcadeRomPath / baseId / def.cdvdFileName;
2022-09-30 16:59:19 -04:00
if(!fs::exists(cdvdPath))
{
throw std::runtime_error(string_format("Failed to find '%s' in game's directory.", def.cdvdFileName.c_str()));
}
//Try to create the optical media for sanity checks (will throw exceptions on errors).
DiskUtils::CreateOpticalMediaFromPath(cdvdPath);
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_CDROM0_PATH, cdvdPath);
virtualMachine->CDROM0_SyncPath();
2022-09-30 16:59:19 -04:00
}
2023-01-03 21:18:18 -05:00
//Mount HDD
if(!def.hddFileName.empty())
{
2023-02-20 20:07:21 -05:00
fs::path hddPath = arcadeRomPath / baseId / def.hddFileName;
2023-01-03 21:18:18 -05:00
if(!fs::exists(hddPath))
{
throw std::runtime_error(string_format("Failed to find '%s' in game's directory.", def.hddFileName.c_str()));
}
auto imageStream = std::make_unique<CChdImageStream>(std::make_unique<Framework::CStdStream>(hddPath.string().c_str(), "rb"));
assert(imageStream->GetUnitSize() == Hdd::g_sectorSize);
auto device = std::make_shared<Iop::Ioman::CHardDiskDumpDevice>(std::move(imageStream));
auto iopBios = dynamic_cast<CIopBios*>(virtualMachine->m_iop->m_bios.get());
iopBios->GetIoman()->RegisterDevice("hdd0", device);
}
2022-09-29 16:02:50 -04:00
std::vector<uint8> mcDumpContents;
{
auto inputStream = Framework::CreateInputStdStream(arcadeRomArchivePath.native());
Framework::CZipArchiveReader archiveReader(inputStream);
auto header = archiveReader.GetFileHeader(def.dongleFileName.c_str());
if(!header)
{
2023-02-20 20:07:21 -05:00
throw std::runtime_error(string_format("Failed to find file '%s' in archive '%s'.", def.dongleFileName.c_str(), romArchiveFileName.c_str()));
2022-09-29 16:02:50 -04:00
}
auto fileStream = archiveReader.BeginReadFile(def.dongleFileName.c_str());
mcDumpContents.resize(header->uncompressedSize);
fileStream->Read(mcDumpContents.data(), header->uncompressedSize);
}
//Override mc0 device with special device reading directly from zip file
{
auto device = std::make_shared<Iop::Ioman::CMcDumpDevice>(std::move(mcDumpContents));
auto iopBios = dynamic_cast<CIopBios*>(virtualMachine->m_iop->m_bios.get());
iopBios->GetIoman()->RegisterDevice("mc0", device);
2022-10-18 21:26:14 -04:00
iopBios->GetIoman()->RegisterDevice("ac0", device);
//Ridge Racer 5: Arcade Battle doesn't have any FILEIO in its IOPRP
//Assuming that the BIOS image for arcade boards is version 2.0.5
iopBios->SetDefaultImageVersion(2050);
2022-12-20 15:39:16 -05:00
2022-11-18 21:46:01 -05:00
auto acRam = std::make_shared<Iop::Namco::CAcRam>(virtualMachine->m_iop->m_ram);
iopBios->RegisterModule(acRam);
iopBios->RegisterHleModuleReplacement("Arcade_Ext._Memory", acRam);
auto acCdvdModule = std::make_shared<Iop::Namco::CAcCdvd>(*iopBios->GetSifman(), *iopBios->GetCdvdman(), virtualMachine->m_iop->m_ram, *acRam.get());
2022-10-20 20:29:41 -04:00
acCdvdModule->SetOpticalMedia(virtualMachine->m_cdrom0.get());
iopBios->RegisterModule(acCdvdModule);
iopBios->RegisterHleModuleReplacement("ATA/ATAPI_driver", acCdvdModule);
iopBios->RegisterHleModuleReplacement("CD/DVD_Compatible", acCdvdModule);
2022-12-20 15:39:16 -05:00
//Taiko no Tatsujin loads and use these, but we don't have a proper HLE
//for PADMAN at the version that's provided by the SYS2x6 BIOS.
//Using our current HLE PADMAN causes the game to crash due to differences in structure layouts.
//Bloody Roar 3 also loads PADMAN and expects some response from it.
//Just provide a dummy module instead to make sure loading succeeds.
//Games rely on JVS for input anyways, so, it shouldn't be a problem if they can't use PADMAN.
auto padManModule = std::make_shared<Iop::Namco::CPadMan>();
iopBios->RegisterHleModuleReplacement("rom0:PADMAN", padManModule);
iopBios->RegisterHleModuleReplacement("rom0:SIO2MAN", padManModule);
2022-10-03 18:46:38 -04:00
{
auto namcoArcadeModule = std::make_shared<Iop::CNamcoArcade>(*iopBios->GetSifman(), *iopBios->GetSifcmd(), *acRam, def.id);
2022-10-03 18:46:38 -04:00
iopBios->RegisterModule(namcoArcadeModule);
2022-10-15 11:41:38 -04:00
iopBios->RegisterHleModuleReplacement("rom0:DAEMON", namcoArcadeModule);
2022-10-09 10:22:01 -04:00
virtualMachine->m_pad->InsertListener(namcoArcadeModule.get());
for(const auto& buttonPair : def.buttons)
{
namcoArcadeModule->SetButton(buttonPair.first, buttonPair.second);
}
switch(def.inputMode)
{
case ARCADE_MACHINE_DEF::INPUT_MODE::LIGHTGUN:
virtualMachine->SetGunListener(namcoArcadeModule.get());
namcoArcadeModule->SetJvsMode(Iop::CNamcoArcade::JVS_MODE::LIGHTGUN);
namcoArcadeModule->SetLightGunXform(def.lightGunXform);
break;
case ARCADE_MACHINE_DEF::INPUT_MODE::DRUM:
namcoArcadeModule->SetJvsMode(Iop::CNamcoArcade::JVS_MODE::DRUM);
break;
case ARCADE_MACHINE_DEF::INPUT_MODE::DRIVE:
namcoArcadeModule->SetJvsMode(Iop::CNamcoArcade::JVS_MODE::DRIVE);
break;
default:
break;
}
2022-10-03 18:46:38 -04:00
}
2022-09-29 16:02:50 -04:00
}
2023-06-09 12:51:41 -04:00
virtualMachine->SetEeFrequencyScale(def.eeFreqScaleNumerator, def.eeFreqScaleDenominator);
if((def.eeFreqScaleNumerator != 1) || (def.eeFreqScaleDenominator != 1))
{
//Adjust SPU sampling rate with EE frequency scale. Not quite sure this is right.
uint32 baseSamplingRate = Iop::Spu2::CCore::DEFAULT_BASE_SAMPLING_RATE * def.eeFreqScaleNumerator / def.eeFreqScaleDenominator;
virtualMachine->m_iop->m_spu2.GetCore(0)->SetBaseSamplingRate(baseSamplingRate);
virtualMachine->m_iop->m_spu2.GetCore(1)->SetBaseSamplingRate(baseSamplingRate);
}
}
2022-09-29 16:02:50 -04:00
void ApplyPatchesFromArcadeDefinition(CPS2VM* virtualMachine, const ARCADE_MACHINE_DEF& def)
{
2022-09-30 18:20:56 -04:00
for(const auto& patch : def.patches)
{
assert(patch.address < PS2::EE_RAM_SIZE);
*reinterpret_cast<uint32*>(virtualMachine->m_ee->m_ram + patch.address) = patch.value;
}
}
void ArcadeUtils::RegisterArcadeMachines()
{
//Remove any arcade bootable registered the old way
//TEMP: Remove this when merging back to main
{
auto arcadeBootables = BootablesDb::CClient::GetInstance().GetBootables(BootablesDb::CClient::SORT_METHOD_ARCADE);
for(const auto& bootable : arcadeBootables)
{
if(bootable.path.has_parent_path())
{
BootablesDb::CClient::GetInstance().UnregisterBootable(bootable.path);
}
}
}
2022-12-20 15:39:16 -05:00
auto arcadeDefsPath = Framework::PathUtils::GetAppResourcesPath() / "arcadedefs";
if(!fs::exists(arcadeDefsPath))
{
return;
}
for(const auto& entry : fs::directory_iterator(arcadeDefsPath))
{
auto arcadeDefPath = entry.path();
auto arcadeDefFilename = arcadeDefPath.filename();
try
{
auto def = ReadArcadeMachineDefinition(arcadeDefsPath / arcadeDefFilename);
BootablesDb::CClient::GetInstance().RegisterBootable(arcadeDefFilename, "", "");
BootablesDb::CClient::GetInstance().SetTitle(arcadeDefFilename, def.name.c_str());
}
catch(const std::exception& exception)
{
printf("Warning: Failed to register arcade machine '%s': %s\r\n",
2023-05-31 13:57:47 -04:00
arcadeDefFilename.string().c_str(), exception.what());
}
}
}
void ArcadeUtils::BootArcadeMachine(CPS2VM* virtualMachine, const fs::path& arcadeDefFilename)
{
auto arcadeDefsPath = Framework::PathUtils::GetAppResourcesPath() / "arcadedefs";
auto def = ReadArcadeMachineDefinition(arcadeDefsPath / arcadeDefFilename);
//Reset PS2VM
virtualMachine->Pause();
virtualMachine->Reset(PS2::EE_EXT_RAM_SIZE, PS2::IOP_EXT_RAM_SIZE);
2023-12-07 18:59:38 -05:00
if(def.driver == "sys147")
{
PrepareNamcoSys147Environment(virtualMachine, def);
return;
}
PrepareArcadeEnvironment(virtualMachine, def);
2022-12-20 15:39:16 -05:00
//Boot mc0:/BOOT (from def)
2022-12-20 15:39:16 -05:00
virtualMachine->m_ee->m_os->BootFromVirtualPath(def.boot.c_str(), {"DANGLE"});
ApplyPatchesFromArcadeDefinition(virtualMachine, def);
2022-12-20 15:39:16 -05:00
virtualMachine->BeforeExecutableReloaded =
2022-12-20 15:39:16 -05:00
[def](CPS2VM* virtualMachine) {
PrepareArcadeEnvironment(virtualMachine, def);
};
virtualMachine->AfterExecutableReloaded =
2022-12-20 15:39:16 -05:00
[def](CPS2VM* virtualMachine) {
ApplyPatchesFromArcadeDefinition(virtualMachine, def);
};
2022-09-29 16:02:50 -04:00
#ifndef DEBUGGER_INCLUDED
virtualMachine->Resume();
#endif
TryUpdateLastBootedTime(arcadeDefFilename);
2022-09-29 16:02:50 -04:00
}