mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-08 15:24:33 +00:00
Qt: Support populating game list entry at runtime
Re-enables support for modifying cheats when running direct from disc.
This commit is contained in:
@@ -1613,7 +1613,22 @@ bool FullscreenUI::SwitchToGameSettingsForPath(const std::string& path, Settings
|
||||
const GameList::Entry* entry = !path.empty() ? GameList::GetEntryForPath(path) : nullptr;
|
||||
if (!entry || entry->serial.empty())
|
||||
{
|
||||
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Game properties is only available for scanned games."));
|
||||
Host::RunOnCPUThread([page]() {
|
||||
Error error;
|
||||
GameList::Entry dynamic_entry;
|
||||
if (System::PopulateGameListEntryFromCurrentGame(&dynamic_entry, &error))
|
||||
{
|
||||
GPUThread::RunOnThread([dynamic_entry = std::move(dynamic_entry), page]() {
|
||||
if (IsInitialized())
|
||||
SwitchToGameSettings(&dynamic_entry, page);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowToast(OSDMessageType::Info, {}, error.TakeDescription());
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1992,6 +2007,13 @@ void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles)
|
||||
BeginMenuButtons();
|
||||
ResetFocusHere();
|
||||
|
||||
if (!s_settings_locals.game_settings_entry || s_settings_locals.game_settings_entry->is_runtime_populated)
|
||||
{
|
||||
MenuButton(FSUI_ICONVSTR(ICON_EMOJI_WARNING,
|
||||
"This game was not scanned by DuckStation. Some functionality is not available."),
|
||||
{}, false);
|
||||
}
|
||||
|
||||
MenuHeading(FSUI_VSTR("Details"));
|
||||
|
||||
if (s_settings_locals.game_settings_entry)
|
||||
@@ -2034,10 +2056,6 @@ void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles)
|
||||
CopyTextToClipboard(FSUI_STR("Game path copied to clipboard."), s_settings_locals.game_settings_entry->path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuButton(FSUI_ICONVSTR(ICON_FA_BAN, "Details unavailable for game not scanned in game list."), "");
|
||||
}
|
||||
|
||||
MenuHeading(FSUI_VSTR("Options"));
|
||||
|
||||
|
||||
@@ -205,6 +205,7 @@ TRANSLATE_NOOP("FullscreenUI", "Copies the global controller configuration to th
|
||||
TRANSLATE_NOOP("FullscreenUI", "Copy Global Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Copy Settings");
|
||||
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");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Cover Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Cover set.");
|
||||
@@ -233,7 +234,6 @@ TRANSLATE_NOOP("FullscreenUI", "Depth Test Transparent Polygons");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Desktop Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Destination Alpha Blending");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Details");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Details unavailable for game not scanned in game list.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitor are.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how long action confirmation messages are displayed on screen.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how long error messages are displayed on screen.");
|
||||
@@ -380,7 +380,6 @@ TRANSLATE_NOOP("FullscreenUI", "Game Quick Save");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game Slot {0}##game_slot_{0}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game compatibility rating copied to clipboard.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game path copied to clipboard.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game properties is only available for scanned games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game region copied to clipboard.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game serial copied to clipboard.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game settings have been cleared for '{}'.");
|
||||
@@ -754,6 +753,7 @@ TRANSLATE_NOOP("FullscreenUI", "The SDL input source supports most controllers."
|
||||
TRANSLATE_NOOP("FullscreenUI", "The audio backend determines how frames produced by the emulator are submitted to the host.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "The selected memory card image will be used in shared mode for this slot.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Theme");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This game was not scanned by DuckStation. Some functionality is not available.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Threaded Rendering");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Time Played");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Time Played: ");
|
||||
|
||||
@@ -401,6 +401,7 @@ void GameList::MakeInvalidEntry(Entry* entry)
|
||||
entry->disc_set_member = false;
|
||||
entry->has_custom_title = false;
|
||||
entry->has_custom_region = false;
|
||||
entry->is_runtime_populated = false;
|
||||
entry->custom_language = GameDatabase::Language::MaxCount;
|
||||
entry->path = {};
|
||||
entry->serial = {};
|
||||
|
||||
@@ -38,6 +38,7 @@ struct Entry
|
||||
bool disc_set_member = false;
|
||||
bool has_custom_title = false;
|
||||
bool has_custom_region = false;
|
||||
bool is_runtime_populated = false;
|
||||
GameDatabase::Language custom_language = GameDatabase::Language::MaxCount;
|
||||
|
||||
std::string path;
|
||||
@@ -96,8 +97,7 @@ const char* GetEntryTypeDisplayName(EntryType type);
|
||||
|
||||
bool IsScannableFilename(std::string_view path);
|
||||
|
||||
/// Populates a game list entry struct with information from the iso/elf.
|
||||
/// Do *not* call while the system is running, it will mess with CDVD state.
|
||||
/// Populates a game list entry struct with information from the specified path.
|
||||
bool PopulateEntryFromPath(const std::string& path, Entry* entry);
|
||||
|
||||
// Game list access. It's the caller's responsibility to hold the lock while manipulating the entry in any way.
|
||||
|
||||
@@ -679,11 +679,6 @@ ConsoleRegion System::GetRegion()
|
||||
return s_state.region;
|
||||
}
|
||||
|
||||
DiscRegion System::GetDiscRegion()
|
||||
{
|
||||
return CDROM::GetDiscRegion();
|
||||
}
|
||||
|
||||
bool System::IsPALRegion()
|
||||
{
|
||||
return s_state.region == ConsoleRegion::PAL;
|
||||
@@ -4091,19 +4086,6 @@ bool System::DumpSPURAM(std::string path, Error* error)
|
||||
return FileSystem::WriteAtomicRenamedFile(std::move(path), SPU::GetRAM(), error);
|
||||
}
|
||||
|
||||
bool System::HasMedia()
|
||||
{
|
||||
return CDROM::HasMedia();
|
||||
}
|
||||
|
||||
std::string System::GetMediaPath()
|
||||
{
|
||||
if (!CDROM::HasMedia())
|
||||
return {};
|
||||
|
||||
return CDROM::GetMediaPath();
|
||||
}
|
||||
|
||||
bool System::InsertMedia(const char* path)
|
||||
{
|
||||
if (IsGPUDumpPath(path)) [[unlikely]]
|
||||
@@ -4267,6 +4249,40 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
|
||||
s_state.running_game_hash);
|
||||
}
|
||||
|
||||
bool System::PopulateGameListEntryFromCurrentGame(GameList::Entry* entry, Error* error)
|
||||
{
|
||||
if (!IsValid() || IsReplayingGPUDump() || IsPsfPath(s_state.running_game_path))
|
||||
{
|
||||
Error::SetStringView(error, TRANSLATE_SV("System", "No valid game is running."));
|
||||
return false;
|
||||
}
|
||||
|
||||
entry->path = s_state.running_game_path;
|
||||
entry->serial = s_state.running_game_serial;
|
||||
entry->title = s_state.running_game_title;
|
||||
entry->dbentry = s_state.running_game_entry;
|
||||
entry->hash = s_state.running_game_hash;
|
||||
entry->has_custom_title = s_state.running_game_custom_title;
|
||||
|
||||
if (CDROM::HasMedia())
|
||||
{
|
||||
entry->type = CDROM::GetMedia()->HasSubImages() ? GameList::EntryType::Playlist : GameList::EntryType::Disc;
|
||||
entry->region = CDROM::GetDiscRegion();
|
||||
}
|
||||
else
|
||||
{
|
||||
entry->type = GameList::EntryType::PSExe;
|
||||
entry->region = ((s_state.region == ConsoleRegion::NTSC_U) ?
|
||||
DiscRegion::NTSC_U :
|
||||
((s_state.region == ConsoleRegion::NTSC_J) ? DiscRegion::NTSC_J : DiscRegion::PAL));
|
||||
}
|
||||
|
||||
entry->achievements_game_id = Achievements::GetGameID();
|
||||
entry->is_runtime_populated = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool System::CheckForRequiredSubQ(Error* error)
|
||||
{
|
||||
if (IsReplayingGPUDump() || !s_state.running_game_entry ||
|
||||
|
||||
@@ -34,6 +34,9 @@ class MediaCapture;
|
||||
namespace GameDatabase {
|
||||
struct Entry;
|
||||
}
|
||||
namespace GameList {
|
||||
struct Entry;
|
||||
}
|
||||
|
||||
struct SystemBootParameters
|
||||
{
|
||||
@@ -186,7 +189,6 @@ void CancelPendingStartup();
|
||||
void InterruptExecution();
|
||||
|
||||
ConsoleRegion GetRegion();
|
||||
DiscRegion GetDiscRegion();
|
||||
bool IsPALRegion();
|
||||
|
||||
/// Taints - flags that are set on the system and only cleared on reset.
|
||||
@@ -238,6 +240,9 @@ BootMode GetBootMode();
|
||||
/// Returns the time elapsed in the current play session.
|
||||
u64 GetSessionPlayedTime();
|
||||
|
||||
/// Populates a game list entry struct with information from the currently-running game.
|
||||
bool PopulateGameListEntryFromCurrentGame(GameList::Entry* entry, Error* error);
|
||||
|
||||
void FormatLatencyStats(SmallStringBase& str);
|
||||
|
||||
/// Loads global settings (i.e. EmuConfig).
|
||||
@@ -323,8 +328,6 @@ bool DumpVRAM(std::string path, Error* error);
|
||||
/// Dumps sound RAM to a file.
|
||||
bool DumpSPURAM(std::string path, Error* error);
|
||||
|
||||
bool HasMedia();
|
||||
std::string GetMediaPath();
|
||||
bool InsertMedia(const char* path);
|
||||
void RemoveMedia();
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindo
|
||||
: m_dialog(dialog)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.revision->setVisible(false);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GameList::EntryType::MaxCount); i++)
|
||||
{
|
||||
@@ -236,7 +235,10 @@ void GameSummaryWidget::populateUi(const GameList::Entry* entry)
|
||||
m_ui.inputProfile->addItem(QString::fromStdString(name));
|
||||
|
||||
reloadGameSettings();
|
||||
populateTracksInfo();
|
||||
if (!entry->is_runtime_populated)
|
||||
populateTracksInfo();
|
||||
else
|
||||
disableWidgetsForRuntimeScannedEntry();
|
||||
}
|
||||
|
||||
void GameSummaryWidget::onSeparateDiscSettingsChanged(Qt::CheckState state)
|
||||
@@ -299,21 +301,6 @@ void GameSummaryWidget::onCustomLanguageChanged(int language)
|
||||
g_main_window->refreshGameListModel();
|
||||
}
|
||||
|
||||
void GameSummaryWidget::setRevisionText(const QString& text)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return;
|
||||
|
||||
if (m_ui.verifySpacer)
|
||||
{
|
||||
m_ui.verifyLayout->removeItem(m_ui.verifySpacer);
|
||||
delete m_ui.verifySpacer;
|
||||
m_ui.verifySpacer = nullptr;
|
||||
}
|
||||
m_ui.revision->setText(text);
|
||||
m_ui.revision->setVisible(true);
|
||||
}
|
||||
|
||||
static QString MSFToString(const CDImage::Position& position)
|
||||
{
|
||||
return QStringLiteral("%1:%2:%3")
|
||||
@@ -334,10 +321,10 @@ void GameSummaryWidget::populateTracksInfo()
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
setRevisionText(tr("%1 tracks covering %2 MB (%3 MB on disk)")
|
||||
.arg(image->GetTrackCount())
|
||||
.arg(((image->GetLBACount() * CDImage::RAW_SECTOR_SIZE) + 1048575) / 1048576)
|
||||
.arg((image->GetSizeOnDisk() + 1048575) / 1048576));
|
||||
m_ui.revision->setText(tr("%1 tracks covering %2 MB (%3 MB on disk)")
|
||||
.arg(image->GetTrackCount())
|
||||
.arg(((image->GetLBACount() * CDImage::RAW_SECTOR_SIZE) + 1048575) / 1048576)
|
||||
.arg((image->GetSizeOnDisk() + 1048575) / 1048576));
|
||||
|
||||
const u32 num_tracks = image->GetTrackCount();
|
||||
for (u32 track = 1; track <= num_tracks; track++)
|
||||
@@ -358,6 +345,20 @@ void GameSummaryWidget::populateTracksInfo()
|
||||
}
|
||||
}
|
||||
|
||||
void GameSummaryWidget::disableWidgetsForRuntimeScannedEntry()
|
||||
{
|
||||
m_ui.tracks->setEnabled(false);
|
||||
m_ui.computeHashes->setEnabled(false);
|
||||
m_ui.title->setReadOnly(true);
|
||||
m_ui.restoreTitle->setEnabled(false);
|
||||
m_ui.region->setEnabled(false);
|
||||
m_ui.restoreRegion->setEnabled(false);
|
||||
m_ui.languages->setReadOnly(true);
|
||||
m_ui.customLanguage->setEnabled(false);
|
||||
m_ui.revision->setText(tr("This game was not scanned by DuckStation. Some functionality is not available."));
|
||||
m_ui.tracks->setVisible(false);
|
||||
}
|
||||
|
||||
void GameSummaryWidget::onCompatibilityCommentsClicked()
|
||||
{
|
||||
QDialog* const dlg = new QDialog(QtUtils::GetRootWidget(this));
|
||||
@@ -587,7 +588,7 @@ void GameSummaryWidget::processHashResults(const CDImageHasher::TrackHashes& tra
|
||||
}
|
||||
}
|
||||
|
||||
setRevisionText(text);
|
||||
m_ui.revision->setText(text);
|
||||
|
||||
// update in ui
|
||||
for (size_t i = 0; i < track_hashes.size(); i++)
|
||||
|
||||
@@ -34,9 +34,9 @@ private:
|
||||
void populateUi(const GameList::Entry* entry);
|
||||
void setCustomTitle(const std::string& text);
|
||||
void setCustomRegion(int region);
|
||||
void setRevisionText(const QString& text);
|
||||
|
||||
void populateTracksInfo();
|
||||
void disableWidgetsForRuntimeScannedEntry();
|
||||
|
||||
void onSeparateDiscSettingsChanged(Qt::CheckState state);
|
||||
void onCustomLanguageChanged(int language);
|
||||
|
||||
@@ -259,20 +259,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1" colspan="2">
|
||||
<layout class="QHBoxLayout" name="verifyLayout" stretch="0,1,0">
|
||||
<item>
|
||||
<spacer name="verifySpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<layout class="QHBoxLayout" name="verifyLayout" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="revision">
|
||||
<property name="minimumSize">
|
||||
@@ -341,6 +328,19 @@
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0" colspan="3">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@@ -2446,7 +2446,8 @@ void MainWindow::connectSignals()
|
||||
connect(m_ui.actionControllerProfiles, &QAction::triggered, this, &MainWindow::onSettingsControllerProfilesTriggered);
|
||||
connect(m_ui.actionViewToolbar, &QAction::triggered, this, &MainWindow::onViewToolbarActionTriggered);
|
||||
connect(m_ui.actionViewLockToolbar, &QAction::triggered, this, &MainWindow::onViewToolbarLockActionTriggered);
|
||||
connect(m_ui.actionViewSmallToolbarIcons, &QAction::triggered, this, &MainWindow::onViewToolbarSmallIconsActionTriggered);
|
||||
connect(m_ui.actionViewSmallToolbarIcons, &QAction::triggered, this,
|
||||
&MainWindow::onViewToolbarSmallIconsActionTriggered);
|
||||
connect(m_ui.actionViewToolbarLabels, &QAction::triggered, this, &MainWindow::onViewToolbarLabelsActionTriggered);
|
||||
connect(m_ui.actionViewToolbarLabelsBesideIcons, &QAction::triggered, this,
|
||||
&MainWindow::onViewToolbarLabelsBesideIconsActionTriggered);
|
||||
@@ -2725,21 +2726,18 @@ void MainWindow::openGamePropertiesForCurrentGame(const char* category /* = null
|
||||
return;
|
||||
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(s_current_game_path.toStdString());
|
||||
const std::string game_path = s_current_game_path.toStdString();
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(game_path);
|
||||
if (entry && entry->disc_set_member && !entry->dbentry->IsFirstDiscInSet() &&
|
||||
!System::ShouldUseSeparateDiscSettingsForSerial(entry->serial))
|
||||
{
|
||||
// show for first disc instead
|
||||
entry = GameList::GetFirstDiscSetMember(entry->dbentry->disc_set);
|
||||
}
|
||||
if (!entry)
|
||||
{
|
||||
QtUtils::AsyncMessageBox(this, QMessageBox::Critical, tr("Error"),
|
||||
tr("Game properties is only available for scanned games."));
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsWindow::openGamePropertiesDialog(entry, category);
|
||||
if (entry)
|
||||
SettingsWindow::openGamePropertiesDialog(entry, category);
|
||||
else
|
||||
g_emu_thread->openGamePropertiesForCurrentGame(category ? QString::fromUtf8(category) : QString());
|
||||
}
|
||||
|
||||
ControllerSettingsWindow* MainWindow::getControllerSettingsWindow()
|
||||
|
||||
@@ -1360,6 +1360,29 @@ void EmuThread::startControllerTest()
|
||||
});
|
||||
}
|
||||
|
||||
void EmuThread::openGamePropertiesForCurrentGame(const QString& category)
|
||||
{
|
||||
if (!isCurrentThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, &EmuThread::openGamePropertiesForCurrentGame, Qt::QueuedConnection, category);
|
||||
return;
|
||||
}
|
||||
|
||||
Error error;
|
||||
GameList::Entry dynamic_entry;
|
||||
if (System::PopulateGameListEntryFromCurrentGame(&dynamic_entry, &error))
|
||||
{
|
||||
Host::RunOnUIThread([dynamic_entry = std::move(dynamic_entry), category]() {
|
||||
SettingsWindow::openGamePropertiesDialog(&dynamic_entry,
|
||||
category.isEmpty() ? nullptr : category.toUtf8().constData());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
emit errorReported(tr("Error"), QString::fromStdString(error.GetDescription()));
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::runOnEmuThread(const std::function<void()>& callback)
|
||||
{
|
||||
callback();
|
||||
@@ -3365,10 +3388,8 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// When running in batch mode, ensure game list is loaded, but don't scan for any new files.
|
||||
if (!autoboot)
|
||||
if (!s_state.batch_mode)
|
||||
g_main_window->refreshGameList(false);
|
||||
else
|
||||
GameList::Refresh(false, true);
|
||||
|
||||
// Don't bother showing the window in no-gui mode.
|
||||
if (!s_state.nogui_mode)
|
||||
|
||||
@@ -181,6 +181,7 @@ public:
|
||||
void reloadTextureReplacements();
|
||||
void captureGPUFrameDump();
|
||||
void startControllerTest();
|
||||
void openGamePropertiesForCurrentGame(const QString& category = {});
|
||||
void setGPUThreadRunIdle(bool active);
|
||||
void updateFullscreenUITheme();
|
||||
void runOnEmuThread(const std::function<void()>& callback);
|
||||
|
||||
Reference in New Issue
Block a user