From 0b858658cac45d5685eec814fd45bf6d9f2cc1d6 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 4 Oct 2020 18:20:18 +1000 Subject: [PATCH] GameSettings: Add per-game input bindings from profiles This just affects the **bindings**. You will still have to set the controller type per game if this is different from the global default. --- src/duckstation-qt/gamepropertiesdialog.cpp | 23 +++++++++ src/duckstation-qt/gamepropertiesdialog.ui | 10 ++++ src/frontend-common/common_host_interface.cpp | 47 ++++++++++++++++++- src/frontend-common/common_host_interface.h | 4 ++ src/frontend-common/game_list.h | 2 +- src/frontend-common/game_settings.cpp | 9 +++- src/frontend-common/game_settings.h | 1 + 7 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/duckstation-qt/gamepropertiesdialog.cpp b/src/duckstation-qt/gamepropertiesdialog.cpp index c104cd784..5ef3004ce 100644 --- a/src/duckstation-qt/gamepropertiesdialog.cpp +++ b/src/duckstation-qt/gamepropertiesdialog.cpp @@ -154,6 +154,9 @@ void GamePropertiesDialog::setupAdditionalUi() m_ui.userControllerType2->addItem( qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast(i)))); } + m_ui.userInputProfile->addItem(tr("(unchanged)")); + for (const auto& it : m_host_interface->getInputProfileList()) + m_ui.userInputProfile->addItem(QString::fromStdString(it.name)); m_ui.userMemoryCard1Type->addItem(tr("(unchanged)")); for (u32 i = 0; i < static_cast(MemoryCardType::Count); i++) @@ -331,6 +334,18 @@ void GamePropertiesDialog::populateGameSettings() QSignalBlocker sb(m_ui.userControllerType2); m_ui.userControllerType2->setCurrentIndex(static_cast(gs.controller_2_type.value()) + 1); } + if (!gs.input_profile_name.empty()) + { + QSignalBlocker sb(m_ui.userInputProfile); + int index = m_ui.userInputProfile->findText(QString::fromStdString(gs.input_profile_name)); + if (index < 0) + { + index = m_ui.userInputProfile->count(); + m_ui.userInputProfile->addItem(QString::fromStdString(gs.input_profile_name)); + } + + m_ui.userInputProfile->setCurrentIndex(index); + } if (gs.memory_card_1_type.has_value()) { @@ -478,6 +493,14 @@ void GamePropertiesDialog::connectUi() saveGameSettings(); }); + connect(m_ui.userInputProfile, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.input_profile_name = {}; + else + m_game_settings.input_profile_name = m_ui.userInputProfile->itemText(index).toStdString(); + saveGameSettings(); + }); + connect(m_ui.userMemoryCard1Type, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { if (index <= 0) m_game_settings.memory_card_1_type.reset(); diff --git a/src/duckstation-qt/gamepropertiesdialog.ui b/src/duckstation-qt/gamepropertiesdialog.ui index 3de862a54..f34b57bad 100644 --- a/src/duckstation-qt/gamepropertiesdialog.ui +++ b/src/duckstation-qt/gamepropertiesdialog.ui @@ -447,6 +447,16 @@ + + + + Input Profile For Bindings: + + + + + + diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index d6c3a2216..5579d36de 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -977,7 +977,10 @@ bool CommonHostInterface::HandleHostMouseEvent(HostMouseButton button, bool pres void CommonHostInterface::UpdateInputMap(SettingsInterface& si) { ClearInputMap(); - UpdateControllerInputMap(si); + + if (!UpdateControllerInputMapFromGameSettings()) + UpdateControllerInputMap(si); + UpdateHotkeyInputMap(si); } @@ -1677,6 +1680,19 @@ void CommonHostInterface::FindInputProfiles(const std::string& base_path, InputP } } +std::string CommonHostInterface::GetInputProfilePath(const char* name) const +{ + std::string path = GetUserDirectoryRelativePath("inputprofiles" FS_OSPATH_SEPARATOR_STR "%s.ini", name); + if (FileSystem::FileExists(path.c_str())) + return path; + + path = GetProgramDirectoryRelativePath("inputprofiles" FS_OSPATH_SEPARATOR_STR "%s.ini", name); + if (FileSystem::FileExists(path.c_str())) + return path; + + return {}; +} + void CommonHostInterface::ClearAllControllerBindings(SettingsInterface& si) { for (u32 controller_index = 1; controller_index <= NUM_CONTROLLER_AND_CARD_PORTS; controller_index++) @@ -2282,6 +2298,35 @@ void CommonHostInterface::ApplyGameSettings(bool display_osd_messages) gs->ApplySettings(display_osd_messages); } +bool CommonHostInterface::UpdateControllerInputMapFromGameSettings() +{ + // this gets called while booting, so can't use valid + if (System::IsShutdown() || System::GetRunningCode().empty() || !g_settings.apply_game_settings) + return false; + + const GameSettings::Entry* gs = m_game_list->GetGameSettings(System::GetRunningPath(), System::GetRunningCode()); + if (!gs || gs->input_profile_name.empty()) + return false; + + std::string path = GetInputProfilePath(gs->input_profile_name.c_str()); + if (path.empty()) + { + AddFormattedOSDMessage(10.0f, TranslateString("OSDMessage", "Input profile '%s' cannot be found."), + gs->input_profile_name.c_str()); + return false; + } + + if (System::GetState() == System::State::Starting) + { + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Using input profile '%s'."), + gs->input_profile_name.c_str()); + } + + INISettingsInterface si(std::move(path)); + UpdateControllerInputMap(si); + return true; +} + std::string CommonHostInterface::GetCheatFileName() const { const std::string& title = System::GetRunningTitle(); diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index def300da2..e21a6b92d 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -233,6 +233,9 @@ protected: /// Returns a list of all input profiles. first - name, second - path InputProfileList GetInputProfileList() const; + /// Returns the path for an input profile. + std::string GetInputProfilePath(const char* name) const; + /// Applies the specified input profile. void ApplyInputProfile(const char* profile_path, SettingsInterface& si); @@ -330,6 +333,7 @@ private: void RegisterAudioHotkeys(); void FindInputProfiles(const std::string& base_path, InputProfileList* out_list) const; void UpdateControllerInputMap(SettingsInterface& si); + bool UpdateControllerInputMapFromGameSettings(); void UpdateHotkeyInputMap(SettingsInterface& si); void ClearAllControllerBindings(SettingsInterface& si); diff --git a/src/frontend-common/game_list.h b/src/frontend-common/game_list.h index 604df9bd2..b70f3aa4f 100644 --- a/src/frontend-common/game_list.h +++ b/src/frontend-common/game_list.h @@ -115,7 +115,7 @@ private: enum : u32 { GAME_LIST_CACHE_SIGNATURE = 0x45434C47, - GAME_LIST_CACHE_VERSION = 11 + GAME_LIST_CACHE_VERSION = 12 }; using DatabaseMap = std::unordered_map; diff --git a/src/frontend-common/game_settings.cpp b/src/frontend-common/game_settings.cpp index 5e8e30dd7..545183579 100644 --- a/src/frontend-common/game_settings.cpp +++ b/src/frontend-common/game_settings.cpp @@ -121,7 +121,7 @@ bool Entry::LoadFromStream(ByteStream* stream) !ReadOptionalFromStream(stream, &controller_2_type) || !ReadOptionalFromStream(stream, &memory_card_1_type) || !ReadOptionalFromStream(stream, &memory_card_2_type) || !ReadStringFromStream(stream, &memory_card_1_shared_path) || - !ReadStringFromStream(stream, &memory_card_2_shared_path)) + !ReadStringFromStream(stream, &memory_card_2_shared_path) || !ReadStringFromStream(stream, &input_profile_name)) { return false; } @@ -162,7 +162,7 @@ bool Entry::SaveToStream(ByteStream* stream) const WriteOptionalToStream(stream, gpu_pgxp) && WriteOptionalToStream(stream, controller_1_type) && WriteOptionalToStream(stream, controller_2_type) && WriteOptionalToStream(stream, memory_card_1_type) && WriteOptionalToStream(stream, memory_card_2_type) && WriteStringToStream(stream, memory_card_1_shared_path) && - WriteStringToStream(stream, memory_card_2_shared_path); + WriteStringToStream(stream, memory_card_2_shared_path) && WriteStringToStream(stream, input_profile_name); } static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA& ini) @@ -247,6 +247,9 @@ static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA cvalue = ini.GetValue(section, "MemoryCard2SharedPath"); if (cvalue) entry->memory_card_2_shared_path = cvalue; + cvalue = ini.GetValue(section, "InputProfileName"); + if (cvalue) + entry->input_profile_name = cvalue; } static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA& ini) @@ -316,6 +319,8 @@ static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA ini.SetValue(section, "MemoryCard1SharedPath", entry.memory_card_1_shared_path.c_str()); if (!entry.memory_card_2_shared_path.empty()) ini.SetValue(section, "MemoryCard2SharedPath", entry.memory_card_2_shared_path.c_str()); + if (!entry.input_profile_name.empty()) + ini.SetValue(section, "InputProfileName", entry.input_profile_name.c_str()); } Database::Database() = default; diff --git a/src/frontend-common/game_settings.h b/src/frontend-common/game_settings.h index 23e88ffa3..46628b5aa 100644 --- a/src/frontend-common/game_settings.h +++ b/src/frontend-common/game_settings.h @@ -61,6 +61,7 @@ struct Entry std::optional memory_card_2_type; std::string memory_card_1_shared_path; std::string memory_card_2_shared_path; + std::string input_profile_name; ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast(trait)]; } ALWAYS_INLINE void AddTrait(Trait trait) { traits[static_cast(trait)] = true; }