2022-09-29 16:02:50 -04:00
|
|
|
#include "ArcadeUtils.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include "string_format.h"
|
2022-12-11 16:23:09 -05:00
|
|
|
#include "PathUtils.h"
|
2022-09-29 16:02:50 -04:00
|
|
|
#include "StdStreamUtils.h"
|
|
|
|
#include "PS2VM.h"
|
|
|
|
#include "PS2VM_Preferences.h"
|
|
|
|
#include "AppConfig.h"
|
2022-12-11 16:23:09 -05:00
|
|
|
#include "BootablesDbClient.h"
|
2022-09-29 16:02:50 -04:00
|
|
|
#include "BootablesProcesses.h"
|
2023-12-16 10:53:17 -05:00
|
|
|
#include "ArcadeDefinition.h"
|
2022-09-29 16:02:50 -04:00
|
|
|
|
2023-12-16 10:53:17 -05:00
|
|
|
#include "arcadedrivers/NamcoSys246Driver.h"
|
|
|
|
#include "arcadedrivers/NamcoSys147Driver.h"
|
2023-06-22 09:40:25 -04:00
|
|
|
|
2023-12-16 10:53:17 -05:00
|
|
|
// clang-format off
|
|
|
|
static const std::pair<const char*, ARCADE_MACHINE_DEF::DRIVER> g_driverValues[] =
|
|
|
|
{
|
|
|
|
{ "sys246", ARCADE_MACHINE_DEF::DRIVER::NAMCO_SYSTEM_246 },
|
|
|
|
{ "sys147", ARCADE_MACHINE_DEF::DRIVER::NAMCO_SYSTEM_147 },
|
2022-09-29 16:02:50 -04:00
|
|
|
};
|
|
|
|
|
2023-05-22 10:25:40 -04:00
|
|
|
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 }
|
|
|
|
};
|
2023-06-22 09:40:25 -04:00
|
|
|
|
|
|
|
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 },
|
2023-07-15 13:50:53 +02:00
|
|
|
{ "drive", ARCADE_MACHINE_DEF::INPUT_MODE::DRIVE },
|
2024-02-15 15:36:23 +08:00
|
|
|
{ "touch", ARCADE_MACHINE_DEF::INPUT_MODE::TOUCH },
|
2023-06-22 09:40:25 -04:00
|
|
|
};
|
2023-05-30 21:00:01 -04:00
|
|
|
// clang-format on
|
2023-05-22 10:25:40 -04:00
|
|
|
|
2023-06-22 09:40:25 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-12-07 19:05:40 -05:00
|
|
|
void ApplyPatchesFromArcadeDefinition(CPS2VM* virtualMachine, const ARCADE_MACHINE_DEF& def)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-29 16:02:50 -04:00
|
|
|
ARCADE_MACHINE_DEF ReadArcadeMachineDefinition(const fs::path& arcadeDefPath)
|
|
|
|
{
|
2023-05-22 10:25:40 -04:00
|
|
|
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();
|
2024-05-17 15:41:52 -04:00
|
|
|
const char* buttonSelector = buttonPair.second.c_str();
|
2023-05-30 21:00:01 -04:00
|
|
|
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));
|
|
|
|
}
|
2024-05-17 15:41:52 -04:00
|
|
|
//Accepted formats for buttonSelector
|
|
|
|
//- ${buttonName}
|
|
|
|
//- ${padIdx}:${buttonName}
|
|
|
|
std::string buttonName = buttonSelector;
|
|
|
|
int padIdx = -1;
|
|
|
|
if(auto colonPos = buttonName.find(':'); colonPos != std::string::npos)
|
|
|
|
{
|
|
|
|
padIdx = strtol(buttonName.c_str(), &endPtr, 10);
|
|
|
|
assert(endPtr == (buttonName.c_str() + colonPos));
|
|
|
|
buttonName = buttonName.substr(colonPos + 1);
|
|
|
|
}
|
|
|
|
auto buttonId = ParseEnumValue(buttonName.c_str(), std::begin(g_buttonValues), std::end(g_buttonValues));
|
|
|
|
auto selector = ARCADE_MACHINE_DEF::ButtonSelector{padIdx, buttonId};
|
|
|
|
buttons[number] = selector;
|
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"))
|
|
|
|
{
|
2023-12-16 10:53:17 -05:00
|
|
|
std::string driverName = defJson["driver"];
|
|
|
|
def.driver = ParseEnumValue(driverName.c_str(), std::begin(g_driverValues), std::end(g_driverValues));
|
2023-12-07 18:59:38 -05:00
|
|
|
}
|
2022-12-11 16:23:09 -05:00
|
|
|
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"];
|
2024-03-18 10:08:35 -04:00
|
|
|
if(defJson["nand"].contains("mounts"))
|
|
|
|
{
|
|
|
|
def.nandMounts = defJson["nand"]["mounts"].get<std::map<std::string, uint32>>();
|
|
|
|
}
|
2023-12-07 18:59:38 -05:00
|
|
|
}
|
2023-05-22 10:25:40 -04:00
|
|
|
if(defJson.contains("buttons"))
|
|
|
|
{
|
|
|
|
def.buttons = parseButtons(defJson["buttons"]);
|
|
|
|
}
|
2023-06-22 09:40:25 -04:00
|
|
|
if(defJson.contains("inputMode"))
|
2023-04-27 19:16:43 -04:00
|
|
|
{
|
2023-06-22 09:40:25 -04:00
|
|
|
std::string inputModeString = defJson["inputMode"];
|
|
|
|
def.inputMode = ParseEnumValue(inputModeString.c_str(), std::begin(g_inputModeValues), std::end(g_inputModeValues));
|
2023-04-27 19:16:43 -04:00
|
|
|
}
|
2024-02-17 17:34:45 +08:00
|
|
|
if(defJson.contains("screenPosXform"))
|
2023-05-03 20:59:43 -04:00
|
|
|
{
|
2024-02-17 17:34:45 +08:00
|
|
|
auto screenPosXformArray = defJson["screenPosXform"];
|
|
|
|
if(screenPosXformArray.is_array() && (screenPosXformArray.size() >= 4))
|
2023-05-03 20:59:43 -04:00
|
|
|
{
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
|
|
{
|
2024-02-17 17:34:45 +08:00
|
|
|
def.screenPosXform[i] = screenPosXformArray[i];
|
2023-05-03 20:59:43 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-31 09:20:55 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-03-18 10:08:35 -04:00
|
|
|
void ApplyParentDefValues(ARCADE_MACHINE_DEF& def, const ARCADE_MACHINE_DEF& parentDef)
|
|
|
|
{
|
|
|
|
if(def.nandFileName.empty())
|
|
|
|
{
|
|
|
|
def.nandFileName = parentDef.nandFileName;
|
|
|
|
}
|
|
|
|
if(def.nandMounts.empty())
|
|
|
|
{
|
|
|
|
def.nandMounts = parentDef.nandMounts;
|
|
|
|
}
|
2024-05-17 15:41:52 -04:00
|
|
|
if(def.buttons.empty())
|
|
|
|
{
|
|
|
|
def.buttons = parentDef.buttons;
|
|
|
|
}
|
2024-03-18 10:08:35 -04:00
|
|
|
}
|
|
|
|
|
2022-12-11 16:23:09 -05:00
|
|
|
void ArcadeUtils::RegisterArcadeMachines()
|
2022-11-10 22:06:18 -05:00
|
|
|
{
|
2024-03-22 17:15:20 -04:00
|
|
|
auto arcadeDefsPath = Framework::PathUtils::GetAppResourcesPath() / "arcadedefs";
|
|
|
|
if(!fs::exists(arcadeDefsPath))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//This set is used to track inactive arcadedefs we might have in the database
|
|
|
|
std::set<fs::path> inactiveArcadeDefs;
|
|
|
|
|
|
|
|
//Collect all arcadedef files we have registered in the database
|
2022-12-11 16:23:09 -05:00
|
|
|
{
|
|
|
|
auto arcadeBootables = BootablesDb::CClient::GetInstance().GetBootables(BootablesDb::CClient::SORT_METHOD_ARCADE);
|
|
|
|
for(const auto& bootable : arcadeBootables)
|
|
|
|
{
|
2024-03-22 17:15:20 -04:00
|
|
|
inactiveArcadeDefs.insert(bootable.path.filename());
|
2022-12-11 16:23:09 -05:00
|
|
|
}
|
|
|
|
}
|
2022-12-20 15:39:16 -05:00
|
|
|
|
2022-12-11 16:23:09 -05:00
|
|
|
for(const auto& entry : fs::directory_iterator(arcadeDefsPath))
|
|
|
|
{
|
|
|
|
auto arcadeDefPath = entry.path();
|
|
|
|
auto arcadeDefFilename = arcadeDefPath.filename();
|
2024-03-22 17:15:20 -04:00
|
|
|
//Remove this arcadedef from the inactive set
|
|
|
|
inactiveArcadeDefs.erase(arcadeDefFilename);
|
2022-12-11 16:23:09 -05:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto def = ReadArcadeMachineDefinition(arcadeDefsPath / arcadeDefFilename);
|
|
|
|
BootablesDb::CClient::GetInstance().RegisterBootable(arcadeDefFilename, "", "");
|
|
|
|
BootablesDb::CClient::GetInstance().SetTitle(arcadeDefFilename, def.name.c_str());
|
|
|
|
}
|
2023-05-22 10:25:40 -04:00
|
|
|
catch(const std::exception& exception)
|
2022-12-11 16:23:09 -05:00
|
|
|
{
|
2023-05-22 10:25:40 -04:00
|
|
|
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());
|
2022-12-11 16:23:09 -05:00
|
|
|
}
|
|
|
|
}
|
2024-03-22 17:15:20 -04:00
|
|
|
|
|
|
|
//Prune any remaining arcadedef
|
|
|
|
for(const auto& inactiveArcadeDef : inactiveArcadeDefs)
|
|
|
|
{
|
|
|
|
BootablesDb::CClient::GetInstance().UnregisterBootable(inactiveArcadeDef);
|
|
|
|
}
|
2022-12-11 16:23:09 -05:00
|
|
|
}
|
|
|
|
|
2023-12-16 10:53:17 -05:00
|
|
|
static CNamcoSys246Driver g_sys246Driver;
|
|
|
|
static CNamcoSys147Driver g_sys147Driver;
|
2023-12-20 19:41:12 -05:00
|
|
|
// clang-format off
|
2023-12-16 10:53:17 -05:00
|
|
|
static CArcadeDriver* g_drivers[] =
|
|
|
|
{
|
|
|
|
nullptr,
|
|
|
|
&g_sys246Driver,
|
|
|
|
&g_sys147Driver,
|
|
|
|
};
|
2023-12-20 19:41:12 -05:00
|
|
|
// clang-format on
|
2023-12-16 10:53:17 -05:00
|
|
|
|
2022-12-11 16:23:09 -05:00
|
|
|
void ArcadeUtils::BootArcadeMachine(CPS2VM* virtualMachine, const fs::path& arcadeDefFilename)
|
|
|
|
{
|
|
|
|
auto arcadeDefsPath = Framework::PathUtils::GetAppResourcesPath() / "arcadedefs";
|
|
|
|
auto def = ReadArcadeMachineDefinition(arcadeDefsPath / arcadeDefFilename);
|
2024-03-18 10:08:35 -04:00
|
|
|
if(!def.parent.empty())
|
|
|
|
{
|
|
|
|
auto parentDefPath = arcadeDefsPath / (def.parent + ".arcadedef");
|
|
|
|
auto parentDef = ReadArcadeMachineDefinition(parentDefPath);
|
|
|
|
ApplyParentDefValues(def, parentDef);
|
|
|
|
}
|
2023-12-20 19:41:12 -05:00
|
|
|
|
2022-11-10 22:06:18 -05:00
|
|
|
//Reset PS2VM
|
|
|
|
virtualMachine->Pause();
|
2023-06-08 17:12:43 -04:00
|
|
|
virtualMachine->Reset(PS2::EE_EXT_RAM_SIZE, PS2::IOP_EXT_RAM_SIZE);
|
2022-11-10 22:06:18 -05:00
|
|
|
|
2023-12-16 11:06:32 -05:00
|
|
|
if(def.driver == ARCADE_MACHINE_DEF::DRIVER::UNKNOWN)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Arcade driver unspecified.");
|
|
|
|
}
|
2023-12-20 19:41:12 -05:00
|
|
|
|
2023-12-16 10:53:17 -05:00
|
|
|
auto driver = g_drivers[def.driver];
|
|
|
|
driver->PrepareEnvironment(virtualMachine, def);
|
|
|
|
driver->Launch(virtualMachine, def);
|
2023-12-20 19:41:12 -05:00
|
|
|
|
2022-11-10 22:06:18 -05:00
|
|
|
ApplyPatchesFromArcadeDefinition(virtualMachine, def);
|
2022-12-20 15:39:16 -05:00
|
|
|
|
2022-11-10 22:06:18 -05:00
|
|
|
virtualMachine->BeforeExecutableReloaded =
|
2022-12-20 15:39:16 -05:00
|
|
|
[def](CPS2VM* virtualMachine) {
|
2023-12-20 19:41:12 -05:00
|
|
|
auto driver = g_drivers[def.driver];
|
|
|
|
driver->PrepareEnvironment(virtualMachine, def);
|
2022-12-20 15:39:16 -05:00
|
|
|
};
|
|
|
|
|
2022-11-10 22:06:18 -05:00
|
|
|
virtualMachine->AfterExecutableReloaded =
|
2022-12-20 15:39:16 -05:00
|
|
|
[def](CPS2VM* virtualMachine) {
|
|
|
|
ApplyPatchesFromArcadeDefinition(virtualMachine, def);
|
|
|
|
};
|
2022-09-29 16:02:50 -04:00
|
|
|
|
2022-10-06 13:29:13 -04:00
|
|
|
#ifndef DEBUGGER_INCLUDED
|
|
|
|
virtualMachine->Resume();
|
|
|
|
#endif
|
|
|
|
|
2022-12-11 16:23:09 -05:00
|
|
|
TryUpdateLastBootedTime(arcadeDefFilename);
|
2022-09-29 16:02:50 -04:00
|
|
|
}
|