Move GameList to FrontendCommon

Reduces libretro core dependencies further.
This commit is contained in:
Connor McLaughlin
2020-09-01 12:29:22 +10:00
parent 13e3095801
commit 6bbbb96d4b
25 changed files with 356 additions and 353 deletions

View File

@ -3,6 +3,10 @@ add_library(frontend-common
common_host_interface.h
controller_interface.cpp
controller_interface.h
game_list.cpp
game_list.h
game_settings.cpp
game_settings.h
icon.cpp
icon.h
imgui_styles.cpp
@ -17,7 +21,7 @@ add_library(frontend-common
vulkan_host_display.h
)
target_link_libraries(frontend-common PUBLIC core common imgui simpleini scmversion glad vulkan-loader)
target_link_libraries(frontend-common PUBLIC core common imgui simpleini tinyxml2 scmversion glad vulkan-loader)
if(WIN32)
target_sources(frontend-common PRIVATE

View File

@ -9,7 +9,6 @@
#include "core/cdrom.h"
#include "core/cpu_code_cache.h"
#include "core/dma.h"
#include "core/game_list.h"
#include "core/gpu.h"
#include "core/host_display.h"
#include "core/mdec.h"
@ -18,6 +17,7 @@
#include "core/spu.h"
#include "core/system.h"
#include "core/timers.h"
#include "game_list.h"
#include "imgui.h"
#include "ini_settings_interface.h"
#include "save_state_selector_ui.h"
@ -368,7 +368,7 @@ bool CommonHostInterface::ParseCommandLineParameters(int argc, char* argv[],
else
{
// find the game id, and get its save state path
std::string game_code = m_game_list->GetGameCodeForPath(boot_filename.c_str());
std::string game_code = System::GetGameCodeForPath(boot_filename.c_str());
if (game_code.empty())
{
Log_WarningPrintf("Could not identify game code for '%s', cannot load save state %d.", boot_filename.c_str(),
@ -2032,13 +2032,13 @@ void CommonHostInterface::GetGameInfo(const char* path, CDImage* image, std::str
else
{
if (image)
*code = GameList::GetGameCodeForImage(image);
*code = System::GetGameCodeForImage(image);
const GameListDatabaseEntry* db_entry = (!code->empty()) ? m_game_list->GetDatabaseEntryForCode(*code) : nullptr;
if (db_entry)
*title = db_entry->title;
else
*title = GameList::GetTitleForPath(path);
*title = System::GetTitleForPath(path);
}
}

View File

@ -53,6 +53,9 @@
<ProjectReference Include="..\..\dep\simpleini\simpleini.vcxproj">
<Project>{3773f4cc-614e-4028-8595-22e08ca649e3}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\tinyxml2\tinyxml2.vcxproj">
<Project>{933118a9-68c5-47b4-b151-b03c93961623}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\vulkan-loader\vulkan-loader.vcxproj">
<Project>{9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035}</Project>
</ProjectReference>
@ -67,6 +70,8 @@
<ClCompile Include="common_host_interface.cpp" />
<ClCompile Include="controller_interface.cpp" />
<ClCompile Include="d3d11_host_display.cpp" />
<ClCompile Include="game_list.cpp" />
<ClCompile Include="game_settings.cpp" />
<ClCompile Include="icon.cpp" />
<ClCompile Include="imgui_styles.cpp" />
<ClCompile Include="ini_settings_interface.cpp" />
@ -82,6 +87,8 @@
<ClInclude Include="common_host_interface.h" />
<ClInclude Include="controller_interface.h" />
<ClInclude Include="d3d11_host_display.h" />
<ClInclude Include="game_list.h" />
<ClInclude Include="game_settings.h" />
<ClInclude Include="icon.h" />
<ClInclude Include="imgui_styles.h" />
<ClInclude Include="ini_settings_interface.h" />
@ -241,7 +248,7 @@
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard>
<ConformanceMode>true</ConformanceMode>
@ -269,7 +276,7 @@
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<SupportJustMyCode>false</SupportJustMyCode>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@ -300,7 +307,7 @@
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard>
<ConformanceMode>true</ConformanceMode>
@ -328,7 +335,7 @@
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<SupportJustMyCode>false</SupportJustMyCode>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@ -360,7 +367,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard>
<WholeProgramOptimization>false</WholeProgramOptimization>
@ -390,7 +397,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<OmitFramePointers>true</OmitFramePointers>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -422,7 +429,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard>
<WholeProgramOptimization>false</WholeProgramOptimization>
@ -452,7 +459,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<OmitFramePointers>true</OmitFramePointers>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard>

View File

@ -14,6 +14,8 @@
<ClCompile Include="d3d11_host_display.cpp" />
<ClCompile Include="opengl_host_display.cpp" />
<ClCompile Include="xinput_controller_interface.cpp" />
<ClCompile Include="game_settings.cpp" />
<ClCompile Include="game_list.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="icon.h" />
@ -29,6 +31,8 @@
<ClInclude Include="d3d11_host_display.h" />
<ClInclude Include="opengl_host_display.h" />
<ClInclude Include="xinput_controller_interface.h" />
<ClInclude Include="game_list.h" />
<ClInclude Include="game_settings.h" />
</ItemGroup>
<ItemGroup>
<None Include="font_roboto_regular.inl" />

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,174 @@
#pragma once
#include "core/types.h"
#include "game_settings.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
class CDImage;
class ByteStream;
class ProgressCallback;
class SettingsInterface;
enum class GameListEntryType
{
Disc,
PSExe,
Playlist
};
enum class GameListCompatibilityRating
{
Unknown = 0,
DoesntBoot = 1,
CrashesInIntro = 2,
CrashesInGame = 3,
GraphicalAudioIssues = 4,
NoIssues = 5,
Count,
};
struct GameListDatabaseEntry
{
std::string code;
std::string title;
DiscRegion region;
};
struct GameListEntry
{
std::string path;
std::string code;
std::string title;
u64 total_size;
u64 last_modified_time;
DiscRegion region;
GameListEntryType type;
GameListCompatibilityRating compatibility_rating;
GameSettings::Entry settings;
};
struct GameListCompatibilityEntry
{
std::string code;
std::string title;
std::string version_tested;
std::string upscaling_issues;
std::string comments;
DiscRegion region;
GameListCompatibilityRating compatibility_rating;
};
class GameList
{
public:
using EntryList = std::vector<GameListEntry>;
GameList();
~GameList();
static const char* EntryTypeToString(GameListEntryType type);
static const char* EntryCompatibilityRatingToString(GameListCompatibilityRating rating);
/// Returns a string representation of a compatibility level.
static const char* GetGameListCompatibilityRatingString(GameListCompatibilityRating rating);
const EntryList& GetEntries() const { return m_entries; }
const u32 GetEntryCount() const { return static_cast<u32>(m_entries.size()); }
const GameListEntry* GetEntryForPath(const char* path) const;
const GameListDatabaseEntry* GetDatabaseEntryForCode(const std::string& code) const;
const GameListCompatibilityEntry* GetCompatibilityEntryForCode(const std::string& code) const;
const std::string& GetCacheFilename() const { return m_cache_filename; }
const std::string& GetDatabaseFilename() const { return m_database_filename; }
const std::string& GetCompatibilityFilename() const { return m_database_filename; }
void SetCacheFilename(std::string filename) { m_cache_filename = std::move(filename); }
void SetDatabaseFilename(std::string filename) { m_database_filename = std::move(filename); }
void SetCompatibilityFilename(std::string filename) { m_compatibility_list_filename = std::move(filename); }
void SetGameSettingsFilename(std::string filename) { m_game_settings_filename = std::move(filename); }
void SetUserGameSettingsFilename(std::string filename) { m_user_game_settings_filename = std::move(filename); }
void SetSearchDirectoriesFromSettings(SettingsInterface& si);
bool IsDatabasePresent() const;
void AddDirectory(std::string path, bool recursive);
void Refresh(bool invalidate_cache, bool invalidate_database, ProgressCallback* progress = nullptr);
void UpdateCompatibilityEntry(GameListCompatibilityEntry new_entry, bool save_to_list = true);
static std::string ExportCompatibilityEntry(const GameListCompatibilityEntry* entry);
const GameSettings::Entry* GetGameSettings(const std::string& filename, const std::string& game_code);
void UpdateGameSettings(const std::string& filename, const std::string& game_code, const std::string& game_title,
const GameSettings::Entry& new_entry, bool save_to_list = true, bool save_to_user = true);
private:
enum : u32
{
GAME_LIST_CACHE_SIGNATURE = 0x45434C47,
GAME_LIST_CACHE_VERSION = 6
};
using DatabaseMap = std::unordered_map<std::string, GameListDatabaseEntry>;
using CacheMap = std::unordered_map<std::string, GameListEntry>;
using CompatibilityMap = std::unordered_map<std::string, GameListCompatibilityEntry>;
struct DirectoryEntry
{
std::string path;
bool recursive;
};
class RedumpDatVisitor;
class CompatibilityListVisitor;
GameListEntry* GetMutableEntryForPath(const char* path);
static bool GetExeListEntry(const char* path, GameListEntry* entry);
bool GetM3UListEntry(const char* path, GameListEntry* entry);
bool GetGameListEntry(const std::string& path, GameListEntry* entry);
bool GetGameListEntryFromCache(const std::string& path, GameListEntry* entry);
void ScanDirectory(const char* path, bool recursive, ProgressCallback* progress);
void LoadCache();
bool LoadEntriesFromCache(ByteStream* stream);
bool OpenCacheForWriting();
bool WriteEntryToCache(const GameListEntry* entry, ByteStream* stream);
void FlushCacheFileStream();
void CloseCacheFileStream();
void RewriteCacheFile();
void DeleteCacheFile();
void LoadDatabase();
void ClearDatabase();
void LoadCompatibilityList();
bool SaveCompatibilityDatabase();
bool SaveCompatibilityDatabaseForEntry(const GameListCompatibilityEntry* entry);
void LoadGameSettings();
DatabaseMap m_database;
EntryList m_entries;
CacheMap m_cache_map;
CompatibilityMap m_compatibility_list;
GameSettings::Database m_game_settings;
std::unique_ptr<ByteStream> m_cache_write_stream;
std::vector<DirectoryEntry> m_search_directories;
std::string m_cache_filename;
std::string m_database_filename;
std::string m_compatibility_list_filename;
std::string m_game_settings_filename;
std::string m_user_game_settings_filename;
bool m_database_load_tried = false;
bool m_compatibility_list_load_tried = false;
bool m_game_settings_load_tried = false;
};

View File

@ -0,0 +1,477 @@
#include "game_settings.h"
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string.h"
#include "common/string_util.h"
#include "core/host_interface.h"
#include "core/settings.h"
#include <array>
#include <utility>
Log_SetChannel(GameSettings);
#ifdef WIN32
#include "common/windows_headers.h"
#endif
#include "SimpleIni.h"
namespace GameSettings {
std::array<std::pair<const char*, const char*>, static_cast<u32>(Trait::Count)> s_trait_names = {{
{"ForceInterpreter", TRANSLATABLE("GameSettingsTrait", "Force Interpreter")},
{"ForceSoftwareRenderer", TRANSLATABLE("GameSettingsTrait", "Force Software Renderer")},
{"ForceInterlacing", TRANSLATABLE("GameSettingsTrait", "Force Interlacing")},
{"DisableTrueColor", TRANSLATABLE("GameSettingsTrait", "Disable True Color")},
{"DisableUpscaling", TRANSLATABLE("GameSettingsTrait", "Disable Upscaling")},
{"DisableScaledDithering", TRANSLATABLE("GameSettingsTrait", "Disable Scaled Dithering")},
{"DisableWidescreen", TRANSLATABLE("GameSettingsTrait", "Disable Widescreen")},
{"DisablePGXP", TRANSLATABLE("GameSettingsTrait", "Disable PGXP")},
{"DisablePGXPCulling", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Culling")},
{"ForcePGXPVertexCache", TRANSLATABLE("GameSettingsTrait", "Force PGXP Vertex Cache")},
{"ForcePGXPCPUMode", TRANSLATABLE("GameSettingsTrait", "Force PGXP CPU Mode")},
{"ForceDigitalController", TRANSLATABLE("GameSettingsTrait", "Force Digital Controller")},
{"ForceRecompilerMemoryExceptions", TRANSLATABLE("GameSettingsTrait", "Force Recompiler Memory Exceptions")},
{"ForceRecompilerICache", TRANSLATABLE("GameSettingsTrait", "Force Recompiler ICache")},
}};
const char* GetTraitName(Trait trait)
{
DebugAssert(trait < Trait::Count);
return s_trait_names[static_cast<u32>(trait)].first;
}
const char* GetTraitDisplayName(Trait trait)
{
DebugAssert(trait < Trait::Count);
return s_trait_names[static_cast<u32>(trait)].second;
}
bool Entry::HasAnySettings() const
{
return traits.any() || display_active_start_offset >= 0 || display_active_end_offset > 0;
}
template<typename T>
bool ReadOptionalFromStream(ByteStream* stream, std::optional<T>* dest)
{
bool has_value;
if (!stream->Read2(&has_value, sizeof(has_value)))
return false;
if (!has_value)
return true;
T value;
if (!stream->Read2(&value, sizeof(T)))
return false;
*dest = value;
return true;
}
template<typename T>
bool WriteOptionalToStream(ByteStream* stream, const std::optional<T>& src)
{
const bool has_value = src.has_value();
if (!stream->Write2(&has_value, sizeof(has_value)))
return false;
if (!has_value)
return true;
return stream->Write2(&src.value(), sizeof(T));
}
bool Entry::LoadFromStream(ByteStream* stream)
{
constexpr u32 num_bytes = (static_cast<u32>(Trait::Count) + 7) / 8;
std::array<u8, num_bytes> bits;
if (!stream->Read2(bits.data(), num_bytes) || !ReadOptionalFromStream(stream, &display_active_start_offset) ||
!ReadOptionalFromStream(stream, &display_active_end_offset) ||
!ReadOptionalFromStream(stream, &display_crop_mode) || !ReadOptionalFromStream(stream, &display_aspect_ratio) ||
!ReadOptionalFromStream(stream, &controller_1_type) || !ReadOptionalFromStream(stream, &controller_2_type))
{
return false;
}
traits.reset();
for (u32 i = 0; i < static_cast<int>(Trait::Count); i++)
{
if ((bits[i / 8] & (1u << (i % 8))) != 0)
AddTrait(static_cast<Trait>(i));
}
return true;
}
bool Entry::SaveToStream(ByteStream* stream) const
{
constexpr u32 num_bytes = (static_cast<u32>(Trait::Count) + 7) / 8;
std::array<u8, num_bytes> bits;
bits.fill(0);
for (u32 i = 0; i < static_cast<int>(Trait::Count); i++)
{
if (HasTrait(static_cast<Trait>(i)))
bits[i / 8] |= (1u << (i % 8));
}
return stream->Write2(bits.data(), num_bytes) && WriteOptionalToStream(stream, display_active_start_offset) &&
WriteOptionalToStream(stream, display_active_end_offset) && WriteOptionalToStream(stream, display_crop_mode) &&
WriteOptionalToStream(stream, display_aspect_ratio) && WriteOptionalToStream(stream, controller_1_type) &&
WriteOptionalToStream(stream, controller_2_type);
}
static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA& ini)
{
for (u32 trait = 0; trait < static_cast<u32>(Trait::Count); trait++)
{
if (ini.GetBoolValue(section, s_trait_names[trait].first, false))
entry->AddTrait(static_cast<Trait>(trait));
}
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);
const char* cvalue = ini.GetValue(section, "DisplayCropMode", nullptr);
if (cvalue)
entry->display_crop_mode = Settings::ParseDisplayCropMode(cvalue);
cvalue = ini.GetValue(section, "DisplayAspectRatio", nullptr);
if (cvalue)
entry->display_aspect_ratio = Settings::ParseDisplayAspectRatio(cvalue);
cvalue = ini.GetValue(section, "Controller1Type", nullptr);
if (cvalue)
entry->controller_1_type = Settings::ParseControllerTypeName(cvalue);
cvalue = ini.GetValue(section, "Controller2Type", nullptr);
if (cvalue)
entry->controller_2_type = Settings::ParseControllerTypeName(cvalue);
cvalue = ini.GetValue(section, "GPUWidescreenHack", nullptr);
if (cvalue)
entry->gpu_widescreen_hack = StringUtil::FromChars<bool>(cvalue);
}
static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA& ini)
{
for (u32 trait = 0; trait < static_cast<u32>(Trait::Count); trait++)
{
if (entry.HasTrait(static_cast<Trait>(trait)))
ini.SetBoolValue(section, s_trait_names[trait].first, true);
}
if (entry.display_active_start_offset.has_value())
ini.SetLongValue(section, "DisplayActiveStartOffset", entry.display_active_start_offset.value());
if (entry.display_active_end_offset.has_value())
ini.SetLongValue(section, "DisplayActiveEndOffset", entry.display_active_end_offset.value());
if (entry.display_crop_mode.has_value())
ini.SetValue(section, "DisplayCropMode", Settings::GetDisplayCropModeName(entry.display_crop_mode.value()));
if (entry.display_aspect_ratio.has_value())
{
ini.SetValue(section, "DisplayAspectRatio",
Settings::GetDisplayAspectRatioName(entry.display_aspect_ratio.value()));
}
if (entry.controller_1_type.has_value())
ini.SetValue(section, "Controller1Type", Settings::GetControllerTypeName(entry.controller_1_type.value()));
if (entry.controller_2_type.has_value())
ini.SetValue(section, "Controller2Type", Settings::GetControllerTypeName(entry.controller_2_type.value()));
if (entry.gpu_widescreen_hack.has_value())
ini.SetValue(section, "GPUWidescreenHack", entry.gpu_widescreen_hack.value() ? "true" : "false");
}
Database::Database() = default;
Database::~Database() = default;
const GameSettings::Entry* Database::GetEntry(const std::string& code) const
{
auto it = m_entries.find(code);
return (it != m_entries.end()) ? &it->second : nullptr;
}
bool Database::Load(const char* path)
{
auto fp = FileSystem::OpenManagedCFile(path, "rb");
if (!fp)
return false;
CSimpleIniA ini;
SI_Error err = ini.LoadFile(fp.get());
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)
{
std::string code(section_entry.pItem);
auto it = m_entries.find(code);
if (it != m_entries.end())
{
ParseIniSection(&it->second, code.c_str(), ini);
continue;
}
Entry entry;
ParseIniSection(&entry, code.c_str(), ini);
m_entries.emplace(std::move(code), std::move(entry));
}
Log_InfoPrintf("Loaded settings for %zu games from '%s'", sections.size(), path);
return true;
}
void Database::SetEntry(const std::string& code, const std::string& name, const Entry& entry, const char* save_path)
{
if (save_path)
{
CSimpleIniA ini;
if (FileSystem::FileExists(save_path))
{
auto fp = FileSystem::OpenManagedCFile(save_path, "rb");
if (fp)
{
SI_Error err = ini.LoadFile(fp.get());
if (err != SI_OK)
Log_ErrorPrintf("Failed to parse game settings ini: %d. Contents will be lost.", static_cast<int>(err));
}
else
{
Log_ErrorPrintf("Failed to open existing settings ini: '%s'", save_path);
}
}
ini.Delete(code.c_str(), nullptr, false);
ini.SetValue(code.c_str(), nullptr, nullptr, SmallString::FromFormat("# %s (%s)", code.c_str(), name.c_str()),
false);
StoreIniSection(entry, code.c_str(), ini);
const bool did_exist = FileSystem::FileExists(save_path);
auto fp = FileSystem::OpenManagedCFile(save_path, "wb");
if (fp)
{
// write file comment so simpleini doesn't get confused
if (!did_exist)
std::fputs("# DuckStation Game Settings\n\n", fp.get());
SI_Error err = ini.SaveFile(fp.get());
if (err != SI_OK)
Log_ErrorPrintf("Failed to save game settings ini: %d", static_cast<int>(err));
}
else
{
Log_ErrorPrintf("Failed to open settings ini for saving: '%s'", save_path);
}
}
auto it = m_entries.find(code);
if (it != m_entries.end())
it->second = entry;
else
m_entries.emplace(code, entry);
}
void Entry::ApplySettings(bool display_osd_messages) const
{
constexpr float osd_duration = 10.0f;
if (display_active_start_offset.has_value())
g_settings.display_active_start_offset = display_active_start_offset.value();
if (display_active_end_offset.has_value())
g_settings.display_active_end_offset = display_active_end_offset.value();
if (display_crop_mode.has_value())
g_settings.display_crop_mode = display_crop_mode.value();
if (display_aspect_ratio.has_value())
g_settings.display_aspect_ratio = display_aspect_ratio.value();
if (controller_1_type.has_value())
g_settings.controller_types[0] = controller_1_type.value();
if (controller_2_type.has_value())
g_settings.controller_types[1] = controller_2_type.value();
if (gpu_widescreen_hack.has_value())
g_settings.gpu_widescreen_hack = gpu_widescreen_hack.value();
if (HasTrait(Trait::ForceInterpreter))
{
if (display_osd_messages && g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "CPU interpreter forced by game settings."), osd_duration);
}
g_settings.cpu_execution_mode = CPUExecutionMode::Interpreter;
}
if (HasTrait(Trait::ForceSoftwareRenderer))
{
if (display_osd_messages && g_settings.gpu_renderer != GPURenderer::Software)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Software renderer forced by game settings."), osd_duration);
}
g_settings.gpu_renderer = GPURenderer::Software;
}
if (HasTrait(Trait::ForceInterlacing))
{
if (display_osd_messages && g_settings.gpu_disable_interlacing)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Interlacing forced by game settings."), osd_duration);
}
g_settings.gpu_disable_interlacing = false;
}
if (HasTrait(Trait::DisableTrueColor))
{
if (display_osd_messages && g_settings.gpu_true_color)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "True color disabled by game settings."), osd_duration);
}
g_settings.gpu_true_color = false;
}
if (HasTrait(Trait::DisableUpscaling))
{
if (display_osd_messages && g_settings.gpu_resolution_scale > 1)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Upscaling disabled by game settings."), osd_duration);
}
g_settings.gpu_resolution_scale = 1;
}
if (HasTrait(Trait::DisableScaledDithering))
{
if (display_osd_messages && g_settings.gpu_scaled_dithering)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Scaled dithering disabled by game settings."),
osd_duration);
}
g_settings.gpu_scaled_dithering = false;
}
if (HasTrait(Trait::DisableWidescreen))
{
if (display_osd_messages &&
(g_settings.display_aspect_ratio == DisplayAspectRatio::R16_9 || g_settings.gpu_widescreen_hack))
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Widescreen disabled by game settings."), osd_duration);
}
g_settings.display_aspect_ratio = DisplayAspectRatio::R4_3;
g_settings.gpu_widescreen_hack = false;
}
if (HasTrait(Trait::DisablePGXP))
{
if (display_osd_messages && g_settings.gpu_pgxp_enable)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "PGXP geometry correction disabled by game settings."),
osd_duration);
}
g_settings.gpu_pgxp_enable = false;
}
if (HasTrait(Trait::DisablePGXPCulling))
{
if (display_osd_messages && g_settings.gpu_pgxp_culling)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "PGXP culling disabled by game settings."), osd_duration);
}
g_settings.gpu_pgxp_culling = false;
}
if (HasTrait(Trait::ForcePGXPVertexCache))
{
if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_vertex_cache)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "PGXP vertex cache forced by game settings."), osd_duration);
}
g_settings.gpu_pgxp_vertex_cache = true;
}
if (HasTrait(Trait::ForcePGXPCPUMode))
{
if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_cpu)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "PGXP CPU mode forced by game settings."), osd_duration);
}
g_settings.gpu_pgxp_cpu = true;
}
if (HasTrait(Trait::ForceDigitalController))
{
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
{
if (g_settings.controller_types[i] != ControllerType::None &&
g_settings.controller_types[i] != ControllerType::DigitalController)
{
if (display_osd_messages)
{
g_host_interface->AddFormattedOSDMessage(
osd_duration,
g_host_interface->TranslateString("OSDMessage", "Controller %u changed to digital by game settings."),
i + 1u);
}
g_settings.controller_types[i] = ControllerType::DigitalController;
}
}
}
if (HasTrait(Trait::ForceRecompilerMemoryExceptions))
{
if (display_osd_messages && g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler &&
!g_settings.cpu_recompiler_memory_exceptions)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Recompiler memory exceptions forced by game settings."),
osd_duration);
}
g_settings.cpu_recompiler_memory_exceptions = true;
}
if (HasTrait(Trait::ForceRecompilerICache))
{
if (display_osd_messages && g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter &&
!g_settings.cpu_recompiler_icache)
{
g_host_interface->AddOSDMessage(
g_host_interface->TranslateStdString("OSDMessage", "Recompiler ICache forced by game settings."), osd_duration);
}
g_settings.cpu_recompiler_icache = true;
}
}
} // namespace GameSettings

