Add per-game overrides (mainly for compatibility)
This commit is contained in:
parent
e8da20f174
commit
60d3fffec1
|
@ -11,6 +11,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c
|
|||
|
||||
## Latest News
|
||||
|
||||
- 2020/08/20: Per-game setting overrides added. Mostly for compatibility, but some options are customizable.
|
||||
- 2020/08/19: CPU PGXP mode added. It is very slow and incompatible with the recompiler, only use for games which need it.
|
||||
- 2020/08/15: Playlist support/single memcard for multi-disc games in Qt frontend added.
|
||||
- 2020/08/07: Automatic updater for standalone Windows builds.
|
||||
|
|
|
@ -26,6 +26,8 @@ add_library(core
|
|||
dma.h
|
||||
game_list.cpp
|
||||
game_list.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
gpu.cpp
|
||||
gpu.h
|
||||
gpu_commands.cpp
|
||||
|
@ -96,7 +98,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 common imgui tinyxml2 zlib vulkan-loader)
|
||||
target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib vulkan-loader simpleini)
|
||||
target_link_libraries(core PRIVATE glad stb)
|
||||
|
||||
if(WIN32)
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
<ClCompile Include="cpu_types.cpp" />
|
||||
<ClCompile Include="digital_controller.cpp" />
|
||||
<ClCompile Include="game_list.cpp" />
|
||||
<ClCompile Include="game_settings.cpp" />
|
||||
<ClCompile Include="gpu_commands.cpp" />
|
||||
<ClCompile Include="gpu_hw_d3d11.cpp" />
|
||||
<ClCompile Include="gpu_hw_shadergen.cpp" />
|
||||
|
@ -107,6 +108,7 @@
|
|||
<ClInclude Include="cpu_recompiler_types.h" />
|
||||
<ClInclude Include="digital_controller.h" />
|
||||
<ClInclude Include="game_list.h" />
|
||||
<ClInclude Include="game_settings.h" />
|
||||
<ClInclude Include="gpu_hw_d3d11.h" />
|
||||
<ClInclude Include="gpu_hw_shadergen.h" />
|
||||
<ClInclude Include="gpu_hw_vulkan.h" />
|
||||
|
@ -142,6 +144,12 @@
|
|||
<ClInclude Include="types.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\simpleini\simpleini.vcxproj">
|
||||
<Project>{3773f4cc-614e-4028-8595-22e08ca649e3}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\stb\stb.vcxproj">
|
||||
<Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project>
|
||||
</ProjectReference>
|
||||
|
@ -299,7 +307,7 @@
|
|||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
|
@ -325,7 +333,7 @@
|
|||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
|
@ -351,7 +359,7 @@
|
|||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
|
@ -380,7 +388,7 @@
|
|||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
|
@ -408,7 +416,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
|
@ -435,7 +443,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
|
@ -463,7 +471,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
|
@ -490,7 +498,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<ClCompile Include="resources.cpp" />
|
||||
<ClCompile Include="host_interface_progress_callback.cpp" />
|
||||
<ClCompile Include="pgxp.cpp" />
|
||||
<ClCompile Include="game_settings.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -97,5 +98,6 @@
|
|||
<ClInclude Include="gte_types.h" />
|
||||
<ClInclude Include="pgxp.h" />
|
||||
<ClInclude Include="cpu_core_private.h" />
|
||||
<ClInclude Include="game_settings.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -446,6 +446,12 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry)
|
|||
entry->compatibility_rating = compatibility_entry->compatibility_rating;
|
||||
else
|
||||
Log_WarningPrintf("'%s' (%s) not found in compatibility list", entry->code.c_str(), entry->title.c_str());
|
||||
|
||||
if (!m_game_settings_load_tried)
|
||||
LoadGameSettings();
|
||||
const GameSettings::Entry* settings = m_game_settings.GetEntry(entry->code);
|
||||
if (settings)
|
||||
entry->settings = *settings;
|
||||
}
|
||||
|
||||
FILESYSTEM_STAT_DATA ffd;
|
||||
|
@ -577,6 +583,12 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
|
|||
ge.type = static_cast<GameListEntryType>(type);
|
||||
ge.compatibility_rating = static_cast<GameListCompatibilityRating>(compatibility_rating);
|
||||
|
||||
if (!ge.settings.LoadFromStream(stream))
|
||||
{
|
||||
Log_WarningPrintf("Game list cache entry is corrupted (settings)");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = m_cache_map.find(ge.path);
|
||||
if (iter != m_cache_map.end())
|
||||
iter->second = std::move(ge);
|
||||
|
@ -625,6 +637,7 @@ bool GameList::WriteEntryToCache(const GameListEntry* entry, ByteStream* stream)
|
|||
result &= WriteU8(stream, static_cast<u8>(entry->region));
|
||||
result &= WriteU8(stream, static_cast<u8>(entry->type));
|
||||
result &= WriteU8(stream, static_cast<u8>(entry->compatibility_rating));
|
||||
result &= entry->settings.SaveToStream(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -853,6 +866,18 @@ const GameListEntry* GameList::GetEntryForPath(const char* path) const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
GameListEntry* GameList::GetMutableEntryForPath(const char* path)
|
||||
{
|
||||
const size_t path_length = std::strlen(path);
|
||||
for (GameListEntry& entry : m_entries)
|
||||
{
|
||||
if (entry.path.size() == path_length && StringUtil::Strcasecmp(entry.path.c_str(), path) == 0)
|
||||
return &entry;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GameListDatabaseEntry* GameList::GetDatabaseEntryForCode(const std::string& code) const
|
||||
{
|
||||
if (!m_database_load_tried)
|
||||
|
@ -1272,3 +1297,46 @@ std::string GameList::ExportCompatibilityEntry(const GameListCompatibilityEntry*
|
|||
entry_elem->Accept(&printer);
|
||||
return std::string(printer.CStr(), printer.CStrSize());
|
||||
}
|
||||
|
||||
void GameList::LoadGameSettings()
|
||||
{
|
||||
if (m_game_settings_load_tried)
|
||||
return;
|
||||
|
||||
m_game_settings_load_tried = true;
|
||||
|
||||
if (!m_game_settings_filename.empty() && FileSystem::FileExists(m_user_game_settings_filename.c_str()))
|
||||
m_game_settings.Load(m_game_settings_filename.c_str());
|
||||
if (!m_user_game_settings_filename.empty() && FileSystem::FileExists(m_user_game_settings_filename.c_str()))
|
||||
m_game_settings.Load(m_user_game_settings_filename.c_str());
|
||||
}
|
||||
|
||||
const GameSettings::Entry* GameList::GetGameSettings(const std::string& filename, const std::string& game_code)
|
||||
{
|
||||
const GameListEntry* entry = GetMutableEntryForPath(filename.c_str());
|
||||
if (entry)
|
||||
return &entry->settings;
|
||||
|
||||
if (!m_game_settings_load_tried)
|
||||
LoadGameSettings();
|
||||
|
||||
return m_game_settings.GetEntry(game_code);
|
||||
}
|
||||
|
||||
void GameList::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 */)
|
||||
{
|
||||
GameListEntry* entry = GetMutableEntryForPath(filename.c_str());
|
||||
if (entry)
|
||||
{
|
||||
entry->settings = new_entry;
|
||||
RewriteCacheFile();
|
||||
}
|
||||
|
||||
if (save_to_list)
|
||||
{
|
||||
m_game_settings.SetEntry(game_code, game_title, new_entry,
|
||||
save_to_user ? m_user_game_settings_filename.c_str() : m_game_settings_filename.c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include "game_settings.h"
|
||||
#include "types.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -48,6 +49,7 @@ struct GameListEntry
|
|||
DiscRegion region;
|
||||
GameListEntryType type;
|
||||
GameListCompatibilityRating compatibility_rating;
|
||||
GameSettings::Entry settings;
|
||||
};
|
||||
|
||||
struct GameListCompatibilityEntry
|
||||
|
@ -109,6 +111,8 @@ public:
|
|||
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;
|
||||
|
@ -120,11 +124,15 @@ public:
|
|||
|
||||
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 = 5
|
||||
GAME_LIST_CACHE_VERSION = 6
|
||||
};
|
||||
|
||||
using DatabaseMap = std::unordered_map<std::string, GameListDatabaseEntry>;
|
||||
|
@ -140,6 +148,8 @@ private:
|
|||
class RedumpDatVisitor;
|
||||
class CompatibilityListVisitor;
|
||||
|
||||
GameListEntry* GetMutableEntryForPath(const char* path);
|
||||
|
||||
static bool GetExeListEntry(const char* path, GameListEntry* entry);
|
||||
bool GetM3UListEntry(const char* path, GameListEntry* entry);
|
||||
|
||||
|
@ -163,16 +173,22 @@ private:
|
|||
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;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,429 @@
|
|||
#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 "host_interface.h"
|
||||
#include "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", "Force Interpreter"},
|
||||
{"ForceSoftwareRenderer", "Force Software Renderer"},
|
||||
{"EnableInterlacing", "Enable Interlacing"},
|
||||
{"DisableTrueColor", "Disable True Color"},
|
||||
{"DisableUpscaling", "Disable Upscaling"},
|
||||
{"DisableScaledDithering", "Disable Scaled Dithering"},
|
||||
{"DisableWidescreen", "Disable Widescreen"},
|
||||
{"DisablePGXP", "Disable PGXP"},
|
||||
{"DisablePGXPCulling", "Disable PGXP Culling"},
|
||||
{"EnablePGXPVertexCache", "Enable PGXP Vertex Cache"},
|
||||
{"EnablePGXPCPUMode", "Enable PGXP CPU Mode"},
|
||||
{"ForceDigitalController", "Force Digital Controller"},
|
||||
{"EnableRecompilerMemoryExceptions", "Enable Recompiler Memory Exceptions"},
|
||||
}};
|
||||
|
||||
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 != S_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 != S_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 != S_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("CPU execution mode forced to interpreter 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("GPU renderer forced to software by game settings.", osd_duration);
|
||||
|
||||
g_settings.gpu_renderer = GPURenderer::Software;
|
||||
}
|
||||
|
||||
if (HasTrait(Trait::EnableInterlacing))
|
||||
{
|
||||
if (display_osd_messages && g_settings.gpu_disable_interlacing)
|
||||
g_host_interface->AddOSDMessage("Interlacing enabled 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("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("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("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("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("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("PGXP culling disabled by game settings.", osd_duration);
|
||||
|
||||
g_settings.gpu_pgxp_culling = false;
|
||||
}
|
||||
|
||||
if (HasTrait(Trait::EnablePGXPVertexCache))
|
||||
{
|
||||
if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_vertex_cache)
|
||||
g_host_interface->AddOSDMessage("PGXP vertex cache enabled by game settings.", osd_duration);
|
||||
|
||||
g_settings.gpu_pgxp_vertex_cache = true;
|
||||
}
|
||||
|
||||
if (HasTrait(Trait::EnablePGXPCPUMode))
|
||||
{
|
||||
if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_cpu)
|
||||
g_host_interface->AddOSDMessage("PGXP CPU mode enabled 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, "Controller %u changed to digital by game settings.",
|
||||
i + 1u);
|
||||
}
|
||||
|
||||
g_settings.controller_types[i] = ControllerType::DigitalController;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (HasTrait(Trait::EnableRecompilerMemoryExceptions))
|
||||
{
|
||||
if (display_osd_messages && g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler &&
|
||||
!g_settings.cpu_recompiler_memory_exceptions)
|
||||
{
|
||||
g_host_interface->AddOSDMessage("Recompiler memory exceptions enabled by game settings.", osd_duration);
|
||||
}
|
||||
|
||||
g_settings.cpu_recompiler_memory_exceptions = true;
|
||||
}
|
||||
|
||||
// TODO: Overscan settings.
|
||||
}
|
||||
|
||||
} // namespace GameSettings
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
#include "types.h"
|
||||
#include <bitset>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class ByteStream;
|
||||
|
||||
namespace GameSettings {
|
||||
enum class Trait : u32
|
||||
{
|
||||
ForceInterpreter,
|
||||
ForceSoftwareRenderer,
|
||||
EnableInterlacing,
|
||||
DisableTrueColor,
|
||||
DisableUpscaling,
|
||||
DisableScaledDithering,
|
||||
DisableWidescreen,
|
||||
DisablePGXP,
|
||||
DisablePGXPCulling,
|
||||
EnablePGXPVertexCache,
|
||||
EnablePGXPCPUMode,
|
||||
ForceDigitalController,
|
||||
EnableRecompilerMemoryExceptions,
|
||||
|
||||
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
|
|
@ -513,15 +513,15 @@ void GPU::UpdateCRTCDisplayParameters()
|
|||
switch (crop_mode)
|
||||
{
|
||||
case DisplayCropMode::None:
|
||||
cs.horizontal_active_start = 487;
|
||||
cs.horizontal_active_end = 3282;
|
||||
cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 487 + g_settings.display_active_start_offset));
|
||||
cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3282 + g_settings.display_active_end_offset));
|
||||
cs.vertical_active_start = 20;
|
||||
cs.vertical_active_end = 308;
|
||||
break;
|
||||
|
||||
case DisplayCropMode::Overscan:
|
||||
cs.horizontal_active_start = 628;
|
||||
cs.horizontal_active_end = 3188;
|
||||
cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 628 + g_settings.display_active_start_offset));
|
||||
cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3188 + g_settings.display_active_end_offset));
|
||||
cs.vertical_active_start = 30;
|
||||
cs.vertical_active_end = 298;
|
||||
break;
|
||||
|
@ -540,15 +540,15 @@ void GPU::UpdateCRTCDisplayParameters()
|
|||
switch (crop_mode)
|
||||
{
|
||||
case DisplayCropMode::None:
|
||||
cs.horizontal_active_start = 488;
|
||||
cs.horizontal_active_end = 3288;
|
||||
cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 488 + g_settings.display_active_start_offset));
|
||||
cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3288 + g_settings.display_active_end_offset));
|
||||
cs.vertical_active_start = 16;
|
||||
cs.vertical_active_end = 256;
|
||||
break;
|
||||
|
||||
case DisplayCropMode::Overscan:
|
||||
cs.horizontal_active_start = 608;
|
||||
cs.horizontal_active_end = 3168;
|
||||
cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 608 + g_settings.display_active_start_offset));
|
||||
cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3168 + g_settings.display_active_end_offset));
|
||||
cs.vertical_active_start = 24;
|
||||
cs.vertical_active_end = 248;
|
||||
break;
|
||||
|
@ -766,7 +766,7 @@ void GPU::CRTCTickEvent(TickCount ticks)
|
|||
else
|
||||
{
|
||||
m_crtc_state.interlaced_field = 0;
|
||||
m_GPUSTAT.interlaced_field = 0u; // new GPU = 1, old GPU = 0
|
||||
m_GPUSTAT.interlaced_field = 0u; // new GPU = 1, old GPU = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -378,6 +378,8 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
|||
si.SetBoolValue("GPU", "PGXPCPU", false);
|
||||
|
||||
si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE));
|
||||
si.SetIntValue("Display", "OverscanActiveStartOffset", 0);
|
||||
si.SetIntValue("Display", "OverscanActiveEndOffset", 0);
|
||||
si.SetStringValue("Display", "AspectRatio",
|
||||
Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO));
|
||||
si.SetBoolValue("Display", "LinearFiltering", true);
|
||||
|
@ -467,7 +469,7 @@ void HostInterface::SaveSettings(SettingsInterface& si)
|
|||
|
||||
void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
|
||||
{
|
||||
if (!System::IsShutdown())
|
||||
if (System::IsValid())
|
||||
{
|
||||
if (g_settings.gpu_renderer != old_settings.gpu_renderer ||
|
||||
g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device)
|
||||
|
@ -522,7 +524,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
|
|||
g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings ||
|
||||
g_settings.display_crop_mode != old_settings.display_crop_mode ||
|
||||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio ||
|
||||
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable)
|
||||
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
|
||||
g_settings.display_active_start_offset != old_settings.display_active_start_offset ||
|
||||
g_settings.display_active_end_offset != old_settings.display_active_end_offset)
|
||||
{
|
||||
g_gpu->UpdateSettings();
|
||||
}
|
||||
|
|
|
@ -116,6 +116,8 @@ void Settings::Load(SettingsInterface& si)
|
|||
ParseDisplayAspectRatio(
|
||||
si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str())
|
||||
.value_or(DEFAULT_DISPLAY_ASPECT_RATIO);
|
||||
display_active_start_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveStartOffset", 0));
|
||||
display_active_end_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveEndOffset", 0));
|
||||
display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true);
|
||||
display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false);
|
||||
display_show_osd_messages = si.GetBoolValue("Display", "ShowOSDMessages", true);
|
||||
|
@ -219,6 +221,8 @@ void Settings::Save(SettingsInterface& si) const
|
|||
si.SetBoolValue("GPU", "PGXPCPU", gpu_pgxp_cpu);
|
||||
|
||||
si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode));
|
||||
si.SetIntValue("Display", "ActiveStartOffset", display_active_start_offset);
|
||||
si.SetIntValue("Display", "ActiveEndOffset", display_active_end_offset);
|
||||
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
|
||||
si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering);
|
||||
si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling);
|
||||
|
|
|
@ -95,6 +95,8 @@ struct Settings
|
|||
bool gpu_pgxp_vertex_cache = false;
|
||||
bool gpu_pgxp_cpu = false;
|
||||
DisplayCropMode display_crop_mode = DisplayCropMode::None;
|
||||
s16 display_active_start_offset = 0;
|
||||
s16 display_active_end_offset = 0;
|
||||
DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::R4_3;
|
||||
bool display_linear_filtering = true;
|
||||
bool display_integer_scaling = false;
|
||||
|
|
|
@ -131,7 +131,7 @@ bool IsShutdown()
|
|||
|
||||
bool IsValid()
|
||||
{
|
||||
return s_state != State::Shutdown;
|
||||
return s_state != State::Shutdown && s_state != State::Starting;
|
||||
}
|
||||
|
||||
ConsoleRegion GetRegion()
|
||||
|
@ -384,6 +384,9 @@ bool Boot(const SystemBootParameters& params)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Notify change of disc.
|
||||
UpdateRunningGame(media ? media->GetFileName().c_str() : params.filename.c_str(), media.get());
|
||||
|
||||
// Component setup.
|
||||
if (!Initialize(params.force_software_renderer))
|
||||
{
|
||||
|
@ -391,8 +394,6 @@ bool Boot(const SystemBootParameters& params)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Notify change of disc.
|
||||
UpdateRunningGame(media ? media->GetFileName().c_str() : params.filename.c_str(), media.get());
|
||||
UpdateControllers();
|
||||
UpdateMemoryCards();
|
||||
Reset();
|
||||
|
@ -1269,6 +1270,9 @@ void RemoveMedia()
|
|||
|
||||
void UpdateRunningGame(const char* path, CDImage* image)
|
||||
{
|
||||
if (s_running_game_path == path)
|
||||
return;
|
||||
|
||||
s_running_game_path.clear();
|
||||
s_running_game_code.clear();
|
||||
s_running_game_title.clear();
|
||||
|
|
|
@ -74,6 +74,11 @@ void GamePropertiesDialog::populate(const GameListEntry* ge)
|
|||
}
|
||||
|
||||
populateTracksInfo(ge->path);
|
||||
|
||||
m_game_code = ge->code;
|
||||
m_game_title = ge->title;
|
||||
m_game_settings = ge->settings;
|
||||
populateGameSettings();
|
||||
}
|
||||
|
||||
void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_code)
|
||||
|
@ -107,6 +112,43 @@ void GamePropertiesDialog::setupAdditionalUi()
|
|||
tr(GameList::GetGameListCompatibilityRatingString(static_cast<GameListCompatibilityRating>(i))));
|
||||
}
|
||||
|
||||
m_ui.userAspectRatio->addItem(tr("(unchanged)"));
|
||||
for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
|
||||
{
|
||||
m_ui.userAspectRatio->addItem(
|
||||
QString::fromUtf8(Settings::GetDisplayAspectRatioName(static_cast<DisplayAspectRatio>(i))));
|
||||
}
|
||||
|
||||
m_ui.userCropMode->addItem(tr("(unchanged)"));
|
||||
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
|
||||
{
|
||||
m_ui.userCropMode->addItem(
|
||||
QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
|
||||
}
|
||||
|
||||
m_ui.userControllerType1->addItem(tr("(unchanged)"));
|
||||
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
|
||||
{
|
||||
m_ui.userControllerType1->addItem(
|
||||
QString::fromUtf8(Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
|
||||
}
|
||||
|
||||
m_ui.userControllerType2->addItem(tr("(unchanged)"));
|
||||
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
|
||||
{
|
||||
m_ui.userControllerType2->addItem(
|
||||
QString::fromUtf8(Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
|
||||
}
|
||||
|
||||
QGridLayout* traits_layout = new QGridLayout(m_ui.compatibilityTraits);
|
||||
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
|
||||
{
|
||||
m_trait_checkboxes[i] =
|
||||
new QCheckBox(QString::fromUtf8(GameSettings::GetTraitDisplayName(static_cast<GameSettings::Trait>(i))),
|
||||
m_ui.compatibilityTraits);
|
||||
traits_layout->addWidget(m_trait_checkboxes[i], i / 2, i % 2);
|
||||
}
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
|
@ -133,7 +175,7 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
|
|||
{"Audio", "Mode 1", "Mode 1/Raw", "Mode 2", "Mode 2/Form 1", "Mode 2/Form 2", "Mode 2/Mix", "Mode 2/Raw"}};
|
||||
|
||||
m_ui.tracks->clearContents();
|
||||
m_image_path = image_path;
|
||||
m_path = image_path;
|
||||
|
||||
std::unique_ptr<CDImage> image = CDImage::Open(image_path.c_str());
|
||||
if (!image)
|
||||
|
@ -155,6 +197,66 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
|
|||
}
|
||||
}
|
||||
|
||||
void GamePropertiesDialog::populateGameSettings()
|
||||
{
|
||||
const GameSettings::Entry& gs = m_game_settings;
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
|
||||
{
|
||||
QSignalBlocker sb(m_trait_checkboxes[i]);
|
||||
m_trait_checkboxes[i]->setChecked(gs.HasTrait(static_cast<GameSettings::Trait>(i)));
|
||||
}
|
||||
|
||||
if (gs.display_active_start_offset.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.displayActiveStartOffset);
|
||||
m_ui.displayActiveStartOffset->setValue(static_cast<int>(gs.display_active_start_offset.value()));
|
||||
}
|
||||
if (gs.display_active_end_offset.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.displayActiveEndOffset);
|
||||
m_ui.displayActiveEndOffset->setValue(static_cast<int>(gs.display_active_end_offset.value()));
|
||||
}
|
||||
|
||||
if (gs.display_crop_mode.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.userCropMode);
|
||||
m_ui.userCropMode->setCurrentIndex(static_cast<int>(gs.display_crop_mode.value()) + 1);
|
||||
}
|
||||
if (gs.display_aspect_ratio.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.userAspectRatio);
|
||||
m_ui.userAspectRatio->setCurrentIndex(static_cast<int>(gs.display_aspect_ratio.value()) + 1);
|
||||
}
|
||||
|
||||
if (gs.controller_1_type.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.userControllerType1);
|
||||
m_ui.userControllerType1->setCurrentIndex(static_cast<int>(gs.controller_1_type.value()) + 1);
|
||||
}
|
||||
if (gs.controller_2_type.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.userControllerType2);
|
||||
m_ui.userControllerType2->setCurrentIndex(static_cast<int>(gs.controller_2_type.value()) + 1);
|
||||
}
|
||||
if (gs.gpu_widescreen_hack.has_value())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.userControllerType2);
|
||||
m_ui.userWidescreenHack->setCheckState(Qt::Checked);
|
||||
}
|
||||
else
|
||||
{
|
||||
QSignalBlocker sb(m_ui.userControllerType2);
|
||||
m_ui.userWidescreenHack->setCheckState(Qt::PartiallyChecked);
|
||||
}
|
||||
}
|
||||
|
||||
void GamePropertiesDialog::saveGameSettings()
|
||||
{
|
||||
m_host_interface->getGameList()->UpdateGameSettings(m_path, m_game_code, m_game_title, m_game_settings, true, true);
|
||||
m_host_interface->applySettings(true);
|
||||
}
|
||||
|
||||
void GamePropertiesDialog::closeEvent(QCloseEvent* ev)
|
||||
{
|
||||
deleteLater();
|
||||
|
@ -186,6 +288,69 @@ void GamePropertiesDialog::connectUi()
|
|||
connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this,
|
||||
&GamePropertiesDialog::onExportCompatibilityInfoClicked);
|
||||
connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close);
|
||||
|
||||
connect(m_ui.userAspectRatio, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (index <= 0)
|
||||
m_game_settings.display_aspect_ratio.reset();
|
||||
else
|
||||
m_game_settings.display_aspect_ratio = static_cast<DisplayAspectRatio>(index - 1);
|
||||
saveGameSettings();
|
||||
});
|
||||
|
||||
connect(m_ui.userCropMode, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (index <= 0)
|
||||
m_game_settings.display_crop_mode.reset();
|
||||
else
|
||||
m_game_settings.display_crop_mode = static_cast<DisplayCropMode>(index - 1);
|
||||
saveGameSettings();
|
||||
});
|
||||
|
||||
connect(m_ui.userControllerType1, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (index <= 0)
|
||||
m_game_settings.controller_1_type.reset();
|
||||
else
|
||||
m_game_settings.controller_1_type = static_cast<ControllerType>(index - 1);
|
||||
saveGameSettings();
|
||||
});
|
||||
|
||||
connect(m_ui.userControllerType2, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (index <= 0)
|
||||
m_game_settings.controller_2_type.reset();
|
||||
else
|
||||
m_game_settings.controller_2_type = static_cast<ControllerType>(index - 1);
|
||||
saveGameSettings();
|
||||
});
|
||||
|
||||
connect(m_ui.userWidescreenHack, &QCheckBox::stateChanged, [this](int state) {
|
||||
if (state == Qt::PartiallyChecked)
|
||||
m_game_settings.gpu_widescreen_hack.reset();
|
||||
else
|
||||
m_game_settings.gpu_widescreen_hack = (state == Qt::Checked);
|
||||
saveGameSettings();
|
||||
});
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
|
||||
{
|
||||
connect(m_trait_checkboxes[i], &QCheckBox::toggled, [this, i](bool checked) {
|
||||
m_game_settings.SetTrait(static_cast<GameSettings::Trait>(i), checked);
|
||||
saveGameSettings();
|
||||
});
|
||||
}
|
||||
|
||||
connect(m_ui.displayActiveStartOffset, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
|
||||
if (value == 0)
|
||||
m_game_settings.display_active_start_offset.reset();
|
||||
else
|
||||
m_game_settings.display_active_start_offset = static_cast<s16>(value);
|
||||
saveGameSettings();
|
||||
});
|
||||
connect(m_ui.displayActiveEndOffset, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
|
||||
if (value == 0)
|
||||
m_game_settings.display_active_end_offset.reset();
|
||||
else
|
||||
m_game_settings.display_active_end_offset = static_cast<s16>(value);
|
||||
saveGameSettings();
|
||||
});
|
||||
}
|
||||
|
||||
void GamePropertiesDialog::fillEntryFromUi(GameListCompatibilityEntry* entry)
|
||||
|
@ -263,10 +428,10 @@ void GamePropertiesDialog::onExportCompatibilityInfoClicked()
|
|||
|
||||
void GamePropertiesDialog::computeTrackHashes()
|
||||
{
|
||||
if (m_image_path.empty())
|
||||
if (m_path.empty())
|
||||
return;
|
||||
|
||||
std::unique_ptr<CDImage> image = CDImage::Open(m_image_path.c_str());
|
||||
std::unique_ptr<CDImage> image = CDImage::Open(m_path.c_str());
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
#include "core/game_settings.h"
|
||||
#include "ui_gamepropertiesdialog.h"
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <array>
|
||||
|
||||
struct GameListEntry;
|
||||
struct GameListCompatibilityEntry;
|
||||
|
@ -40,15 +42,22 @@ private:
|
|||
void connectUi();
|
||||
void populateCompatibilityInfo(const std::string& game_code);
|
||||
void populateTracksInfo(const std::string& image_path);
|
||||
void populateGameSettings();
|
||||
void saveGameSettings();
|
||||
void fillEntryFromUi(GameListCompatibilityEntry* entry);
|
||||
void computeTrackHashes();
|
||||
void onResize();
|
||||
|
||||
Ui::GamePropertiesDialog m_ui;
|
||||
std::array<QCheckBox*, static_cast<u32>(GameSettings::Trait::Count)> m_trait_checkboxes{};
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
std::string m_image_path;
|
||||
std::string m_path;
|
||||
std::string m_game_code;
|
||||
std::string m_game_title;
|
||||
|
||||
GameSettings::Entry m_game_settings;
|
||||
|
||||
bool m_compatibility_info_changed = false;
|
||||
bool m_tracks_hashed = false;
|
||||
|
|
|
@ -6,171 +6,346 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>722</width>
|
||||
<height>466</height>
|
||||
<width>793</width>
|
||||
<height>647</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="resources/icons.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Image Path:</string>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Properties</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Image Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="imagePath">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Game Code:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="gameCode">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="title">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Region:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="region">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Compatibility:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="compatibility"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Upscaling Issues:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="upscalingIssues"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Comments:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="comments"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Version Tested:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="versionTested"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setToCurrent">
|
||||
<property name="text">
|
||||
<string>Set to Current</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Tracks:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QTableWidget" name="tracks">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>#</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Mode</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Length</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Hash</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>User Settings</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>GPU Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Crop Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="userCropMode"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Aspect Ratio:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="userAspectRatio"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="userWidescreenHack">
|
||||
<property name="text">
|
||||
<string>Widescreen Hack</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Controller Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Controller 1 Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="userControllerType1"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Controller 2 Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="userControllerType2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Compatibility Settings</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="compatibilityTraits">
|
||||
<property name="title">
|
||||
<string>Traits</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="compatibilityOverrides">
|
||||
<property name="title">
|
||||
<string>Overrides</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Display Active Offset:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="displayActiveStartOffset">
|
||||
<property name="minimum">
|
||||
<number>-5000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>5000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="displayActiveEndOffset">
|
||||
<property name="minimum">
|
||||
<number>-5000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>5000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="imagePath">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Game Code:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="gameCode">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="title">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Region:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="region">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Compatibility:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="compatibility"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Upscaling Issues:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="upscalingIssues"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Comments:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="comments"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Version Tested:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="versionTested"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setToCurrent">
|
||||
<property name="text">
|
||||
<string>Set to Current</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Tracks:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QTableWidget" name="tracks">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>#</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Mode</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Length</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Hash</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
|
|
|
@ -280,16 +280,17 @@ void QtHostInterface::setDefaultSettings()
|
|||
m_settings_interface->Save();
|
||||
|
||||
CommonHostInterface::LoadSettings(*m_settings_interface.get());
|
||||
CommonHostInterface::ApplyGameSettings(false);
|
||||
}
|
||||
|
||||
CheckForSettingsChanges(old_settings);
|
||||
}
|
||||
|
||||
void QtHostInterface::applySettings()
|
||||
void QtHostInterface::applySettings(bool display_osd_messages /* = false */)
|
||||
{
|
||||
if (!isOnWorkerThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection, Q_ARG(bool, display_osd_messages));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -297,6 +298,7 @@ void QtHostInterface::applySettings()
|
|||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
||||
CommonHostInterface::LoadSettings(*m_settings_interface.get());
|
||||
CommonHostInterface::ApplyGameSettings(display_osd_messages);
|
||||
}
|
||||
|
||||
CheckForSettingsChanges(old_settings);
|
||||
|
@ -649,6 +651,7 @@ void QtHostInterface::OnSystemPerformanceCountersUpdated()
|
|||
void QtHostInterface::OnRunningGameChanged()
|
||||
{
|
||||
CommonHostInterface::OnRunningGameChanged();
|
||||
applySettings(true);
|
||||
|
||||
if (!System::IsShutdown())
|
||||
{
|
||||
|
|
|
@ -129,7 +129,7 @@ Q_SIGNALS:
|
|||
|
||||
public Q_SLOTS:
|
||||
void setDefaultSettings();
|
||||
void applySettings();
|
||||
void applySettings(bool display_osd_messages = false);
|
||||
void updateInputMap();
|
||||
void applyInputProfile(const QString& profile_path);
|
||||
void onDisplayWindowKeyEvent(int key, bool pressed);
|
||||
|
|
|
@ -321,6 +321,11 @@ void SDLHostInterface::OnRunningGameChanged()
|
|||
{
|
||||
CommonHostInterface::OnRunningGameChanged();
|
||||
|
||||
Settings old_settings(std::move(g_settings));
|
||||
CommonHostInterface::LoadSettings(*m_settings_interface.get());
|
||||
CommonHostInterface::ApplyGameSettings(true);
|
||||
CheckForSettingsChanges(old_settings);
|
||||
|
||||
if (!System::GetRunningTitle().empty())
|
||||
SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str());
|
||||
else
|
||||
|
@ -347,6 +352,7 @@ void SDLHostInterface::SaveAndUpdateSettings()
|
|||
|
||||
Settings old_settings(std::move(g_settings));
|
||||
CommonHostInterface::LoadSettings(*m_settings_interface.get());
|
||||
CommonHostInterface::ApplyGameSettings(false);
|
||||
CheckForSettingsChanges(old_settings);
|
||||
|
||||
m_settings_interface->Save();
|
||||
|
|
|
@ -71,6 +71,8 @@ bool CommonHostInterface::Initialize()
|
|||
m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache"));
|
||||
m_game_list->SetDatabaseFilename(GetUserDirectoryRelativePath("cache/redump.dat"));
|
||||
m_game_list->SetCompatibilityFilename(GetProgramDirectoryRelativePath("database/compatibility.xml"));
|
||||
m_game_list->SetGameSettingsFilename(GetProgramDirectoryRelativePath("database/gamesettings.ini"));
|
||||
m_game_list->SetUserGameSettingsFilename(GetUserDirectoryRelativePath("gamesettings.ini"));
|
||||
|
||||
m_save_state_selector_ui = std::make_unique<FrontendCommon::SaveStateSelectorUI>(this);
|
||||
|
||||
|
@ -2024,6 +2026,17 @@ bool CommonHostInterface::SaveScreenshot(const char* filename /* = nullptr */, b
|
|||
return true;
|
||||
}
|
||||
|
||||
void CommonHostInterface::ApplyGameSettings(bool display_osd_messages)
|
||||
{
|
||||
// this gets called while booting, so can't use valid
|
||||
if (System::IsShutdown() || System::GetRunningCode().empty())
|
||||
return;
|
||||
|
||||
const GameSettings::Entry* gs = m_game_list->GetGameSettings(System::GetRunningPath(), System::GetRunningCode());
|
||||
if (gs)
|
||||
gs->ApplySettings(display_osd_messages);
|
||||
}
|
||||
|
||||
#ifdef WITH_DISCORD_PRESENCE
|
||||
|
||||
void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled)
|
||||
|
|
|
@ -266,6 +266,8 @@ protected:
|
|||
|
||||
void RecreateSystem() override;
|
||||
|
||||
void ApplyGameSettings(bool display_osd_messages);
|
||||
|
||||
virtual void DrawImGuiWindows();
|
||||
|
||||
void DrawFPSWindow();
|
||||
|
|
Loading…
Reference in New Issue