mirror of https://github.com/PCSX2/pcsx2
GameDB: Fix infinite loops caused by UB when YAML parsing fails
This commit is contained in:
parent
07bc2fa452
commit
0ce312c1c3
|
|
@ -7,7 +7,6 @@ endif(NOT TOP_CMAKE_WAS_SOURCED)
|
||||||
|
|
||||||
add_library(common)
|
add_library(common)
|
||||||
|
|
||||||
# x86emitter sources
|
|
||||||
target_sources(common PRIVATE
|
target_sources(common PRIVATE
|
||||||
AlignedMalloc.cpp
|
AlignedMalloc.cpp
|
||||||
Assertions.cpp
|
Assertions.cpp
|
||||||
|
|
@ -34,9 +33,9 @@ target_sources(common PRIVATE
|
||||||
Timer.cpp
|
Timer.cpp
|
||||||
WAVWriter.cpp
|
WAVWriter.cpp
|
||||||
WindowInfo.cpp
|
WindowInfo.cpp
|
||||||
|
YAML.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# x86emitter headers
|
|
||||||
target_sources(common PRIVATE
|
target_sources(common PRIVATE
|
||||||
AlignedMalloc.h
|
AlignedMalloc.h
|
||||||
Assertions.h
|
Assertions.h
|
||||||
|
|
@ -81,6 +80,7 @@ target_sources(common PRIVATE
|
||||||
WAVWriter.h
|
WAVWriter.h
|
||||||
WindowInfo.h
|
WindowInfo.h
|
||||||
WrappedMemCopy.h
|
WrappedMemCopy.h
|
||||||
|
YAML.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if(_M_X86)
|
if(_M_X86)
|
||||||
|
|
@ -208,6 +208,7 @@ target_link_libraries(common PRIVATE
|
||||||
target_link_libraries(common PUBLIC
|
target_link_libraries(common PUBLIC
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
fast_float
|
fast_float
|
||||||
|
rapidyaml::rapidyaml
|
||||||
)
|
)
|
||||||
|
|
||||||
fixup_file_properties(common)
|
fixup_file_properties(common)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||||
|
// SPDX-License-Identifier: GPL-3.0+
|
||||||
|
|
||||||
|
#include "YAML.h"
|
||||||
|
|
||||||
|
#include <csetjmp>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
struct RapidYAMLContext
|
||||||
|
{
|
||||||
|
std::jmp_buf env;
|
||||||
|
Error* error = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
|
||||||
|
{
|
||||||
|
RapidYAMLContext context;
|
||||||
|
context.error = error;
|
||||||
|
|
||||||
|
ryml::Callbacks callbacks;
|
||||||
|
callbacks.m_user_data = static_cast<void*>(&context);
|
||||||
|
callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location location, void* user_data) {
|
||||||
|
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||||
|
|
||||||
|
Error::SetString(context->error, std::string(msg, msg_len));
|
||||||
|
std::longjmp(context->env, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
ryml::EventHandlerTree event_handler(callbacks);
|
||||||
|
ryml::Parser parser(&event_handler);
|
||||||
|
|
||||||
|
ryml::Tree tree;
|
||||||
|
|
||||||
|
// The only options RapidYAML provides for recovering from errors are
|
||||||
|
// throwing an exception or using setjmp/longjmp. Since we have exceptions
|
||||||
|
// disabled we have to use the latter option.
|
||||||
|
if (setjmp(context.env))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||||
|
// SPDX-License-Identifier: GPL-3.0+
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Error.h"
|
||||||
|
|
||||||
|
#include "ryml_std.hpp"
|
||||||
|
#include "ryml.hpp"
|
||||||
|
#include "ryml.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
/// Parse a YAML file with RapidYAML, and use setjmp/longjmp to recover from
|
||||||
|
/// parsing errors (as is recommended by the documentation for cases where
|
||||||
|
/// exceptions are disabled). The file_name parameter is only used for error
|
||||||
|
/// messages, which are returned via the error parameter.
|
||||||
|
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error);
|
||||||
|
|
@ -36,10 +36,12 @@
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fast_float\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fast_float\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\include</AdditionalIncludeDirectories>
|
||||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||||
<ForcedIncludeFiles>PrecompiledHeader.h</ForcedIncludeFiles>
|
<ForcedIncludeFiles>PrecompiledHeader.h</ForcedIncludeFiles>
|
||||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||||
<ObjectFileName>$(IntDir)%(RelativeDir)</ObjectFileName>
|
<ObjectFileName>$(IntDir)%(RelativeDir)</ObjectFileName>
|
||||||
|
<PreprocessorDefinitions>C4_NO_DEBUG_BREAK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -71,6 +73,7 @@
|
||||||
<ClCompile Include="Timer.cpp" />
|
<ClCompile Include="Timer.cpp" />
|
||||||
<ClCompile Include="WAVWriter.cpp" />
|
<ClCompile Include="WAVWriter.cpp" />
|
||||||
<ClCompile Include="WindowInfo.cpp" />
|
<ClCompile Include="WindowInfo.cpp" />
|
||||||
|
<ClCompile Include="YAML.cpp" />
|
||||||
<ClCompile Include="Perf.cpp" />
|
<ClCompile Include="Perf.cpp" />
|
||||||
<ClCompile Include="PrecompiledHeader.cpp">
|
<ClCompile Include="PrecompiledHeader.cpp">
|
||||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||||
|
|
@ -157,6 +160,7 @@
|
||||||
<ClInclude Include="Timer.h" />
|
<ClInclude Include="Timer.h" />
|
||||||
<ClInclude Include="WAVWriter.h" />
|
<ClInclude Include="WAVWriter.h" />
|
||||||
<ClInclude Include="WindowInfo.h" />
|
<ClInclude Include="WindowInfo.h" />
|
||||||
|
<ClInclude Include="YAML.h" />
|
||||||
<ClInclude Include="Threading.h" />
|
<ClInclude Include="Threading.h" />
|
||||||
<ClInclude Include="emitter\implement\avx.h" />
|
<ClInclude Include="emitter\implement\avx.h" />
|
||||||
<ClInclude Include="emitter\implement\bmi.h" />
|
<ClInclude Include="emitter\implement\bmi.h" />
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@
|
||||||
<ClCompile Include="SmallString.cpp">
|
<ClCompile Include="SmallString.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="YAML.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="AlignedMalloc.h">
|
<ClInclude Include="AlignedMalloc.h">
|
||||||
|
|
@ -335,6 +338,9 @@
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="SingleRegisterTypes.h" />
|
<ClInclude Include="SingleRegisterTypes.h" />
|
||||||
<ClInclude Include="FPControl.h" />
|
<ClInclude Include="FPControl.h" />
|
||||||
|
<ClInclude Include="YAML.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="Source Files">
|
<Filter Include="Source Files">
|
||||||
|
|
@ -349,4 +355,4 @@
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</MASM>
|
</MASM>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1140,7 +1140,6 @@ target_link_libraries(PCSX2_FLAGS INTERFACE
|
||||||
common
|
common
|
||||||
imgui
|
imgui
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
rapidyaml::rapidyaml
|
|
||||||
libchdr
|
libchdr
|
||||||
libzip::zip
|
libzip::zip
|
||||||
cpuinfo
|
cpuinfo
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "common/Path.h"
|
#include "common/Path.h"
|
||||||
#include "common/StringUtil.h"
|
#include "common/StringUtil.h"
|
||||||
#include "common/Timer.h"
|
#include "common/Timer.h"
|
||||||
|
#include "common/YAML.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "ryml_std.hpp"
|
#include "ryml_std.hpp"
|
||||||
|
|
@ -357,8 +358,7 @@ static const char* s_round_modes[static_cast<u32>(FPRoundMode::MaxCount)] = {
|
||||||
"Nearest",
|
"Nearest",
|
||||||
"NegativeInfinity",
|
"NegativeInfinity",
|
||||||
"PositiveInfinity",
|
"PositiveInfinity",
|
||||||
"Chop"
|
"Chop"};
|
||||||
};
|
|
||||||
|
|
||||||
static const char* s_gs_hw_fix_names[] = {
|
static const char* s_gs_hw_fix_names[] = {
|
||||||
"autoFlush",
|
"autoFlush",
|
||||||
|
|
@ -930,7 +930,7 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
|
||||||
Host::AddKeyedOSDMessage("HWFixesWarning",
|
Host::AddKeyedOSDMessage("HWFixesWarning",
|
||||||
fmt::format(ICON_FA_WAND_MAGIC_SPARKLES " {}\n{}",
|
fmt::format(ICON_FA_WAND_MAGIC_SPARKLES " {}\n{}",
|
||||||
TRANSLATE_SV("GameDatabase", "Manual GS hardware renderer fixes are enabled, automatic fixes were not applied:"),
|
TRANSLATE_SV("GameDatabase", "Manual GS hardware renderer fixes are enabled, automatic fixes were not applied:"),
|
||||||
disabled_fixes),
|
disabled_fixes),
|
||||||
Host::OSD_ERROR_DURATION);
|
Host::OSD_ERROR_DURATION);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -941,25 +941,28 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
|
||||||
|
|
||||||
void GameDatabase::initDatabase()
|
void GameDatabase::initDatabase()
|
||||||
{
|
{
|
||||||
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
|
const std::string path(Path::Combine(EmuFolders::Resources, GAMEDB_YAML_FILE_NAME));
|
||||||
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void* userdata) {
|
const std::string name(GAMEDB_YAML_FILE_NAME);
|
||||||
Console.Error(fmt::format("[GameDB YAML] Parsing error at {}:{} (bufpos={}): {}",
|
|
||||||
loc.line, loc.col, loc.offset, std::string_view(msg, msg_len)));
|
|
||||||
};
|
|
||||||
ryml::set_callbacks(rymlCallbacks);
|
|
||||||
ryml::set_error_callback([](const char* msg, size_t msg_size) {
|
|
||||||
Console.Error(fmt::format("[GameDB YAML] Internal Parsing error: {}", std::string_view(msg, msg_size)));
|
|
||||||
});
|
|
||||||
|
|
||||||
auto buf = FileSystem::ReadFileToString(Path::Combine(EmuFolders::Resources, GAMEDB_YAML_FILE_NAME).c_str());
|
const std::optional<std::string> buffer = FileSystem::ReadFileToString(path.c_str());
|
||||||
if (!buf.has_value())
|
if (!buffer.has_value())
|
||||||
{
|
{
|
||||||
Console.Error("GameDB: Unable to open GameDB file, file does not exist.");
|
Console.Error("GameDB: Unable to open GameDB file, file does not exist.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(buf.value()));
|
const ryml::csubstr yaml = ryml::to_csubstr(*buffer);
|
||||||
ryml::NodeRef root = tree.rootref();
|
|
||||||
|
Error error;
|
||||||
|
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
|
||||||
|
if (!tree.has_value())
|
||||||
|
{
|
||||||
|
Console.ErrorFmt("GameDB: Failed to parse game database file {}:", path);
|
||||||
|
Console.Error(error.GetDescription());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ryml::NodeRef root = tree->rootref();
|
||||||
|
|
||||||
for (const ryml::NodeRef& n : root.children())
|
for (const ryml::NodeRef& n : root.children())
|
||||||
{
|
{
|
||||||
|
|
@ -971,7 +974,7 @@ void GameDatabase::initDatabase()
|
||||||
// However, YAML's keys are as expected case-sensitive, so we have to explicitly do our own duplicate checking
|
// However, YAML's keys are as expected case-sensitive, so we have to explicitly do our own duplicate checking
|
||||||
if (s_game_db.count(serial) == 1)
|
if (s_game_db.count(serial) == 1)
|
||||||
{
|
{
|
||||||
Console.Error(fmt::format("GameDB: Duplicate serial '{}' found in GameDB. Skipping, Serials are case-insensitive!", serial));
|
Console.ErrorFmt("GameDB: Duplicate serial '{}' found in GameDB. Skipping, Serials are case-insensitive!", serial);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -980,8 +983,6 @@ void GameDatabase::initDatabase()
|
||||||
parseAndInsert(serial, n);
|
parseAndInsert(serial, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ryml::reset_callbacks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameDatabase::ensureLoaded()
|
void GameDatabase::ensureLoaded()
|
||||||
|
|
@ -1069,7 +1070,7 @@ static bool parseHashDatabaseEntry(const ryml::NodeRef& node)
|
||||||
{
|
{
|
||||||
if (!n.is_map() || !n.has_child("size") || !n.has_child("md5"))
|
if (!n.is_map() || !n.has_child("size") || !n.has_child("md5"))
|
||||||
{
|
{
|
||||||
Console.Error(fmt::format("[HashDatabase] Incomplete hash definition in {}", entry.name));
|
Console.ErrorFmt("[HashDatabase] Incomplete hash definition in {}", entry.name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1080,12 +1081,12 @@ static bool parseHashDatabaseEntry(const ryml::NodeRef& node)
|
||||||
|
|
||||||
if (!th.parseHash(md5))
|
if (!th.parseHash(md5))
|
||||||
{
|
{
|
||||||
Console.Error(fmt::format("[HashDatabase] Failed to parse hash in {}: '{}'", entry.name, md5));
|
Console.ErrorFmt("[HashDatabase] Failed to parse hash in {}: '{}'", entry.name, md5);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.tracks.empty() && s_track_hash_to_entry_map.find(th) != s_track_hash_to_entry_map.end())
|
if (entry.tracks.empty() && s_track_hash_to_entry_map.find(th) != s_track_hash_to_entry_map.end())
|
||||||
Console.Warning(fmt::format("[HashDatabase] Duplicate first track hash in {}", entry.name));
|
Console.WarningFmt("[HashDatabase] Duplicate first track hash in {}", entry.name);
|
||||||
|
|
||||||
entry.tracks.push_back(th);
|
entry.tracks.push_back(th);
|
||||||
s_track_hash_to_entry_map.emplace(th, index);
|
s_track_hash_to_entry_map.emplace(th, index);
|
||||||
|
|
@ -1100,27 +1101,30 @@ bool GameDatabase::loadHashDatabase()
|
||||||
if (!s_hash_database.empty())
|
if (!s_hash_database.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
|
|
||||||
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void*) {
|
|
||||||
Console.Error(fmt::format(
|
|
||||||
"[HashDatabase YAML] Parsing error at {}:{} (bufpos={}): {}", loc.line, loc.col, loc.offset, msg));
|
|
||||||
};
|
|
||||||
ryml::set_callbacks(rymlCallbacks);
|
|
||||||
ryml::set_error_callback([](const char* msg, size_t msg_size) {
|
|
||||||
Console.Error(fmt::format("[HashDatabase YAML] Internal Parsing error: {}", std::string_view(msg, msg_size)));
|
|
||||||
});
|
|
||||||
|
|
||||||
Common::Timer load_timer;
|
Common::Timer load_timer;
|
||||||
|
|
||||||
auto buf = FileSystem::ReadFileToString(Path::Combine(EmuFolders::Resources, HASHDB_YAML_FILE_NAME).c_str());
|
const std::string path(Path::Combine(EmuFolders::Resources, HASHDB_YAML_FILE_NAME));
|
||||||
if (!buf.has_value())
|
const std::string name(HASHDB_YAML_FILE_NAME);
|
||||||
|
|
||||||
|
std::optional<std::string> buffer = FileSystem::ReadFileToString(path.c_str());
|
||||||
|
if (!buffer.has_value())
|
||||||
{
|
{
|
||||||
Console.Error("GameDB: Unable to open hash database file, file does not exist.");
|
Console.Error("[HashDatabase] Unable to open hash database file, file does not exist.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(buf.value()));
|
ryml::csubstr yaml = ryml::to_csubstr(*buffer);
|
||||||
ryml::NodeRef root = tree.rootref();
|
|
||||||
|
Error error;
|
||||||
|
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
|
||||||
|
if (!tree.has_value())
|
||||||
|
{
|
||||||
|
Console.ErrorFmt("[HashDatabase] Failed to parse hash database file {}:", path);
|
||||||
|
Console.Error(error.GetDescription());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ryml::NodeRef root = tree->rootref();
|
||||||
|
|
||||||
bool okay = true;
|
bool okay = true;
|
||||||
for (const ryml::NodeRef& n : root.children())
|
for (const ryml::NodeRef& n : root.children())
|
||||||
|
|
@ -1132,7 +1136,6 @@ bool GameDatabase::loadHashDatabase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ryml::reset_callbacks();
|
|
||||||
if (!okay)
|
if (!okay)
|
||||||
{
|
{
|
||||||
s_track_hash_to_entry_map.clear();
|
s_track_hash_to_entry_map.clear();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue