diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 9d013c292..639e729da 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -73,7 +73,7 @@ set(RECOMPILER_SRCS
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
-target_link_libraries(core PUBLIC Threads::Threads YBaseLib common imgui)
+target_link_libraries(core PUBLIC Threads::Threads YBaseLib common imgui tinyxml2)
target_link_libraries(core PRIVATE glad simpleini stb)
if(WIN32)
diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj
index 34e57cd3e..e9785b307 100644
--- a/src/core/core.vcxproj
+++ b/src/core/core.vcxproj
@@ -110,6 +110,9 @@
{ed601289-ac1a-46b8-a8ed-17db9eb73423}
+
+ {933118a9-68c5-47b4-b151-b03c93961623}
+
{b56ce698-7300-4fa5-9609-942f1d05c5a2}
@@ -262,10 +265,10 @@
Level4
Disabled
- WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -284,10 +287,10 @@
Level4
Disabled
- WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -306,10 +309,10 @@
Level4
Disabled
- WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
true
false
@@ -331,10 +334,10 @@
Level4
Disabled
- WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
true
false
@@ -357,8 +360,8 @@
MaxSpeed
true
- WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -380,8 +383,8 @@
MaxSpeed
true
- WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
@@ -404,8 +407,8 @@
MaxSpeed
true
- WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -427,8 +430,8 @@
MaxSpeed
true
- WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_RECOMPILER=1;TINYXML2_IMPORT;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp
index 560e6a773..005ce3306 100644
--- a/src/core/game_list.cpp
+++ b/src/core/game_list.cpp
@@ -6,6 +6,7 @@
#include "common/iso_reader.h"
#include
#include
+#include
#include
Log_SetChannel(GameList);
@@ -78,24 +79,37 @@ std::string GameList::GetGameCodeForImage(CDImage* cdi)
// cdrom:\SCES_123.45;1
std::string code = iter->second;
std::string::size_type pos = code.rfind('\\');
- if (pos == std::string::npos)
- return {};
- code.erase(0, pos + 1);
- pos = code.find(';');
- if (pos == std::string::npos)
- return {};
+ if (pos != std::string::npos)
+ {
+ code.erase(0, pos + 1);
+ }
+ else
+ {
+ // cdrom:SCES_123.45;1
+ pos = code.rfind(':');
+ if (pos != std::string::npos)
+ code.erase(0, pos + 1);
+ }
- code.erase(pos);
+ pos = code.find(';');
+ if (pos != std::string::npos)
+ code.erase(pos);
// SCES_123.45 -> SCES-12345
for (pos = 0; pos < code.size();)
{
- if (code[pos] == '_')
- code[pos++] = '-';
- else if (code[pos] == '.')
+ if (code[pos] == '.')
+ {
code.erase(pos, 1);
+ continue;
+ }
+
+ if (code[pos] == '_')
+ code[pos] = '-';
else
- pos++;
+ code[pos] = static_cast(std::toupper(code[pos]));
+
+ pos++;
}
return code;
@@ -137,11 +151,24 @@ bool GameList::GetGameListEntry(const char* path, GameListEntry* entry)
return false;
std::string game_code = GetGameCodeForImage(cdi.get());
+ cdi.reset();
entry->path = path;
- entry->title = game_code;
entry->code = game_code;
- entry->region = GetRegionForCode(game_code).value_or(ConsoleRegion::NTSC_U);
+
+ auto iter = m_database.find(game_code);
+ if (iter != m_database.end())
+ {
+ entry->title = iter->second.title;
+ entry->region = iter->second.region;
+ }
+ else
+ {
+ Log_WarningPrintf("'%s' not found in database", game_code.c_str());
+ entry->title = game_code;
+ entry->region = GetRegionForCode(game_code).value_or(ConsoleRegion::NTSC_U);
+ }
+
return true;
}
@@ -183,3 +210,105 @@ void GameList::ScanDirectory(const char* path, bool recursive)
}
}
}
+
+class RedumpDatVisitor final : public tinyxml2::XMLVisitor
+{
+public:
+ RedumpDatVisitor(GameList::DatabaseMap& database) : m_database(database) {}
+
+ static std::string FixupSerial(const std::string_view str)
+ {
+ std::string ret;
+ ret.reserve(str.length());
+ for (size_t i = 0; i < str.length(); i++)
+ {
+ if (str[i] == '.' || str[i] == '#')
+ continue;
+ else if (str[i] == ',')
+ break;
+ else if (str[i] == '_' || str[i] == ' ')
+ ret.push_back('-');
+ else
+ ret.push_back(static_cast(std::toupper(str[i])));
+ }
+
+ return ret;
+ }
+
+ bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) override
+ {
+ // recurse into gamelist
+ if (Y_stricmp(element.Name(), "datafile") == 0)
+ return true;
+
+ if (Y_stricmp(element.Name(), "game") != 0)
+ return false;
+
+ const char* name = element.Attribute("name");
+ if (!name)
+ return false;
+
+ const tinyxml2::XMLElement* serial_elem = element.FirstChildElement("serial");
+ if (!serial_elem)
+ return false;
+
+ const char* serial_text = serial_elem->GetText();
+ if (!serial_text)
+ return false;
+
+ // Handle entries like SCES-00984, SCES-00984#
+ const char* start = serial_text;
+ const char* end = std::strchr(start, ',');
+ for (;;)
+ {
+ std::string code = FixupSerial(end ? std::string_view(start, end - start) : std::string_view(start));
+ auto iter = m_database.find(code);
+ if (iter == m_database.end())
+ {
+ GameList::GameDatabaseEntry gde;
+ gde.code = std::move(code);
+ gde.region = GameList::GetRegionForCode(gde.code).value_or(ConsoleRegion::NTSC_U);
+ gde.title = name;
+ m_database.emplace(gde.code, std::move(gde));
+ }
+
+ if (!end)
+ break;
+
+ start = end + 1;
+ while (std::isspace(*start))
+ start++;
+
+ end = std::strchr(start, ',');
+ }
+
+ return false;
+ }
+
+private:
+ GameList::DatabaseMap& m_database;
+};
+
+bool GameList::ParseRedumpDatabase(const char* redump_dat_path)
+{
+ tinyxml2::XMLDocument doc;
+ tinyxml2::XMLError error = doc.LoadFile(redump_dat_path);
+ if (error != tinyxml2::XML_SUCCESS)
+ {
+ Log_ErrorPrintf("Failed to parse redump dat '%s': %s", redump_dat_path,
+ tinyxml2::XMLDocument::ErrorIDToName(error));
+ return false;
+ }
+
+ const tinyxml2::XMLElement* datafile_elem = doc.FirstChildElement("datafile");
+ if (!datafile_elem)
+ {
+ Log_ErrorPrintf("Failed to get datafile element in '%s'", redump_dat_path);
+ return false;
+ }
+
+ RedumpDatVisitor visitor(m_database);
+ datafile_elem->Accept(&visitor);
+ Log_InfoPrintf("Loaded %zu entries from Redump.org database '%s'", m_database.size(), redump_dat_path);
+ return true;
+}
diff --git a/src/core/game_list.h b/src/core/game_list.h
index 5957127b2..237f99f3e 100644
--- a/src/core/game_list.h
+++ b/src/core/game_list.h
@@ -10,16 +10,15 @@ class CDImage;
class GameList
{
public:
- GameList();
- ~GameList();
+ struct GameDatabaseEntry
+ {
+ std::string code;
+ std::string title;
+ ConsoleRegion region;
+ };
- static std::string GetGameCodeForImage(CDImage* cdi);
- static std::string GetGameCodeForPath(const char* image_path);
- static std::optional GetRegionForCode(std::string_view code);
+ using DatabaseMap = std::unordered_map;
- void AddDirectory(const char* path, bool recursive);
-
-private:
struct GameListEntry
{
std::string path;
@@ -28,9 +27,28 @@ private:
ConsoleRegion region;
};
+ using EntryList = std::vector;
+
+ GameList();
+ ~GameList();
+
+ static std::string GetGameCodeForImage(CDImage* cdi);
+ static std::string GetGameCodeForPath(const char* image_path);
+ static std::optional GetRegionForCode(std::string_view code);
+
+ const DatabaseMap& GetDatabase() const { return m_database; }
+ const EntryList& GetEntries() const { return m_entries; }
+
+ void AddDirectory(const char* path, bool recursive);
+
+ bool ParseRedumpDatabase(const char* redump_dat_path);
+
+private:
+
bool GetGameListEntry(const char* path, GameListEntry* entry);
void ScanDirectory(const char* path, bool recursive);
- std::vector m_entries;
+ DatabaseMap m_database;
+ EntryList m_entries;
};