diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index eb30e9c083..2cf0d6fa46 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -17,6 +17,7 @@ set(SRCS
NetPlayServer.cpp
PatchEngine.cpp
State.cpp
+ TitleDatabase.cpp
WiiRoot.cpp
Boot/Boot_BS2Emu.cpp
Boot/Boot.cpp
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index 87a03f73a3..0134b33d45 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -285,6 +285,7 @@
+
@@ -512,6 +513,7 @@
+
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index 7967b61478..14da13445a 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -168,6 +168,7 @@
+
ActionReplay
@@ -871,6 +872,7 @@
+
ActionReplay
diff --git a/Source/Core/Core/TitleDatabase.cpp b/Source/Core/Core/TitleDatabase.cpp
new file mode 100644
index 0000000000..41d576f1fe
--- /dev/null
+++ b/Source/Core/Core/TitleDatabase.cpp
@@ -0,0 +1,151 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+
+#include "Core/TitleDatabase.h"
+
+#include "Common/FileUtil.h"
+#include "Common/MsgHandler.h"
+#include "Common/StringUtil.h"
+#include "Core/ConfigManager.h"
+#include "DiscIO/Enums.h"
+
+namespace Core
+{
+static std::string GetLanguageCode(DiscIO::Language language)
+{
+ switch (language)
+ {
+ case DiscIO::Language::LANGUAGE_JAPANESE:
+ return "ja";
+ case DiscIO::Language::LANGUAGE_ENGLISH:
+ return "en";
+ case DiscIO::Language::LANGUAGE_GERMAN:
+ return "de";
+ case DiscIO::Language::LANGUAGE_FRENCH:
+ return "fr";
+ case DiscIO::Language::LANGUAGE_SPANISH:
+ return "es";
+ case DiscIO::Language::LANGUAGE_ITALIAN:
+ return "it";
+ case DiscIO::Language::LANGUAGE_DUTCH:
+ return "nl";
+ case DiscIO::Language::LANGUAGE_SIMPLIFIED_CHINESE:
+ return "zh_CN";
+ case DiscIO::Language::LANGUAGE_TRADITIONAL_CHINESE:
+ return "zh_TW";
+ case DiscIO::Language::LANGUAGE_KOREAN:
+ return "ko";
+ default:
+ return "en";
+ }
+}
+
+using Map = std::unordered_map;
+
+static bool LoadMap(const std::string& file_path, Map& map,
+ std::function predicate)
+{
+ std::ifstream txt;
+ OpenFStream(txt, file_path, std::ios::in);
+
+ if (!txt.is_open())
+ return false;
+
+ std::string line;
+ while (std::getline(txt, line))
+ {
+ if (line.empty())
+ continue;
+
+ const size_t equals_index = line.find('=');
+ if (equals_index != std::string::npos)
+ {
+ const std::string game_id = StripSpaces(line.substr(0, equals_index));
+ if (predicate(game_id))
+ map[game_id] = StripSpaces(line.substr(equals_index + 1));
+ }
+ }
+ return true;
+}
+
+// This should only be used with the standard game ID format (used by WiiTDBs), not Dolphin's.
+// The main difference is that Dolphin uses 6 characters for non-disc titles (instead of 4).
+static bool IsGCTitle(const std::string& game_id)
+{
+ const char system_id = game_id[0];
+ return game_id.length() == 6 &&
+ (system_id == 'G' || system_id == 'D' || system_id == 'U' || system_id == 'P');
+}
+
+static bool IsWiiTitle(const std::string& game_id)
+{
+ // Assume that any non-GameCube title is a Wii title.
+ return !IsGCTitle(game_id);
+}
+
+static bool LoadMap(const std::string& file_path, Map& gc_map, Map& wii_map)
+{
+ Map map;
+ if (!LoadMap(file_path, map, [](const auto& game_id) { return true; }))
+ return false;
+
+ for (auto& entry : map)
+ {
+ auto& destination_map = IsGCTitle(entry.first) ? gc_map : wii_map;
+ destination_map.emplace(std::move(entry));
+ }
+ return true;
+}
+
+TitleDatabase::TitleDatabase()
+{
+ // Titles that cannot be part of the Wii TDB,
+ // but common enough to justify having entries for them.
+
+ // i18n: "Wii Menu" (or System Menu) refers to the Wii's main menu,
+ // which is (usually) the first thing users see when a Wii console starts.
+ m_wii_title_map.emplace("0000000100000002", GetStringT("Wii Menu"));
+ for (const auto& id : {"HAXX", "JODI", "00010001af1bf516", "LULZ", "OHBC"})
+ m_wii_title_map.emplace(id, "The Homebrew Channel");
+
+ // Load the English database as the base database.
+ LoadMap(File::GetSysDirectory() + "wiitdb-en.txt", m_gc_title_map, m_wii_title_map);
+
+ // Load the database in the console language.
+ const std::string gc_code = GetLanguageCode(SConfig::GetInstance().GetCurrentLanguage(false));
+ const std::string wii_code = GetLanguageCode(SConfig::GetInstance().GetCurrentLanguage(true));
+ if (gc_code != "en")
+ LoadMap(File::GetSysDirectory() + "wiitdb-" + gc_code + ".txt", m_gc_title_map, IsGCTitle);
+ if (wii_code != "en")
+ LoadMap(File::GetSysDirectory() + "wiitdb-" + wii_code + ".txt", m_wii_title_map, IsWiiTitle);
+
+ // Load the user databases.
+ const std::string& load_directory = File::GetUserPath(D_LOAD_IDX);
+ if (!LoadMap(load_directory + "wiitdb.txt", m_gc_title_map, m_wii_title_map))
+ LoadMap(load_directory + "titles.txt", m_gc_title_map, m_wii_title_map);
+}
+
+TitleDatabase::~TitleDatabase() = default;
+
+std::string TitleDatabase::GetTitleName(const std::string& game_id, TitleType type) const
+{
+ const auto& map = IsWiiTitle(game_id) ? m_wii_title_map : m_gc_title_map;
+ const std::string key =
+ type == TitleType::Channel && game_id.length() == 6 ? game_id.substr(0, 4) : game_id;
+ const auto iterator = map.find(key);
+ return iterator != map.end() ? iterator->second : "";
+}
+
+std::string TitleDatabase::Describe(const std::string& game_id, TitleType type) const
+{
+ const std::string title_name = GetTitleName(game_id, type);
+ if (title_name.empty())
+ return game_id;
+ return StringFromFormat("%s (%s)", title_name.c_str(), game_id.c_str());
+}
+} // namespace Core
diff --git a/Source/Core/Core/TitleDatabase.h b/Source/Core/Core/TitleDatabase.h
new file mode 100644
index 0000000000..23871bdf6a
--- /dev/null
+++ b/Source/Core/Core/TitleDatabase.h
@@ -0,0 +1,38 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+#include "Common/CommonTypes.h"
+
+namespace Core
+{
+// Reader for title database files.
+class TitleDatabase final
+{
+public:
+ TitleDatabase();
+ ~TitleDatabase();
+
+ enum class TitleType
+ {
+ Channel,
+ Other,
+ };
+
+ // Get a user friendly title name for a game ID.
+ // This falls back to returning an empty string if none could be found.
+ std::string GetTitleName(const std::string& game_id, TitleType = TitleType::Other) const;
+
+ // Get a description for a game ID (title name if available + game ID).
+ std::string Describe(const std::string& game_id, TitleType = TitleType::Other) const;
+
+private:
+ std::unordered_map m_wii_title_map;
+ std::unordered_map m_gc_title_map;
+};
+} // namespace Core