|
|
|
|
@ -1141,51 +1141,47 @@ std::shared_ptr<SystemBootParameters> MainWindow::getSystemBootParameters(std::s
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<bool> MainWindow::promptForResumeState(const std::string& save_state_path)
|
|
|
|
|
bool MainWindow::openResumeStateDialog(const std::string& path, const std::string& serial)
|
|
|
|
|
{
|
|
|
|
|
System::FlushSaveStates();
|
|
|
|
|
|
|
|
|
|
FILESYSTEM_STAT_DATA sd;
|
|
|
|
|
std::string save_state_path = System::GetGameSaveStatePath(serial, -1);
|
|
|
|
|
if (save_state_path.empty() || !FileSystem::StatFile(save_state_path.c_str(), &sd))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
QMessageBox msgbox(this);
|
|
|
|
|
msgbox.setIcon(QMessageBox::Question);
|
|
|
|
|
msgbox.setWindowTitle(tr("Load Resume State"));
|
|
|
|
|
msgbox.setWindowModality(Qt::WindowModal);
|
|
|
|
|
msgbox.setText(
|
|
|
|
|
QMessageBox* msgbox = new QMessageBox(this);
|
|
|
|
|
msgbox->setIcon(QMessageBox::Question);
|
|
|
|
|
msgbox->setWindowTitle(tr("Load Resume State"));
|
|
|
|
|
msgbox->setWindowModality(Qt::WindowModal);
|
|
|
|
|
msgbox->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
|
msgbox->setText(
|
|
|
|
|
tr("A resume save state was found for this game, saved at:\n\n%1.\n\nDo you want to load this state, "
|
|
|
|
|
"or start from a fresh boot?")
|
|
|
|
|
.arg(QtHost::FormatNumber(Host::NumberFormatType::LongDateTime, static_cast<s64>(sd.ModificationTime))));
|
|
|
|
|
|
|
|
|
|
QPushButton* load = msgbox.addButton(tr("Load State"), QMessageBox::AcceptRole);
|
|
|
|
|
QPushButton* boot = msgbox.addButton(tr("Fresh Boot"), QMessageBox::RejectRole);
|
|
|
|
|
QPushButton* delboot = msgbox.addButton(tr("Delete And Boot"), QMessageBox::RejectRole);
|
|
|
|
|
msgbox.addButton(QMessageBox::Cancel);
|
|
|
|
|
msgbox.setDefaultButton(load);
|
|
|
|
|
msgbox.exec();
|
|
|
|
|
QPushButton* load = msgbox->addButton(tr("Load State"), QMessageBox::AcceptRole);
|
|
|
|
|
QPushButton* boot = msgbox->addButton(tr("Fresh Boot"), QMessageBox::RejectRole);
|
|
|
|
|
QPushButton* delboot = msgbox->addButton(tr("Delete And Boot"), QMessageBox::RejectRole);
|
|
|
|
|
msgbox->addButton(QMessageBox::Cancel);
|
|
|
|
|
msgbox->setDefaultButton(load);
|
|
|
|
|
|
|
|
|
|
QAbstractButton* clicked = msgbox.clickedButton();
|
|
|
|
|
if (load == clicked)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (boot == clicked)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else if (delboot == clicked)
|
|
|
|
|
{
|
|
|
|
|
connect(load, &QPushButton::clicked, [this, path, save_state_path]() mutable {
|
|
|
|
|
startFile(std::move(path), std::move(save_state_path), std::nullopt);
|
|
|
|
|
});
|
|
|
|
|
connect(boot, &QPushButton::clicked,
|
|
|
|
|
[this, path]() mutable { startFile(std::move(path), std::nullopt, std::nullopt); });
|
|
|
|
|
connect(delboot, &QPushButton::clicked, [this, path, save_state_path]() mutable {
|
|
|
|
|
if (!FileSystem::DeleteFile(save_state_path.c_str()))
|
|
|
|
|
{
|
|
|
|
|
QMessageBox::critical(this, tr("Error"),
|
|
|
|
|
tr("Failed to delete save state file '%1'.").arg(QString::fromStdString(save_state_path)));
|
|
|
|
|
}
|
|
|
|
|
startFile(std::move(path), std::nullopt, std::nullopt);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
msgbox->show();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::startFile(std::string path, std::optional<std::string> save_path, std::optional<bool> fast_boot)
|
|
|
|
|
@ -1198,34 +1194,24 @@ void MainWindow::startFile(std::string path, std::optional<std::string> save_pat
|
|
|
|
|
g_emu_thread->bootSystem(std::move(params));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::startFileOrChangeDisc(const QString& path)
|
|
|
|
|
void MainWindow::startFileOrChangeDisc(const QString& qpath)
|
|
|
|
|
{
|
|
|
|
|
if (s_system_valid)
|
|
|
|
|
{
|
|
|
|
|
// this is a disc change
|
|
|
|
|
promptForDiscChange(path);
|
|
|
|
|
promptForDiscChange(qpath);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// try to find the serial for the game
|
|
|
|
|
std::string path_str(path.toStdString());
|
|
|
|
|
std::string serial(GameDatabase::GetSerialForPath(path_str.c_str()));
|
|
|
|
|
std::optional<std::string> save_path;
|
|
|
|
|
if (!serial.empty())
|
|
|
|
|
std::string path = qpath.toStdString();
|
|
|
|
|
{
|
|
|
|
|
std::string resume_path(System::GetGameSaveStatePath(serial.c_str(), -1));
|
|
|
|
|
std::optional<bool> resume = promptForResumeState(resume_path);
|
|
|
|
|
if (!resume.has_value())
|
|
|
|
|
{
|
|
|
|
|
// cancelled
|
|
|
|
|
const auto lock = GameList::GetLock();
|
|
|
|
|
const GameList::Entry* entry = GameList::GetEntryForPath(path);
|
|
|
|
|
if (entry && !entry->serial.empty() && openResumeStateDialog(entry->path, entry->serial))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (resume.value())
|
|
|
|
|
save_path = std::move(resume_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// only resume if the option is enabled, and we have one for this game
|
|
|
|
|
startFile(std::move(path_str), std::move(save_path), std::nullopt);
|
|
|
|
|
startFile(std::move(path), std::nullopt, std::nullopt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::promptForDiscChange(const QString& path)
|
|
|
|
|
@ -1557,22 +1543,11 @@ void MainWindow::onGameListEntryActivated()
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<std::string> save_path;
|
|
|
|
|
if (!entry->serial.empty())
|
|
|
|
|
{
|
|
|
|
|
std::string resume_path(System::GetGameSaveStatePath(entry->serial.c_str(), -1));
|
|
|
|
|
std::optional<bool> resume = promptForResumeState(resume_path);
|
|
|
|
|
if (!resume.has_value())
|
|
|
|
|
{
|
|
|
|
|
// cancelled
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (resume.value())
|
|
|
|
|
save_path = std::move(resume_path);
|
|
|
|
|
}
|
|
|
|
|
if (!entry->serial.empty() && openResumeStateDialog(entry->path, entry->serial))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// only resume if the option is enabled, and we have one for this game
|
|
|
|
|
startFile(entry->path, std::move(save_path), std::nullopt);
|
|
|
|
|
startFile(entry->path, std::nullopt, std::nullopt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
|
|
|
|
@ -2991,27 +2966,36 @@ void MainWindow::requestShutdown(bool allow_confirm, bool allow_save_to_state, b
|
|
|
|
|
|
|
|
|
|
SystemLock lock(pauseAndLockSystem());
|
|
|
|
|
|
|
|
|
|
QMessageBox msgbox(lock.getDialogParent());
|
|
|
|
|
msgbox.setIcon(QMessageBox::Question);
|
|
|
|
|
msgbox.setWindowTitle(tr("Confirm Shutdown"));
|
|
|
|
|
msgbox.setWindowModality(Qt::WindowModal);
|
|
|
|
|
msgbox.setText(tr("Are you sure you want to shut down the virtual machine?"));
|
|
|
|
|
QMessageBox* msgbox = new QMessageBox(lock.getDialogParent());
|
|
|
|
|
msgbox->setWindowTitle(tr("Confirm Shutdown"));
|
|
|
|
|
msgbox->setWindowModality(Qt::WindowModal);
|
|
|
|
|
msgbox->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
|
msgbox->setIcon(QMessageBox::Question);
|
|
|
|
|
msgbox->setText(tr("Are you sure you want to shut down the virtual machine?"));
|
|
|
|
|
|
|
|
|
|
QCheckBox* save_cb = new QCheckBox(tr("Save State For Resume"), &msgbox);
|
|
|
|
|
QCheckBox* const save_cb = new QCheckBox(tr("Save State For Resume"), msgbox);
|
|
|
|
|
save_cb->setChecked(allow_save_to_state && save_state);
|
|
|
|
|
save_cb->setEnabled(allow_save_to_state);
|
|
|
|
|
msgbox.setCheckBox(save_cb);
|
|
|
|
|
msgbox.addButton(QMessageBox::Yes);
|
|
|
|
|
msgbox.addButton(QMessageBox::No);
|
|
|
|
|
msgbox.setDefaultButton(QMessageBox::Yes);
|
|
|
|
|
if (msgbox.exec() != QMessageBox::Yes)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
save_state = save_cb->isChecked();
|
|
|
|
|
|
|
|
|
|
// Don't switch back to fullscreen when we're shutting down anyway.
|
|
|
|
|
if (!QtHost::IsFullscreenUIStarted())
|
|
|
|
|
lock.cancelResume();
|
|
|
|
|
msgbox->setCheckBox(save_cb);
|
|
|
|
|
msgbox->addButton(QMessageBox::Yes);
|
|
|
|
|
msgbox->addButton(QMessageBox::No);
|
|
|
|
|
msgbox->setDefaultButton(QMessageBox::Yes);
|
|
|
|
|
connect(msgbox, &QMessageBox::finished, this,
|
|
|
|
|
[this, lock = std::move(lock), save_cb, allow_save_to_state, check_safety, check_pause, exit_fullscreen_ui,
|
|
|
|
|
quit_afterwards](int result) mutable {
|
|
|
|
|
if (result != QMessageBox::Yes)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Don't switch back to fullscreen when we're shutting down anyway.
|
|
|
|
|
if (!QtHost::IsFullscreenUIStarted())
|
|
|
|
|
lock.cancelResume();
|
|
|
|
|
|
|
|
|
|
const bool save_state = save_cb->isChecked();
|
|
|
|
|
requestShutdown(false, allow_save_to_state, save_state, check_safety, check_pause, exit_fullscreen_ui,
|
|
|
|
|
quit_afterwards);
|
|
|
|
|
});
|
|
|
|
|
msgbox->show();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we're running in batch mode, don't show the main window after shutting down.
|
|
|
|
|
@ -3134,10 +3118,11 @@ void MainWindow::openMemoryCardEditor(const QString& card_a_path, const QString&
|
|
|
|
|
|
|
|
|
|
void MainWindow::onAchievementsLoginRequested(Achievements::LoginRequestReason reason)
|
|
|
|
|
{
|
|
|
|
|
const auto lock = pauseAndLockSystem();
|
|
|
|
|
auto lock = pauseAndLockSystem();
|
|
|
|
|
|
|
|
|
|
AchievementLoginDialog dlg(lock.getDialogParent(), reason);
|
|
|
|
|
dlg.exec();
|
|
|
|
|
AchievementLoginDialog* dlg = new AchievementLoginDialog(lock.getDialogParent(), reason);
|
|
|
|
|
connect(dlg, &AchievementLoginDialog::finished, this, [lock = std::move(lock)]() {});
|
|
|
|
|
dlg->show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::onAchievementsLoginSuccess(const QString& username, quint32 points, quint32 sc_points,
|
|
|
|
|
@ -3335,7 +3320,6 @@ void MainWindow::checkForUpdates(bool display_message)
|
|
|
|
|
{
|
|
|
|
|
QMessageBox mbox(this);
|
|
|
|
|
mbox.setWindowTitle(tr("Updater Error"));
|
|
|
|
|
mbox.setWindowModality(Qt::WindowModal);
|
|
|
|
|
mbox.setTextFormat(Qt::RichText);
|
|
|
|
|
|
|
|
|
|
QString message;
|
|
|
|
|
@ -3440,20 +3424,26 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MainWindow::SystemLock::SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
|
|
|
|
|
: m_dialog_parent(dialog_parent), m_was_paused(was_paused), m_was_fullscreen(was_fullscreen)
|
|
|
|
|
: m_dialog_parent(dialog_parent), m_was_paused(was_paused), m_was_fullscreen(was_fullscreen), m_valid(true)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MainWindow::SystemLock::SystemLock(SystemLock&& lock)
|
|
|
|
|
: m_dialog_parent(lock.m_dialog_parent), m_was_paused(lock.m_was_paused), m_was_fullscreen(lock.m_was_fullscreen)
|
|
|
|
|
: m_dialog_parent(lock.m_dialog_parent), m_was_paused(lock.m_was_paused), m_was_fullscreen(lock.m_was_fullscreen),
|
|
|
|
|
m_valid(true)
|
|
|
|
|
{
|
|
|
|
|
Assert(lock.m_valid);
|
|
|
|
|
lock.m_dialog_parent = nullptr;
|
|
|
|
|
lock.m_was_paused = true;
|
|
|
|
|
lock.m_was_fullscreen = false;
|
|
|
|
|
lock.m_valid = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MainWindow::SystemLock::~SystemLock()
|
|
|
|
|
{
|
|
|
|
|
if (!m_valid)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
DebugAssert(s_system_locked.load(std::memory_order_relaxed) > 0);
|
|
|
|
|
s_system_locked.fetch_sub(1, std::memory_order_release);
|
|
|
|
|
if (m_was_fullscreen)
|
|
|
|
|
|