View File

@ -0,0 +1,75 @@
#pragma once
#include "core/types.h"
#include <bitset>
#include <optional>
#include <string>
#include <unordered_map>
class ByteStream;
namespace GameSettings {
enum class Trait : u32
{
ForceInterpreter,
ForceSoftwareRenderer,
ForceInterlacing,
DisableTrueColor,
DisableUpscaling,
DisableScaledDithering,
DisableWidescreen,
DisablePGXP,
DisablePGXPCulling,
ForcePGXPVertexCache,
ForcePGXPCPUMode,
ForceDigitalController,
ForceRecompilerMemoryExceptions,
ForceRecompilerICache,
Count
};
const char* GetTraitName(Trait trait);
const char* GetTraitDisplayName(Trait trait);
struct Entry
{
std::bitset<static_cast<int>(Trait::Count)> traits{};
std::optional<s16> display_active_start_offset;
std::optional<s16> display_active_end_offset;
// user settings
std::optional<DisplayCropMode> display_crop_mode;
std::optional<DisplayAspectRatio> display_aspect_ratio;
std::optional<ControllerType> controller_1_type;
std::optional<ControllerType> controller_2_type;
std::optional<bool> gpu_widescreen_hack;
ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast<int>(trait)]; }
ALWAYS_INLINE void AddTrait(Trait trait) { traits[static_cast<int>(trait)] = true; }
ALWAYS_INLINE void RemoveTrait(Trait trait) { traits[static_cast<int>(trait)] = false; }
ALWAYS_INLINE void SetTrait(Trait trait, bool enabled) { traits[static_cast<int>(trait)] = enabled; }
bool HasAnySettings() const;
bool LoadFromStream(ByteStream* stream);
bool SaveToStream(ByteStream* stream) const;
void ApplySettings(bool display_osd_messages) const;
};
class Database
{
public:
Database();
~Database();
const Entry* GetEntry(const std::string& code) const;
void SetEntry(const std::string& code, const std::string& name, const Entry& entry, const char* save_path);
bool Load(const char* path);
private:
std::unordered_map<std::string, Entry> m_entries;
};
}; // namespace GameSettings