libretro-common: ryaml, and the GameDB reads through it

ryaml is a YAML reader for the block subset plus single-line flow
collections, which is what GameIndex.yaml is written in. One
translation unit, C89, and no file I/O: the caller hands over bytes,
as the rest of formats/ does. Scalars are borrowed from that buffer
rather than copied, so only block scalars and escaped quoted scalars
reach the arena.

GameDatabase drops rapidyaml for it. The tree stays vendored because
StringUtil wants fast_float out of its ext/ directory, but the ryml
and c4core translation units are gone from both build systems, along
with the parse-error callback plumbing -- ryaml reports position by
return value. A node handle of RYAML_NONE is inert rather than
undefined, which is what lets the extraction sites chain lookups
without a guard at every step.

Verified against rapidyaml over the shipped GameIndex.yaml: identical
trees across all 62283 nodes, and identical extraction output across
all 12806 entries and 10370 fix and patch records. 3.7x faster to
parse (10.6ms to 2.8ms) and 9.3KB of text against 237KB. 90 unit
tests and 6000 mutated inputs under ASan, UBSan and LSan, clean.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-08-22 09:35:53 +02:00
1 parent f7e22b2da7
commit 43685d4ca9
8 files changed
+1848 -122

No files matched your search

+1 -24
View File
@@ -30,8 +30,6 @@ INCFLAGS := \
-I$(DEPS_DIR) \
-I$(DEPS_DIR)/include \
-I$(DEPS_DIR)/xbyak \
-I$(DEPS_DIR)/rapidyaml/rapidyaml/src \
-I$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src \
-I$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float/include \
-I$(DEPS_DIR)/d3d12memalloc/include
@@ -562,6 +560,7 @@ SOURCES_C += $(LIBRETRO_COMM_DIR)/encodings/encoding_rlz4.c
SOURCES_C += \
$(LIBRETRO_COMM_DIR)/formats/chd/rchd.c \
$(LIBRETRO_COMM_DIR)/formats/flac/rflac.c \
$(LIBRETRO_COMM_DIR)/formats/yaml/ryaml.c \
$(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_rzstd.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_huffman.c \
@@ -594,28 +593,6 @@ SOURCES_CC += \
$(wildcard $(DEPS_DIR)/vixl/src/aarch64/*.cc)
endif
# rapidyaml (rolls c4core into the same library, matching ryml.vcxproj)
SOURCES_CXX += \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/common.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/node.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/node_type.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/parse.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/preprocess.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/reference_resolver.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/tag.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/tree.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/src/c4/yml/version.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/base64.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/char_traits.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/error.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/format.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/language.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/memory_resource.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/memory_util.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/utf.cpp \
$(DEPS_DIR)/rapidyaml/rapidyaml/ext/c4core/src/c4/version.cpp
# D3D12MemAlloc (Windows only)
ifeq ($(IS_WINDOWS), 1)
SOURCES_CXX += $(DEPS_DIR)/d3d12memalloc/src/D3D12MemAlloc.cpp
-1
View File
@@ -15,7 +15,6 @@ option(LIBRETRO "Enables building the libretro core" ON)
set(CMAKE_BUILD_PO FALSE)
set(USE_SYSTEM_LIBS OFF)
add_definitions(-D__LIBRETRO__)
optional_system_library(ryml)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
option(LTO_PCSX2_CORE "Enable LTO/IPO/LTCG on the subset of pcsx2 that benefits most from it but not anything else")
#-------------------------------------------------------------------------------
+5 -5
View File
@@ -45,15 +45,15 @@ if((GCC_VERSION VERSION_EQUAL "9.0" OR GCC_VERSION VERSION_GREATER "9.0") AND GC
This text being in a compile log in an open issue may cause it to be closed.")
endif()
find_optional_system_library(ryml 3rdparty/rapidyaml/rapidyaml 0.11.1)
# zstd, libzip, lzma, and chdr are gone with their vendored trees: CHD
# decodes through rchd, ZIP members and CSO/gz through rinflate, all in
# libretro-common (see libretro/CMakeLists.txt for the source list).
# rapidyaml includes fast_float as a submodule, saves us pulling it in directly.
# Normally, we'd just pull in the cmake project, and link to it, but... it seems to enable
# permissive mode, which breaks other parts of PCSX2. So, we'll just create a target here
# for now.
# The rapidyaml tree is kept only for fast_float, which is header-only;
# YAML itself is read by ryaml in libretro-common. Normally we would just
# pull in the cmake project and link to it, but... it seems to enable
# permissive mode, which breaks other parts of PCSX2. So, we'll just create
# a target here for now.
#add_subdirectory(3rdparty/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float EXCLUDE_FROM_ALL)
add_library(fast_float INTERFACE)
target_include_directories(fast_float INTERFACE 3rdparty/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float/include)
+1
View File
@@ -23,6 +23,7 @@ target_sources(pcsx2_libretro PRIVATE
${CMAKE_SOURCE_DIR}/libretro/libretro-common/features/features_cpu.c
${CMAKE_SOURCE_DIR}/libretro/libretro-common/formats/chd/rchd.c
${CMAKE_SOURCE_DIR}/libretro/libretro-common/formats/flac/rflac.c
${CMAKE_SOURCE_DIR}/libretro/libretro-common/formats/yaml/ryaml.c
${CMAKE_SOURCE_DIR}/libretro/libretro-common/formats/7z/r7z_lzma.c
${CMAKE_SOURCE_DIR}/libretro/libretro-common/encodings/encoding_rzstd.c
${CMAKE_SOURCE_DIR}/libretro/libretro-common/encodings/encoding_huffman.c
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,279 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (ryaml.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* YAML reader for block-structured documents.
*
* The caller owns the bytes and hands them over; the reader never opens
* anything, in keeping with the rest of formats/. One call turns a
* buffer into a tree of nodes addressed by integer handles, and the
* handles stay valid until the tree is freed.
*
* Scalars are not copied. A node's key and value point into the caller's
* buffer, so that buffer has to outlive the tree. The two constructs
* that cannot be expressed as a subrange of the input -- block scalars,
* whose lines carry indentation that is not part of the value, and
* quoted scalars containing escapes -- are materialised into the tree's
* own arena instead. Everything else costs an offset and a length.
*
* Usage:
*
* ryaml_t *y = ryaml_parse(buf, len);
* if (y)
* {
* int root = ryaml_root(y);
* int n;
* for (n = ryaml_first_child(y, root); n >= 0;
* n = ryaml_next_sibling(y, n))
* {
* size_t klen;
* const char *k = ryaml_key(y, n, &klen);
* ...
* }
* ryaml_free(y);
* }
*
* The grammar is the block subset plus single-line flow collections:
* nested block mappings, block sequences, compact mappings written on a
* sequence dash, plain and quoted scalars, literal and folded block
* scalars with the three chomping modes, {} and [] flow collections,
* and comments. Anchors, aliases, tags, directives, multi-document
* streams and flow collections spanning several lines are not read; a
* document using them is rejected rather than half-parsed. Tabs are
* never indentation, which is what the specification says and what a
* hand-written file usually means anyway.
*/
#ifndef __LIBRETRO_SDK_FORMAT_RYAML_H
#define __LIBRETRO_SDK_FORMAT_RYAML_H
#include <stddef.h>
#include <stdint.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
/* Returned by the navigation calls when there is nothing to return.
* Every call below accepts RYAML_NONE and answers as if asked about an
* empty node, so a chain of lookups can be written without a test at
* each step. */
#define RYAML_NONE (-1)
enum ryaml_type
{
RYAML_TYPE_NONE = 0,
RYAML_TYPE_SCALAR, /* has a value */
RYAML_TYPE_MAP, /* has children, each with a key */
RYAML_TYPE_SEQ /* has children, none with a key */
};
typedef struct ryaml ryaml_t;
/**
* ryaml_parse:
* @buf : document bytes, borrowed, not copied
* @len : length of @buf in bytes
*
* Reads a whole document. @buf must stay valid and unmodified for as
* long as the returned tree is used, because scalars point into it.
*
* A byte order mark, if present, is skipped. CRLF and LF both count as
* line endings.
*
* Returns: the tree, or NULL if the document could not be read or the
* allocation failed. Use ryaml_parse_ex() to find out which.
*/
ryaml_t *ryaml_parse(const char *buf, size_t len);
/**
* ryaml_parse_ex:
* @buf : document bytes, borrowed, not copied
* @len : length of @buf in bytes
* @err_line : filled with the 1-based line of the fault
* @err_col : filled with the 1-based column of the fault
*
* As ryaml_parse(), and on failure reports where the reader stopped.
* Either pointer may be NULL. On success both are left alone.
*
* Returns: the tree, or NULL.
*/
ryaml_t *ryaml_parse_ex(const char *buf, size_t len,
size_t *err_line, size_t *err_col);
/**
* ryaml_free:
* @y : tree, may be NULL
*
* Releases the tree. The document buffer is untouched.
*/
void ryaml_free(ryaml_t *y);
/**
* ryaml_root:
* @y : tree
*
* Returns: the root node. An empty document still has a root; it simply
* has no children.
*/
int ryaml_root(const ryaml_t *y);
/**
* ryaml_count:
* @y : tree
*
* Returns: how many nodes the tree holds, root included. Useful for
* sizing a side table indexed by node handle.
*/
size_t ryaml_count(const ryaml_t *y);
int ryaml_first_child(const ryaml_t *y, int node);
int ryaml_last_child(const ryaml_t *y, int node);
int ryaml_next_sibling(const ryaml_t *y, int node);
int ryaml_parent(const ryaml_t *y, int node);
/**
* ryaml_num_children:
* @y : tree
* @node : node to count under
*
* Returns: the number of direct children, 0 for a scalar.
*/
size_t ryaml_num_children(const ryaml_t *y, int node);
/**
* ryaml_find_child:
* @y : tree
* @node : mapping to search
* @key : NUL-terminated key, matched exactly
*
* Returns: the child, or RYAML_NONE. Key comparison is case-sensitive,
* as YAML requires; a caller wanting otherwise should fold its own keys.
*/
int ryaml_find_child(const ryaml_t *y, int node, const char *key);
/**
* ryaml_find_child_len:
* @y : tree
* @node : mapping to search
* @key : key bytes, need not be NUL-terminated
* @key_len : length of @key
*
* Returns: the child, or RYAML_NONE.
*/
int ryaml_find_child_len(const ryaml_t *y, int node,
const char *key, size_t key_len);
/**
* ryaml_has_child:
* @y : tree
* @node : mapping to search
* @key : NUL-terminated key
*
* Returns: 1 if present, 0 otherwise.
*/
int ryaml_has_child(const ryaml_t *y, int node, const char *key);
int ryaml_type(const ryaml_t *y, int node);
int ryaml_is_map(const ryaml_t *y, int node);
int ryaml_is_seq(const ryaml_t *y, int node);
int ryaml_has_children(const ryaml_t *y, int node);
int ryaml_has_val(const ryaml_t *y, int node);
int ryaml_has_key(const ryaml_t *y, int node);
/**
* ryaml_key:
* @y : tree
* @node : node to read
* @len : filled with the key length
*
* The bytes are not NUL-terminated: they are a subrange of the document
* buffer, or of the tree's arena for a key that needed unescaping.
*
* Returns: the key bytes, or NULL when the node has no key.
*/
const char *ryaml_key(const ryaml_t *y, int node, size_t *len);
/**
* ryaml_val:
* @y : tree
* @node : node to read
* @len : filled with the value length
*
* As ryaml_key(). A mapping or sequence has no value of its own.
*
* Returns: the value bytes, or NULL when the node has no value.
*/
const char *ryaml_val(const ryaml_t *y, int node, size_t *len);
/**
* ryaml_val_int:
* @y : tree
* @node : node to read
* @out : filled with the parsed value on success
*
* Reads a decimal or 0x-prefixed integer, with an optional sign. The
* whole value has to be consumed, so a trailing unit or stray character
* is a failure rather than a truncated number.
*
* Returns: 1 on success, 0 if the node has no value or it does not read
* as an integer, in which case @out is untouched.
*/
int ryaml_val_int(const ryaml_t *y, int node, int *out);
/**
* ryaml_val_uint:
* @y : tree
* @node : node to read
* @out : filled with the parsed value on success
*
* As ryaml_val_int() without a sign.
*
* Returns: 1 on success, 0 otherwise.
*/
int ryaml_val_uint(const ryaml_t *y, int node, uint32_t *out);
/**
* ryaml_val_bool:
* @y : tree
* @node : node to read
* @out : filled with 0 or 1 on success
*
* Accepts the core schema spellings: true/false, yes/no, on/off, in any
* case.
*
* Returns: 1 on success, 0 otherwise.
*/
int ryaml_val_bool(const ryaml_t *y, int node, int *out);
/**
* ryaml_val_equals:
* @y : tree
* @node : node to read
* @str : NUL-terminated string to compare against
*
* Returns: 1 when the node's value is exactly @str, 0 otherwise.
*/
int ryaml_val_equals(const ryaml_t *y, int node, const char *str);
RETRO_END_DECLS
#endif
-1
View File
@@ -872,7 +872,6 @@ endif()
target_link_libraries(PCSX2_FLAGS INTERFACE
common
ryml
${LIBC_LIBRARIES}
)
+128 -91
View File
@@ -18,8 +18,8 @@
#include <optional>
#include "../common/Threading.h"
#include "../3rdparty/rapidyaml/rapidyaml/src/ryml_std.hpp"
#include "../3rdparty/rapidyaml/rapidyaml/src/ryml.hpp"
#include <formats/ryaml.h>
#include "../common/Console.h"
#include "../common/FileSystem.h"
@@ -40,7 +40,7 @@ namespace GameDatabaseSchema
namespace GameDatabase
{
static void parseAndInsert(const char *serial, const c4::yml::NodeRef& node);
static void parseAndInsert(const char *serial, const ryaml_t *yaml, int node);
static void initDatabase();
} // namespace GameDatabase
@@ -86,90 +86,117 @@ const std::string* GameDatabaseSchema::GameEntry::findPatch(u32 crc) const
return nullptr;
}
void GameDatabase::parseAndInsert(const char *serial, const c4::yml::NodeRef& node)
/* Small adapters over the C reader, so the extraction sites below read
* the way they did when this used a C++ YAML library. A node handle of
* RYAML_NONE is inert, so a lookup that missed can be passed straight
* on without a test at each step. */
static std::string yamlString(const ryaml_t* yaml, int node)
{
size_t len = 0;
const char* str = ryaml_val(yaml, node, &len);
return str ? std::string(str, len) : std::string();
}
static std::string_view yamlKeyView(const ryaml_t* yaml, int node)
{
size_t len = 0;
const char* str = ryaml_key(yaml, node, &len);
return str ? std::string_view(str, len) : std::string_view();
}
static std::string_view yamlValView(const ryaml_t* yaml, int node)
{
size_t len = 0;
const char* str = ryaml_val(yaml, node, &len);
return str ? std::string_view(str, len) : std::string_view();
}
static int yamlInt(const ryaml_t* yaml, int node, int def)
{
int value = def;
ryaml_val_int(yaml, node, &value);
return value;
}
void GameDatabase::parseAndInsert(const char *serial, const ryaml_t *yaml, int node)
{
GameDatabaseSchema::GameEntry gameEntry;
if (node.has_child("name"))
node["name"] >> gameEntry.name;
if (node.has_child("region"))
node["region"] >> gameEntry.region;
if (node.has_child("roundModes"))
gameEntry.name = yamlString(yaml, ryaml_find_child(yaml, node, "name"));
gameEntry.region = yamlString(yaml, ryaml_find_child(yaml, node, "region"));
{
if (node["roundModes"].has_child("eeRoundMode"))
const int roundModes = ryaml_find_child(yaml, node, "roundModes");
{
int eeVal = -1;
node["roundModes"]["eeRoundMode"] >> eeVal;
const int eeVal = yamlInt(yaml,
ryaml_find_child(yaml, roundModes, "eeRoundMode"), -1);
if (eeVal >= 0 && eeVal < static_cast<int>(FPRoundMode::MaxCount))
gameEntry.eeRoundMode = static_cast<FPRoundMode>(eeVal);
}
if (node["roundModes"].has_child("eeDivRoundMode"))
{
int eeVal = -1;
node["roundModes"]["eeDivRoundMode"] >> eeVal;
const int eeVal = yamlInt(yaml,
ryaml_find_child(yaml, roundModes, "eeDivRoundMode"), -1);
if (eeVal >= 0 && eeVal < static_cast<int>(FPRoundMode::MaxCount))
gameEntry.eeDivRoundMode = static_cast<FPRoundMode>(eeVal);
}
if (node["roundModes"].has_child("vuRoundMode"))
{
int vuVal = -1;
node["roundModes"]["vuRoundMode"] >> vuVal;
const int vuVal = yamlInt(yaml,
ryaml_find_child(yaml, roundModes, "vuRoundMode"), -1);
if (vuVal >= 0 && vuVal < static_cast<int>(FPRoundMode::MaxCount))
{
gameEntry.vu0RoundMode = static_cast<FPRoundMode>(vuVal);
gameEntry.vu1RoundMode = static_cast<FPRoundMode>(vuVal);
}
}
if (node["roundModes"].has_child("vu0RoundMode"))
{
int vuVal = -1;
node["roundModes"]["vu0RoundMode"] >> vuVal;
const int vuVal = yamlInt(yaml,
ryaml_find_child(yaml, roundModes, "vu0RoundMode"), -1);
if (vuVal >= 0 && vuVal < static_cast<int>(FPRoundMode::MaxCount))
gameEntry.vu0RoundMode = static_cast<FPRoundMode>(vuVal);
}
if (node["roundModes"].has_child("vu1RoundMode"))
{
int vuVal = -1;
node["roundModes"]["vu1RoundMode"] >> vuVal;
const int vuVal = yamlInt(yaml,
ryaml_find_child(yaml, roundModes, "vu1RoundMode"), -1);
if (vuVal >= 0 && vuVal < static_cast<int>(FPRoundMode::MaxCount))
gameEntry.vu1RoundMode = static_cast<FPRoundMode>(vuVal);
}
}
if (node.has_child("clampModes"))
{
if (node["clampModes"].has_child("eeClampMode"))
const int clampModes = ryaml_find_child(yaml, node, "clampModes");
if (ryaml_has_child(yaml, clampModes, "eeClampMode"))
{
int eeVal = -1;
node["clampModes"]["eeClampMode"] >> eeVal;
const int eeVal = yamlInt(yaml,
ryaml_find_child(yaml, clampModes, "eeClampMode"), -1);
gameEntry.eeClampMode = static_cast<GameDatabaseSchema::ClampMode>(eeVal);
}
if (node["clampModes"].has_child("vuClampMode"))
if (ryaml_has_child(yaml, clampModes, "vuClampMode"))
{
int vuVal = -1;
node["clampModes"]["vuClampMode"] >> vuVal;
const int vuVal = yamlInt(yaml,
ryaml_find_child(yaml, clampModes, "vuClampMode"), -1);
gameEntry.vu0ClampMode = static_cast<GameDatabaseSchema::ClampMode>(vuVal);
gameEntry.vu1ClampMode = static_cast<GameDatabaseSchema::ClampMode>(vuVal);
}
if (node["clampModes"].has_child("vu0ClampMode"))
if (ryaml_has_child(yaml, clampModes, "vu0ClampMode"))
{
int vuVal = -1;
node["clampModes"]["vu0ClampMode"] >> vuVal;
const int vuVal = yamlInt(yaml,
ryaml_find_child(yaml, clampModes, "vu0ClampMode"), -1);
gameEntry.vu0ClampMode = static_cast<GameDatabaseSchema::ClampMode>(vuVal);
}
if (node["clampModes"].has_child("vu1ClampMode"))
if (ryaml_has_child(yaml, clampModes, "vu1ClampMode"))
{
int vuVal = -1;
node["clampModes"]["vu1ClampMode"] >> vuVal;
const int vuVal = yamlInt(yaml,
ryaml_find_child(yaml, clampModes, "vu1ClampMode"), -1);
gameEntry.vu1ClampMode = static_cast<GameDatabaseSchema::ClampMode>(vuVal);
}
}
// Validate game fixes, invalid ones will be dropped!
if (node.has_child("gameFixes") && node["gameFixes"].has_children())
{
for (const auto& n : node["gameFixes"].children())
const int gameFixes = ryaml_find_child(yaml, node, "gameFixes");
for (int n = ryaml_first_child(yaml, gameFixes); n >= 0;
n = ryaml_next_sibling(yaml, n))
{
bool fixValidated = false;
auto fix = std::string(n.val().str, n.val().len);
std::string fix(yamlValView(yaml, n));
// Enum values don't end with Hack, but gamedb does, so remove it before comparing.
if (StringUtil::EndsWith(fix, "Hack"))
@@ -192,12 +219,13 @@ void GameDatabase::parseAndInsert(const char *serial, const c4::yml::NodeRef& no
}
}
if (node.has_child("speedHacks") && node["speedHacks"].has_children())
{
for (const auto& n : node["speedHacks"].children())
const int speedHacks = ryaml_find_child(yaml, node, "speedHacks");
for (int n = ryaml_first_child(yaml, speedHacks); n >= 0;
n = ryaml_next_sibling(yaml, n))
{
const std::string_view id_view = std::string_view(n.key().str, n.key().len);
const std::string_view value_view = std::string_view(n.val().str, n.val().len);
const std::string_view id_view = yamlKeyView(yaml, n);
const std::string_view value_view = yamlValView(yaml, n);
const std::optional<SpeedHack> id = Pcsx2Config::SpeedhackOptions::ParseSpeedHackName(id_view);
const std::optional<int> value = StringUtil::FromChars<int>(value_view);
@@ -210,18 +238,19 @@ void GameDatabase::parseAndInsert(const char *serial, const c4::yml::NodeRef& no
}
}
if (node.has_child("gsHWFixes"))
{
for (const auto& n : node["gsHWFixes"].children())
const int gsHWFixes = ryaml_find_child(yaml, node, "gsHWFixes");
for (int n = ryaml_first_child(yaml, gsHWFixes); n >= 0;
n = ryaml_next_sibling(yaml, n))
{
const std::string_view id_name(n.key().data(), n.key().size());
const std::string_view id_name = yamlKeyView(yaml, n);
std::optional<GameDatabaseSchema::GSHWFixId> id = GameDatabaseSchema::parseHWFixName(id_name);
std::optional<s32> value;
if (id.has_value() && (id.value() == GameDatabaseSchema::GSHWFixId::GetSkipCount ||
id.value() == GameDatabaseSchema::GSHWFixId::BeforeDraw ||
id.value() == GameDatabaseSchema::GSHWFixId::MoveHandler))
{
const std::string_view str_value(n.has_val() ? std::string_view(n.val().data(), n.val().size()) : std::string_view());
const std::string_view str_value = yamlValView(yaml, n);
if (id.value() == GameDatabaseSchema::GSHWFixId::GetSkipCount)
value = GSLookupGetSkipCountFunctionId(str_value);
else if (id.value() == GameDatabaseSchema::GSHWFixId::BeforeDraw)
@@ -236,7 +265,9 @@ void GameDatabase::parseAndInsert(const char *serial, const c4::yml::NodeRef& no
}
}
else
value = n.has_val() ? StringUtil::FromChars<s32>(std::string_view(n.val().data(), n.val().size())) : 1;
value = ryaml_has_val(yaml, n)
? StringUtil::FromChars<s32>(yamlValView(yaml, n))
: std::optional<s32>(1);
if (!id.has_value() || !value.has_value())
{
@@ -250,22 +281,21 @@ void GameDatabase::parseAndInsert(const char *serial, const c4::yml::NodeRef& no
// Memory Card Filters - Store as a vector to allow flexibility in the future
// - currently they are used as a '\n' delimited string in the app
if (node.has_child("memcardFilters") && node["memcardFilters"].has_children())
{
for (const auto& n : node["memcardFilters"].children())
{
auto memcardFilter = std::string(n.val().str, n.val().len);
gameEntry.memcardFilters.emplace_back(std::move(memcardFilter));
}
const int memcardFilters = ryaml_find_child(yaml, node, "memcardFilters");
for (int n = ryaml_first_child(yaml, memcardFilters); n >= 0;
n = ryaml_next_sibling(yaml, n))
gameEntry.memcardFilters.emplace_back(yamlValView(yaml, n));
}
// Game Patches
if (node.has_child("patches") && node["patches"].has_children())
{
for (const auto& n : node["patches"].children())
const int patches = ryaml_find_child(yaml, node, "patches");
for (int n = ryaml_first_child(yaml, patches); n >= 0;
n = ryaml_next_sibling(yaml, n))
{
// use a crc of 0 for default patches
const std::string_view crc_str(n.key().str, n.key().len);
const std::string_view crc_str = yamlKeyView(yaml, n);
const std::optional<u32> crc = ((crc_str.length() == 7) && (Strncasecmp(crc_str.data(), "default", 7) == 0)) ? std::optional<u32>(0) : StringUtil::FromChars<u32>(crc_str, 16);
if (!crc.has_value())
{
@@ -278,34 +308,40 @@ void GameDatabase::parseAndInsert(const char *serial, const c4::yml::NodeRef& no
continue;
}
std::string patch;
if (n.has_child("content"))
n["content"] >> patch;
std::string patch =
yamlString(yaml, ryaml_find_child(yaml, n, "content"));
gameEntry.patches.emplace(crc.value(), std::move(patch));
}
}
if (node.has_child("dynaPatches") && node["dynaPatches"].has_children())
{
for (const auto& n : node["dynaPatches"].children())
const int dynaPatches = ryaml_find_child(yaml, node, "dynaPatches");
for (int n = ryaml_first_child(yaml, dynaPatches); n >= 0;
n = ryaml_next_sibling(yaml, n))
{
DynamicPatch patch;
const int pattern = ryaml_find_child(yaml, n, "pattern");
if (n.has_child("pattern") && n["pattern"].has_children())
if (ryaml_has_children(yaml, pattern))
{
for (const auto& db_pattern : n["pattern"].children())
const int replacement = ryaml_find_child(yaml, n, "replacement");
int e;
for (e = ryaml_first_child(yaml, pattern); e >= 0;
e = ryaml_next_sibling(yaml, e))
{
DynamicPatchEntry entry;
db_pattern["offset"] >> entry.offset;
db_pattern["value"] >> entry.value;
DynamicPatchEntry entry = {};
ryaml_val_uint(yaml, ryaml_find_child(yaml, e, "offset"), &entry.offset);
ryaml_val_uint(yaml, ryaml_find_child(yaml, e, "value"), &entry.value);
patch.pattern.push_back(entry);
}
for (const auto& db_replacement : n["replacement"].children())
for (e = ryaml_first_child(yaml, replacement); e >= 0;
e = ryaml_next_sibling(yaml, e))
{
DynamicPatchEntry entry;
db_replacement["offset"] >> entry.offset;
db_replacement["value"] >> entry.value;
DynamicPatchEntry entry = {};
ryaml_val_uint(yaml, ryaml_find_child(yaml, e, "offset"), &entry.offset);
ryaml_val_uint(yaml, ryaml_find_child(yaml, e, "value"), &entry.value);
patch.replacement.push_back(entry);
}
@@ -874,17 +910,6 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
void GameDatabase::initDatabase()
{
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
rymlCallbacks.m_error_parse = [](c4::csubstr msg, ryml::ErrorDataParse const& errdata, void*) {
Console.Error("[YAML] Parsing error at {%zu}:{%zu} (bufpos={%zu}): {%.*s}",
errdata.ymlloc.line, errdata.ymlloc.col, errdata.ymlloc.offset,
(int)msg.len, msg.str);
};
ryml::set_callbacks(rymlCallbacks);
c4::set_error_callback([](const char* msg, size_t msg_size) {
Console.Error("[YAML] Internal Parsing error: {%s}",
msg);
});
auto buf = Host::ReadResourceFileToString(GAMEDB_YAML_FILE_NAME);
if (!buf.has_value())
{
@@ -892,12 +917,24 @@ void GameDatabase::initDatabase()
return;
}
ryml::Tree tree = ryml::parse_in_arena(c4::to_csubstr(buf.value()));
ryml::NodeRef root = tree.rootref();
for (const auto& n : root.children())
/* The reader borrows these bytes rather than copying them, so the
* buffer has to outlive the tree; it does, both are scoped here. */
size_t err_line = 0;
size_t err_col = 0;
ryaml_t* yaml = ryaml_parse_ex(buf.value().data(), buf.value().size(),
&err_line, &err_col);
if (!yaml)
{
auto serial = StringUtil::toLower(std::string(n.key().str, n.key().len));
Console.Error("[GameDB] Parsing error at {%zu}:{%zu}", err_line, err_col);
return;
}
const int root = ryaml_root(yaml);
for (int n = ryaml_first_child(yaml, root); n >= 0;
n = ryaml_next_sibling(yaml, n))
{
auto serial = StringUtil::toLower(std::string(yamlKeyView(yaml, n)));
// Serials and CRCs must be inserted as lower-case, as that is how they are retrieved
// this is because the application may pass a lowercase CRC or serial along
@@ -908,11 +945,11 @@ void GameDatabase::initDatabase()
Console.Error("[GameDB] Duplicate serial '{%s}' found in GameDB. Skipping, Serials are case-insensitive!", serial.c_str());
continue;
}
if (n.is_map())
parseAndInsert(serial.c_str(), n);
if (ryaml_is_map(yaml, n))
parseAndInsert(serial.c_str(), yaml, n);
}
ryml::reset_callbacks();
ryaml_free(yaml);
}
void GameDatabase::ensureLoaded()