From 4616c8513a42c3e1fc482dbe76f540e0faf63e2d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 4 Jul 2026 13:04:52 +1000 Subject: [PATCH] FullscreenUI: Add custom title/disc set title/serial editing --- src/core/fullscreenui_settings.cpp | 194 +++++++++++++++++++++++++++-- src/core/fullscreenui_strings.h | 24 ++++ 2 files changed, 211 insertions(+), 7 deletions(-) diff --git a/src/core/fullscreenui_settings.cpp b/src/core/fullscreenui_settings.cpp index 045c40954..d278503f2 100644 --- a/src/core/fullscreenui_settings.cpp +++ b/src/core/fullscreenui_settings.cpp @@ -101,6 +101,11 @@ static void ProcessImageVerificationResults(std::string path, GameDatabase::Trac static void DrawSummarySettingsPage(bool show_localized_titles); static void DrawSummarySettingsTrackList(); +static void OpenGameTitleActions(const std::string_view& path, const std::string_view& title, bool has_custom_title, + bool is_disc_set_title); +static void OpenGameSerialActions(const std::string& path, const std::string& serial, bool has_custom_serial); +static void SaveCustomTitle(const std::string& path, const std::string& title); +static void SaveCustomSerial(const std::string& path, const std::string& serial); static void DrawInterfaceSettingsPage(); static void DrawGameListSettingsPage(); static void DrawBIOSSettingsPage(); @@ -1720,7 +1725,7 @@ bool FullscreenUI::SwitchToGameSettingsForPath(const std::string& path, Settings const GameList::Entry* entry = !path.empty() ? GameList::GetEntryForPath(path) : nullptr; // playlists will always contain the first disc's serial, so use the current game instead - if (!entry || entry->serial.empty() || entry->type == GameList::EntryType::Playlist) + if (!entry || entry->serial.empty() || (entry->type == GameList::EntryType::Playlist && VideoThread::HasGPUBackend())) { Host::RunOnCoreThread([page]() { Error error; @@ -2142,6 +2147,8 @@ void FullscreenUI::DrawSettingsWindow() void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles) { + SmallString sstr; + BeginMenuButtons(); ResetFocusHere(); @@ -2156,14 +2163,68 @@ void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles) if (s_settings_locals.game_settings_entry) { - if (MenuButton(FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Title"), - s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles), true)) + const bool allow_customization = !s_settings_locals.game_settings_entry->is_runtime_populated; + const std::string_view title = s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles); + if (MenuButton(FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Title"), title, true)) { - CopyTextToClipboard(FSUI_STR("Game title copied to clipboard."), - s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles)); + if (allow_customization) + { + OpenGameTitleActions(s_settings_locals.game_settings_entry->path, title, + s_settings_locals.game_settings_entry->has_custom_title, false); + } + else + { + CopyTextToClipboard(FSUI_STR("Game title copied to clipboard."), title); + } } - if (MenuButton(FSUI_ICONVSTR(ICON_FA_PAGER, "Serial"), s_settings_locals.game_settings_entry->serial, true)) - CopyTextToClipboard(FSUI_STR("Game serial copied to clipboard."), s_settings_locals.game_settings_entry->serial); + + if (s_settings_locals.game_settings_entry->disc_set_member) + { + const std::string_view disc_set_name = s_settings_locals.game_settings_entry->GetDiscSetEntry()->GetSaveTitle(); + std::string disc_set_title; + bool has_custom_disc_set_title = false; + { + const auto lock = GameList::GetLock(); + const GameList::Entry* const disc_set_entry = GameList::GetEntryForPath(disc_set_name); + if (disc_set_entry) + { + disc_set_title = disc_set_entry->GetDisplayTitle(show_localized_titles); + has_custom_disc_set_title = disc_set_entry->has_custom_title; + } + } + + if (!disc_set_title.empty() && + MenuButton(FSUI_ICONVSTR(ICON_FA_LAYER_GROUP, "Disc Set Title"), disc_set_title, true)) + { + if (allow_customization) + OpenGameTitleActions(disc_set_name, disc_set_title, has_custom_disc_set_title, true); + else + CopyTextToClipboard(FSUI_STR("Disc set title copied to clipboard."), disc_set_title); + } + } + + std::string_view visible_serial = s_settings_locals.game_settings_entry->serial; + if (s_settings_locals.game_settings_entry->has_custom_serial) + { + sstr.format(FSUI_FSTR("{} (Custom)"), s_settings_locals.game_settings_entry->serial); + visible_serial = sstr; + } + + if (MenuButton(FSUI_ICONVSTR(ICON_FA_PAGER, "Serial"), visible_serial, true)) + { + if (allow_customization) + { + OpenGameSerialActions(s_settings_locals.game_settings_entry->path, + s_settings_locals.game_settings_entry->serial, + s_settings_locals.game_settings_entry->has_custom_serial); + } + else + { + CopyTextToClipboard(FSUI_STR("Game serial copied to clipboard."), + s_settings_locals.game_settings_entry->serial); + } + } + if (MenuButton(FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Type"), GameList::GetEntryTypeDisplayName(s_settings_locals.game_settings_entry->type), true)) { @@ -2226,6 +2287,125 @@ void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles) EndMenuButtons(); } +void FullscreenUI::OpenGameTitleActions(const std::string_view& path, const std::string_view& title, + bool has_custom_title, bool is_disc_set_title) +{ + ChoiceDialogOptions options; + options.reserve(has_custom_title ? 4 : 3); + options.emplace_back(FSUI_ICONSTR(ICON_FA_FILE_PEN, "Change Title"), false); + if (has_custom_title) + options.emplace_back(FSUI_ICONSTR(ICON_FA_ARROW_ROTATE_LEFT, "Reset Title"), false); + options.emplace_back(FSUI_ICONSTR(ICON_FA_COPY, "Copy Title"), false); + options.emplace_back(FSUI_ICONSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false); + + OpenChoiceDialog(is_disc_set_title ? FSUI_ICONVSTR(ICON_FA_LAYER_GROUP, "Disc Set Title") : + FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Game Title"), + false, std::move(options), + [path = std::string(path), title = std::string(title), has_custom_title, + is_disc_set_title](s32 index, const std::string&, bool) mutable { + if (index < 0) + return; + + if (index == 0) + { + OpenInputStringDialog(is_disc_set_title ? + FSUI_ICONSTR(ICON_FA_LAYER_GROUP, "Change Disc Set Title") : + FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Change Game Title"), + is_disc_set_title ? FSUI_STR("Enter a new title for this disc set.") : + FSUI_STR("Enter a new title for this game."), + FSUI_STR("Title:"), FSUI_ICONSTR(ICON_FA_CHECK, "Save"), title, + [path = std::move(path)](std::string new_title) mutable { + if (!new_title.empty()) + SaveCustomTitle(std::move(path), std::move(new_title)); + }); + } + else if (has_custom_title && index == 1) + { + SaveCustomTitle(std::move(path), {}); + } + else if (index == (has_custom_title ? 2 : 1)) + { + CopyTextToClipboard(is_disc_set_title ? FSUI_STR("Disc set title copied to clipboard.") : + FSUI_STR("Game title copied to clipboard."), + title); + } + }); +} + +void FullscreenUI::OpenGameSerialActions(const std::string& path, const std::string& serial, bool has_custom_serial) +{ + ChoiceDialogOptions options; + options.reserve(has_custom_serial ? 4 : 3); + options.emplace_back(FSUI_ICONSTR(ICON_FA_FILE_PEN, "Change Serial"), false); + if (has_custom_serial) + options.emplace_back(FSUI_ICONSTR(ICON_FA_ARROW_ROTATE_LEFT, "Reset Serial"), false); + options.emplace_back(FSUI_ICONSTR(ICON_FA_COPY, "Copy Serial"), false); + options.emplace_back(FSUI_ICONSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false); + + OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_PAGER, "Game Serial"), false, std::move(options), + [path, serial, has_custom_serial](s32 index, const std::string&, bool) mutable { + if (index < 0) + return; + + if (index == 0) + { + OpenInputStringDialog(FSUI_ICONSTR(ICON_FA_PAGER, "Change Game Serial"), + FSUI_STR("Enter a new serial for this game."), FSUI_STR("Serial:"), + FSUI_ICONSTR(ICON_FA_CHECK, "Save"), serial, + [path = std::move(path)](std::string new_serial) mutable { + StringUtil::StripWhitespace(&new_serial); + if (!new_serial.empty()) + SaveCustomSerial(std::move(path), std::move(new_serial)); + }); + } + else if (has_custom_serial && index == 1) + { + SaveCustomSerial(std::move(path), {}); + } + else if (index == (has_custom_serial ? 2 : 1)) + { + CopyTextToClipboard(FSUI_STR("Game serial copied to clipboard."), serial); + } + }); +} + +void FullscreenUI::SaveCustomTitle(const std::string& path, const std::string& title) +{ + if (!GameList::SaveCustomTitleForPath(path, title)) + { + ShowToast(OSDMessageType::Error, {}, FSUI_STR("Failed to save custom title.")); + return; + } + + if (!s_settings_locals.game_settings_entry || s_settings_locals.game_settings_entry->path != path) + return; + + const auto lock = GameList::GetLock(); + const GameList::Entry* const entry = GameList::GetEntryForPath(path); + if (entry) + { + SwitchToGameSettings(entry, SettingsPage::Summary); + ShowToast(OSDMessageType::Info, {}, FSUI_STR("Game title updated.")); + } +} + +void FullscreenUI::SaveCustomSerial(const std::string& path, const std::string& serial) +{ + if (!GameList::SaveCustomSerialForPath(path, serial)) + { + ShowToast(OSDMessageType::Error, {}, FSUI_STR("Failed to save custom serial.")); + return; + } + + const auto lock = GameList::GetLock(); + const GameList::Entry* const entry = GameList::GetEntryForPath(path); + if (entry) + { + SwitchToGameSettings(entry, SettingsPage::Summary); + ShowToast(OSDMessageType::Info, {}, FSUI_STR("Game serial updated.")); + } +} + void FullscreenUI::DrawSummarySettingsTrackList() { DebugAssert(!s_settings_locals.image_track_list.empty()); diff --git a/src/core/fullscreenui_strings.h b/src/core/fullscreenui_strings.h index 0676b6a19..a951805aa 100644 --- a/src/core/fullscreenui_strings.h +++ b/src/core/fullscreenui_strings.h @@ -143,8 +143,13 @@ TRANSLATE_NOOP("FullscreenUI", "Cancel"); TRANSLATE_NOOP("FullscreenUI", "Capture"); TRANSLATE_NOOP("FullscreenUI", "Challenge Indicators"); TRANSLATE_NOOP("FullscreenUI", "Change Disc"); +TRANSLATE_NOOP("FullscreenUI", "Change Disc Set Title"); +TRANSLATE_NOOP("FullscreenUI", "Change Game Serial"); +TRANSLATE_NOOP("FullscreenUI", "Change Game Title"); TRANSLATE_NOOP("FullscreenUI", "Change Page"); TRANSLATE_NOOP("FullscreenUI", "Change Selection"); +TRANSLATE_NOOP("FullscreenUI", "Change Serial"); +TRANSLATE_NOOP("FullscreenUI", "Change Title"); TRANSLATE_NOOP("FullscreenUI", "Change View"); TRANSLATE_NOOP("FullscreenUI", "Changes settings for the application."); TRANSLATE_NOOP("FullscreenUI", "Changes the aspect ratio used to display the console's output to the screen."); @@ -185,7 +190,9 @@ TRANSLATE_NOOP("FullscreenUI", "Controls the volume of the audio played on the h TRANSLATE_NOOP("FullscreenUI", "Copies the current global settings to this game."); TRANSLATE_NOOP("FullscreenUI", "Copies the global controller configuration to this game."); TRANSLATE_NOOP("FullscreenUI", "Copy Global Settings"); +TRANSLATE_NOOP("FullscreenUI", "Copy Serial"); TRANSLATE_NOOP("FullscreenUI", "Copy Settings"); +TRANSLATE_NOOP("FullscreenUI", "Copy Title"); TRANSLATE_NOOP("FullscreenUI", "Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it."); TRANSLATE_NOOP("FullscreenUI", "Cover Download Error"); TRANSLATE_NOOP("FullscreenUI", "Cover Downloader"); @@ -265,6 +272,8 @@ TRANSLATE_NOOP("FullscreenUI", "Disabled"); TRANSLATE_NOOP("FullscreenUI", "Disables texture emulation in the GPU, forcing all primitives to only show vertex colours."); TRANSLATE_NOOP("FullscreenUI", "Disables vertex lighting in the GPU, forcing all primitives to only show raw texture colours."); TRANSLATE_NOOP("FullscreenUI", "Disc"); +TRANSLATE_NOOP("FullscreenUI", "Disc Set Title"); +TRANSLATE_NOOP("FullscreenUI", "Disc set title copied to clipboard."); TRANSLATE_NOOP("FullscreenUI", "Discord Server"); TRANSLATE_NOOP("FullscreenUI", "Display Area"); TRANSLATE_NOOP("FullscreenUI", "Displays only the game title in the list, instead of the title and serial/file name."); @@ -332,6 +341,9 @@ TRANSLATE_NOOP("FullscreenUI", "Enables the replacement of background textures i TRANSLATE_NOOP("FullscreenUI", "Encore Mode"); TRANSLATE_NOOP("FullscreenUI", "Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, such as GSync/FreeSync. Disable if you are having speed or sound issues."); TRANSLATE_NOOP("FullscreenUI", "Enter Value"); +TRANSLATE_NOOP("FullscreenUI", "Enter a new serial for this game."); +TRANSLATE_NOOP("FullscreenUI", "Enter a new title for this disc set."); +TRANSLATE_NOOP("FullscreenUI", "Enter a new title for this game."); TRANSLATE_NOOP("FullscreenUI", "Enter the name of the controller preset you wish to create."); TRANSLATE_NOOP("FullscreenUI", "Error"); TRANSLATE_NOOP("FullscreenUI", "Error Message Duration"); @@ -351,6 +363,8 @@ TRANSLATE_NOOP("FullscreenUI", "Failed to load '{}'."); TRANSLATE_NOOP("FullscreenUI", "Failed to load resume save state info."); TRANSLATE_NOOP("FullscreenUI", "Failed to load shader {}. It may be invalid.\nError was:"); TRANSLATE_NOOP("FullscreenUI", "Failed to save controller preset '{}'."); +TRANSLATE_NOOP("FullscreenUI", "Failed to save custom serial."); +TRANSLATE_NOOP("FullscreenUI", "Failed to save custom title."); TRANSLATE_NOOP("FullscreenUI", "Fast Boot"); TRANSLATE_NOOP("FullscreenUI", "Fast Forward Boot"); TRANSLATE_NOOP("FullscreenUI", "Fast Forward Memory Card Access"); @@ -382,14 +396,18 @@ TRANSLATE_NOOP("FullscreenUI", "Game List Settings"); TRANSLATE_NOOP("FullscreenUI", "Game Patches"); TRANSLATE_NOOP("FullscreenUI", "Game Properties"); TRANSLATE_NOOP("FullscreenUI", "Game Quick Save"); +TRANSLATE_NOOP("FullscreenUI", "Game Serial"); TRANSLATE_NOOP("FullscreenUI", "Game Slot {0}##game_slot_{0}"); +TRANSLATE_NOOP("FullscreenUI", "Game Title"); TRANSLATE_NOOP("FullscreenUI", "Game compatibility rating copied to clipboard."); TRANSLATE_NOOP("FullscreenUI", "Game path copied to clipboard."); TRANSLATE_NOOP("FullscreenUI", "Game region copied to clipboard."); TRANSLATE_NOOP("FullscreenUI", "Game serial copied to clipboard."); +TRANSLATE_NOOP("FullscreenUI", "Game serial updated."); TRANSLATE_NOOP("FullscreenUI", "Game settings have been cleared for '{}'."); TRANSLATE_NOOP("FullscreenUI", "Game settings initialized with global settings for '{}'."); TRANSLATE_NOOP("FullscreenUI", "Game title copied to clipboard."); +TRANSLATE_NOOP("FullscreenUI", "Game title updated."); TRANSLATE_NOOP("FullscreenUI", "Game type copied to clipboard."); TRANSLATE_NOOP("FullscreenUI", "Gamepad Button Type"); TRANSLATE_NOOP("FullscreenUI", "Genre: "); @@ -613,7 +631,9 @@ TRANSLATE_NOOP("FullscreenUI", "Rescan All Games"); TRANSLATE_NOOP("FullscreenUI", "Reset"); TRANSLATE_NOOP("FullscreenUI", "Reset Controller Settings"); TRANSLATE_NOOP("FullscreenUI", "Reset Play Time"); +TRANSLATE_NOOP("FullscreenUI", "Reset Serial"); TRANSLATE_NOOP("FullscreenUI", "Reset Settings"); +TRANSLATE_NOOP("FullscreenUI", "Reset Title"); TRANSLATE_NOOP("FullscreenUI", "Reset To Default"); TRANSLATE_NOOP("FullscreenUI", "Resets all configuration to defaults (including bindings)."); TRANSLATE_NOOP("FullscreenUI", "Resets all settings to the defaults."); @@ -641,6 +661,7 @@ TRANSLATE_NOOP("FullscreenUI", "Runs the software renderer in parallel for VRAM TRANSLATE_NOOP("FullscreenUI", "SDL DualSense Player LED"); TRANSLATE_NOOP("FullscreenUI", "SDL DualShock 4 / DualSense Enhanced Mode"); TRANSLATE_NOOP("FullscreenUI", "Safe Mode"); +TRANSLATE_NOOP("FullscreenUI", "Save"); TRANSLATE_NOOP("FullscreenUI", "Save Controller Preset"); TRANSLATE_NOOP("FullscreenUI", "Save Locations"); TRANSLATE_NOOP("FullscreenUI", "Save Preset"); @@ -698,6 +719,7 @@ TRANSLATE_NOOP("FullscreenUI", "Selects the type of emulated controller for this TRANSLATE_NOOP("FullscreenUI", "Selects the view that the game list will open to."); TRANSLATE_NOOP("FullscreenUI", "Selects which location on the screen messages are displayed."); TRANSLATE_NOOP("FullscreenUI", "Serial"); +TRANSLATE_NOOP("FullscreenUI", "Serial:"); TRANSLATE_NOOP("FullscreenUI", "Session: {}"); TRANSLATE_NOOP("FullscreenUI", "Set Cover Image"); TRANSLATE_NOOP("FullscreenUI", "Set Input Binding"); @@ -812,6 +834,7 @@ TRANSLATE_NOOP("FullscreenUI", "Time Played"); TRANSLATE_NOOP("FullscreenUI", "Time Played: "); TRANSLATE_NOOP("FullscreenUI", "Timing out in {:.0f} seconds..."); TRANSLATE_NOOP("FullscreenUI", "Title"); +TRANSLATE_NOOP("FullscreenUI", "Title:"); TRANSLATE_NOOP("FullscreenUI", "To use achievements, please log in with your retroachievements.org account."); TRANSLATE_NOOP("FullscreenUI", "Toggle Analog"); TRANSLATE_NOOP("FullscreenUI", "Toggle Fast Forward"); @@ -900,6 +923,7 @@ TRANSLATE_NOOP("FullscreenUI", "change disc"); TRANSLATE_NOOP("FullscreenUI", "restart"); TRANSLATE_NOOP("FullscreenUI", "shut down"); TRANSLATE_NOOP("FullscreenUI", "{0} achievement unlocks have not been confirmed by the server. Continuing to {1} will result in loss of these unlocks. Once network connectivity has been re-established, these unlocks will be confirmed automatically.\n\nDo you want to {1} anyway?"); +TRANSLATE_NOOP("FullscreenUI", "{} (Custom)"); TRANSLATE_NOOP("FullscreenUI", "{} Frames"); TRANSLATE_NOOP("FullscreenUI", "{} deleted."); TRANSLATE_NOOP("FullscreenUI", "{} does not exist.");