GameDB: Merge traits/compatibility into single file

This commit is contained in:
Stenzek
2023-08-19 22:00:20 +10:00
parent e501012a68
commit cee5decec4
14 changed files with 8038 additions and 8316 deletions

View File

@ -13,7 +13,6 @@
#include "rapidjson/document.h"
#include "rapidjson/error/en.h"
#include "system.h"
#include "tinyxml2.h"
#include "util/cd_image.h"
#include <iomanip>
#include <memory>
@ -24,14 +23,13 @@ Log_SetChannel(GameDatabase);
#ifdef _WIN32
#include "common/windows_headers.h"
#endif
#include "SimpleIni.h"
namespace GameDatabase {
enum : u32
{
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48,
GAME_DATABASE_CACHE_VERSION = 2,
GAME_DATABASE_CACHE_VERSION = 3,
};
static Entry* GetMutableEntry(const std::string_view& serial);
@ -42,31 +40,29 @@ static bool SaveToCache();
static bool LoadGameDBJson();
static bool ParseJsonEntry(Entry* entry, const rapidjson::Value& value);
static bool ParseJsonCodes(u32 index, const rapidjson::Value& value);
static bool LoadGameSettingsIni();
static bool ParseGameSettingsIniEntry(const CSimpleIniA& ini, const char* section);
static bool LoadGameCompatibilityXml();
static bool LoadTrackHashes();
std::array<std::pair<const char*, const char*>, static_cast<u32>(GameDatabase::Trait::Count)> s_trait_names = {{
{"ForceInterpreter", TRANSLATABLE("GameSettingsTrait", "Force Interpreter")},
{"ForceSoftwareRenderer", TRANSLATABLE("GameSettingsTrait", "Force Software Renderer")},
{"ForceSoftwareRendererForReadbacks", TRANSLATABLE("GameSettingsTrait", "Force Software Renderer For Readbacks")},
{"ForceInterlacing", TRANSLATABLE("GameSettingsTrait", "Force Interlacing")},
{"DisableTrueColor", TRANSLATABLE("GameSettingsTrait", "Disable True Color")},
{"DisableUpscaling", TRANSLATABLE("GameSettingsTrait", "Disable Upscaling")},
{"DisableScaledDithering", TRANSLATABLE("GameSettingsTrait", "Disable Scaled Dithering")},
{"DisableForceNTSCTimings", TRANSLATABLE("GameSettingsTrait", "Disallow Forcing NTSC Timings")},
{"DisableWidescreen", TRANSLATABLE("GameSettingsTrait", "Disable Widescreen")},
{"DisablePGXP", TRANSLATABLE("GameSettingsTrait", "Disable PGXP")},
{"DisablePGXPCulling", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Culling")},
{"DisablePGXPTextureCorrection", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Perspective Correct Textures")},
{"DisablePGXPColorCorrection", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Perspective Correct Colors")},
{"DisablePGXPDepthBuffer", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Depth Buffer")},
{"ForcePGXPVertexCache", TRANSLATABLE("GameSettingsTrait", "Force PGXP Vertex Cache")},
{"ForcePGXPCPUMode", TRANSLATABLE("GameSettingsTrait", "Force PGXP CPU Mode")},
{"ForceRecompilerMemoryExceptions", TRANSLATABLE("GameSettingsTrait", "Force Recompiler Memory Exceptions")},
{"ForceRecompilerICache", TRANSLATABLE("GameSettingsTrait", "Force Recompiler ICache")},
{"ForceRecompilerLUTFastmem", TRANSLATABLE("GameSettingsTrait", "Force Recompiler LUT Fastmem")},
std::array<const char*, static_cast<u32>(GameDatabase::Trait::Count)> s_trait_names = {{
"ForceInterpreter",
"ForceSoftwareRenderer",
"ForceSoftwareRendererForReadbacks",
"ForceInterlacing",
"DisableTrueColor",
"DisableUpscaling",
"DisableScaledDithering",
"DisableForceNTSCTimings",
"DisableWidescreen",
"DisablePGXP",
"DisablePGXPCulling",
"DisablePGXPTextureCorrection",
"DisablePGXPColorCorrection",
"DisablePGXPDepthBuffer",
"ForcePGXPVertexCache",
"ForcePGXPCPUMode",
"ForceRecompilerMemoryExceptions",
"ForceRecompilerICache",
"ForceRecompilerLUTFastmem",
"IsLibCryptProtected",
}};
static bool s_loaded = false;
@ -93,8 +89,6 @@ void GameDatabase::EnsureLoaded()
s_code_lookup = {};
LoadGameDBJson();
LoadGameSettingsIni();
LoadGameCompatibilityXml();
SaveToCache();
}
@ -180,13 +174,7 @@ GameDatabase::Entry* GameDatabase::GetMutableEntry(const std::string_view& seria
const char* GameDatabase::GetTraitName(Trait trait)
{
DebugAssert(trait < Trait::Count);
return s_trait_names[static_cast<u32>(trait)].first;
}
const char* GameDatabase::GetTraitDisplayName(Trait trait)
{
DebugAssert(trait < Trait::Count);
return s_trait_names[static_cast<u32>(trait)].second;
return s_trait_names[static_cast<u32>(trait)];
}
const char* GameDatabase::GetCompatibilityRatingName(CompatibilityRating rating)
@ -263,9 +251,10 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
{
if (display_osd_messages && settings.gpu_renderer != GPURenderer::Software)
{
Host::AddKeyedOSDMessage("gamedb_force_software_rb",
Host::TranslateStdString("OSDMessage", "Using software renderer for readbacks based on game settings."),
osd_duration);
Host::AddKeyedOSDMessage(
"gamedb_force_software_rb",
Host::TranslateStdString("OSDMessage", "Using software renderer for readbacks based on game settings."),
osd_duration);
}
settings.gpu_use_software_renderer_for_readbacks = true;
@ -716,6 +705,18 @@ static bool GetStringFromObject(const rapidjson::Value& object, const char* key,
return true;
}
static bool GetBoolFromObject(const rapidjson::Value& object, const char* key, bool* dest)
{
*dest = false;
auto member = object.FindMember(key);
if (member == object.MemberEnd() || !member->value.IsBool())
return false;
*dest = member->value.GetBool();
return true;
}
template<typename T>
static bool GetUIntFromObject(const rapidjson::Value& object, const char* key, T* dest)
{
@ -746,9 +747,38 @@ static bool GetArrayOfStringsFromObject(const rapidjson::Value& object, const ch
return true;
}
template<typename T>
static std::optional<T> GetOptionalIntFromObject(const rapidjson::Value& object, const char* key)
{
auto member = object.FindMember(key);
if (member == object.MemberEnd() || !member->value.IsInt())
return std::nullopt;
return static_cast<T>(member->value.GetInt());
}
template<typename T>
static std::optional<T> GetOptionalUIntFromObject(const rapidjson::Value& object, const char* key)
{
auto member = object.FindMember(key);
if (member == object.MemberEnd() || !member->value.IsUint())
return std::nullopt;
return static_cast<T>(member->value.GetUint());
}
static std::optional<float> GetOptionalFloatFromObject(const rapidjson::Value& object, const char* key)
{
auto member = object.FindMember(key);
if (member == object.MemberEnd() || !member->value.IsFloat())
return std::nullopt;
return member->value.GetFloat();
}
bool GameDatabase::LoadGameDBJson()
{
std::optional<std::string> gamedb_data(Host::ReadResourceFileToString("database/gamedb.json"));
std::optional<std::string> gamedb_data(Host::ReadResourceFileToString("gamedb.json"));
if (!gamedb_data.has_value())
{
Log_ErrorPrintf("Failed to read game database");
@ -837,7 +867,7 @@ bool GameDatabase::ParseJsonEntry(Entry* entry, const rapidjson::Value& value)
}
entry->supported_controllers = ~0u;
auto controllers = value.FindMember("controllers");
const auto controllers = value.FindMember("controllers");
if (controllers != value.MemberEnd())
{
if (controllers->value.IsArray())
@ -873,6 +903,57 @@ bool GameDatabase::ParseJsonEntry(Entry* entry, const rapidjson::Value& value)
}
}
const auto compatibility = value.FindMember("compatibility");
if (compatibility != value.MemberEnd())
{
if (compatibility->value.IsObject())
{
u32 rating;
if (GetUIntFromObject(compatibility->value, "rating", &rating) &&
rating < static_cast<u32>(CompatibilityRating::Count))
{
entry->compatibility = static_cast<CompatibilityRating>(rating);
}
}
else
{
Log_WarningPrintf("compatibility is not an object");
}
}
if (entry->serial == "SCES-02105")
__debugbreak();
const auto traits = value.FindMember("traits");
if (traits != value.MemberEnd())
{
if (traits->value.IsObject())
{
const auto& traitsobj = traits->value;
for (u32 trait = 0; trait < static_cast<u32>(Trait::Count); trait++)
{
bool bvalue;
if (GetBoolFromObject(traitsobj, s_trait_names[trait], &bvalue) && bvalue)
entry->traits[trait] = bvalue;
}
entry->display_active_start_offset = GetOptionalIntFromObject<s16>(traitsobj, "DisplayActiveStartOffset");
entry->display_active_end_offset = GetOptionalIntFromObject<s16>(traitsobj, "DisplayActiveEndOffset");
entry->display_line_start_offset = GetOptionalIntFromObject<s8>(traitsobj, "DisplayLineStartOffset");
entry->display_line_end_offset = GetOptionalIntFromObject<s8>(traitsobj, "DisplayLineEndOffset");
entry->dma_max_slice_ticks = GetOptionalUIntFromObject<u32>(traitsobj, "DMAMaxSliceTicks");
entry->dma_halt_ticks = GetOptionalUIntFromObject<u32>(traitsobj, "DMAHaltTicks");
entry->gpu_fifo_size = GetOptionalUIntFromObject<u32>(traitsobj, "GPUFIFOSize");
entry->gpu_max_run_ahead = GetOptionalUIntFromObject<u32>(traitsobj, "GPUMaxRunAhead");
entry->gpu_pgxp_tolerance = GetOptionalFloatFromObject(traitsobj, "GPUPGXPTolerance");
entry->gpu_pgxp_depth_threshold = GetOptionalFloatFromObject(traitsobj, "GPUPGXPDepthThreshold");
}
else
{
Log_WarningPrintf("traits is not an object");
}
}
return true;
}
@ -915,151 +996,6 @@ bool GameDatabase::ParseJsonCodes(u32 index, const rapidjson::Value& value)
return (added > 0);
}
bool GameDatabase::LoadGameSettingsIni()
{
std::optional<std::string> gamedb_data(Host::ReadResourceFileToString("database/gamesettings.ini"));
if (!gamedb_data.has_value())
{
Log_ErrorPrintf("Failed to read gamesettings database");
return false;
}
CSimpleIniA ini;
SI_Error err = ini.LoadData(gamedb_data->data(), gamedb_data->size());
if (err != SI_OK)
{
Log_ErrorPrintf("Failed to parse game settings ini: %d", static_cast<int>(err));
return false;
}
std::list<CSimpleIniA::Entry> sections;
ini.GetAllSections(sections);
for (const CSimpleIniA::Entry& section_entry : sections)
ParseGameSettingsIniEntry(ini, section_entry.pItem);
Log_InfoPrintf("Loaded %zu gamesettings entries", sections.size());
return true;
}
bool GameDatabase::ParseGameSettingsIniEntry(const CSimpleIniA& ini, const char* section)
{
Entry* entry = GetMutableEntry(section);
if (!entry)
{
Log_ErrorPrintf("Unknown game serial '%s' in gamesettings", section);
return false;
}
for (u32 trait = 0; trait < static_cast<u32>(Trait::Count); trait++)
{
if (ini.GetBoolValue(section, s_trait_names[trait].first, false))
entry->traits[trait] = true;
}
long lvalue = ini.GetLongValue(section, "DisplayActiveStartOffset", 0);
if (lvalue != 0)
entry->display_active_start_offset = static_cast<s16>(lvalue);
lvalue = ini.GetLongValue(section, "DisplayActiveEndOffset", 0);
if (lvalue != 0)
entry->display_active_end_offset = static_cast<s16>(lvalue);
lvalue = ini.GetLongValue(section, "DisplayLineStartOffset", 0);
if (lvalue != 0)
entry->display_line_start_offset = static_cast<s8>(lvalue);
lvalue = ini.GetLongValue(section, "DisplayLineEndOffset", 0);
if (lvalue != 0)
entry->display_line_end_offset = static_cast<s8>(lvalue);
lvalue = ini.GetLongValue(section, "DMAMaxSliceTicks", 0);
if (lvalue > 0)
entry->dma_max_slice_ticks = static_cast<u32>(lvalue);
lvalue = ini.GetLongValue(section, "DMAHaltTicks", 0);
if (lvalue > 0)
entry->dma_halt_ticks = static_cast<u32>(lvalue);
lvalue = ini.GetLongValue(section, "GPUFIFOSize", 0);
if (lvalue > 0)
entry->gpu_fifo_size = static_cast<u32>(lvalue);
lvalue = ini.GetLongValue(section, "GPUMaxRunAhead", 0);
if (lvalue > 0)
entry->gpu_max_run_ahead = static_cast<u32>(lvalue);
float fvalue = static_cast<float>(ini.GetDoubleValue(section, "GPUPGXPTolerance", -1.0f));
if (fvalue >= 0.0f)
entry->gpu_pgxp_tolerance = fvalue;
fvalue = static_cast<float>(ini.GetDoubleValue(section, "GPUPGXPDepthThreshold", -1.0f));
if (fvalue > 0.0f)
entry->gpu_pgxp_depth_threshold = fvalue;
return true;
}
class CompatibilityListVisitor final : public tinyxml2::XMLVisitor
{
public:
ALWAYS_INLINE u32 GetCount() const { return m_count; }
bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) override
{
// recurse into gamelist
if (StringUtil::Strcasecmp(element.Name(), "compatibility-list") == 0)
return true;
if (StringUtil::Strcasecmp(element.Name(), "entry") != 0)
return false;
const char* attr = element.Attribute("code");
std::string code(attr ? attr : "");
const u32 compatibility = static_cast<u32>(element.IntAttribute("compatibility"));
if (code.empty() || compatibility >= static_cast<u32>(GameDatabase::CompatibilityRating::Count))
{
Log_ErrorPrintf("Missing child node at line %d", element.GetLineNum());
return false;
}
GameDatabase::Entry* entry = GameDatabase::GetMutableEntry(code);
if (!entry)
{
Log_ErrorPrintf("Unknown serial in compatibility list: '%s'", code.c_str());
return false;
}
entry->compatibility = static_cast<GameDatabase::CompatibilityRating>(compatibility);
m_count++;
return false;
}
private:
u32 m_count = 0;
};
bool GameDatabase::LoadGameCompatibilityXml()
{
std::optional<std::string> xml(Host::ReadResourceFileToString("database/compatibility.xml"));
if (!xml.has_value())
{
Log_ErrorPrintf("Failed to load compatibility.xml from package");
return false;
}
tinyxml2::XMLDocument doc;
tinyxml2::XMLError error = doc.Parse(xml->c_str(), xml->size());
if (error != tinyxml2::XML_SUCCESS)
{
Log_ErrorPrintf("Failed to parse compatibility list: %s", tinyxml2::XMLDocument::ErrorIDToName(error));
return false;
}
const tinyxml2::XMLElement* datafile_elem = doc.FirstChildElement("compatibility-list");
if (!datafile_elem)
{
Log_ErrorPrintf("Failed to get compatibility-list element");
return false;
}
CompatibilityListVisitor visitor;
datafile_elem->Accept(&visitor);
Log_InfoPrintf("Loaded %u entries from compatibility list", visitor.GetCount());
return true;
}
void GameDatabase::EnsureTrackHashesMapLoaded()
{
if (s_track_hashes_loaded)
@ -1071,7 +1007,7 @@ void GameDatabase::EnsureTrackHashesMapLoaded()
bool GameDatabase::LoadTrackHashes()
{
std::optional<std::string> gamedb_data(Host::ReadResourceFileToString("database/gamedb.json"));
std::optional<std::string> gamedb_data(Host::ReadResourceFileToString("gamedb.json"));
if (!gamedb_data.has_value())
{
Log_ErrorPrintf("Failed to read game database");