Merge pull request #1724 from stenzek/cheevos-hardcore-mode

Cheevos: Implement hardcore mode
This commit is contained in:
Connor McLaughlin 2021-03-03 20:34:50 +10:00 committed by GitHub
commit a8a1a9efd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 446 additions and 128 deletions

View file

@ -66,7 +66,7 @@
<item> <item>
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Please enter user name and password for retroachievements.org below. Your password will not be saved in DuckStation, instead an access token will be generated and used instead.</string> <string>Please enter user name and password for retroachievements.org below. Your password will not be saved in DuckStation, an access token will be generated and used instead.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View file

@ -3,6 +3,7 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "core/system.h" #include "core/system.h"
#include "frontend-common/cheevos.h" #include "frontend-common/cheevos.h"
#include "mainwindow.h"
#include "qtutils.h" #include "qtutils.h"
#include "settingsdialog.h" #include "settingsdialog.h"
#include "settingwidgetbinder.h" #include "settingwidgetbinder.h"
@ -15,11 +16,12 @@ AchievementSettingsWidget::AchievementSettingsWidget(QtHostInterface* host_inter
{ {
m_ui.setupUi(this); m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enable, "Cheevos", "Enabled", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.richPresence, "Cheevos", "RichPresence", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.richPresence, "Cheevos", "RichPresence", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.testMode, "Cheevos", "TestMode", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.testMode, "Cheevos", "TestMode", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.useFirstDiscFromPlaylist, "Cheevos", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.useFirstDiscFromPlaylist, "Cheevos",
"UseFirstDiscFromPlaylist", true); "UseFirstDiscFromPlaylist", true);
m_ui.enable->setChecked(m_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false));
m_ui.challengeMode->setChecked(m_host_interface->GetBoolSettingValue("Cheevos", "ChallengeMode", false));
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
tr("When enabled and logged in, DuckStation will scan for achievements on startup.")); tr("When enabled and logged in, DuckStation will scan for achievements on startup."));
@ -33,10 +35,14 @@ AchievementSettingsWidget::AchievementSettingsWidget(QtHostInterface* host_inter
m_ui.useFirstDiscFromPlaylist, tr("Use First Disc From Playlist"), tr("Unchecked"), m_ui.useFirstDiscFromPlaylist, tr("Use First Disc From Playlist"), tr("Unchecked"),
tr( tr(
"When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active.")); "When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active."));
dialog->registerWidgetHelp(m_ui.challengeMode, tr("Enable Hardcore Mode"), tr("Unchecked"),
tr("\"Challenge\" mode for achievements. Disables save state, cheats, and slowdown "
"functions, but you receive double the achievement points."));
connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.enable, &QCheckBox::toggled, this, &AchievementSettingsWidget::onEnableToggled);
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed); connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed); connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
connect(m_ui.challengeMode, &QCheckBox::toggled, this, &AchievementSettingsWidget::onChallengeModeToggled);
connect(host_interface, &QtHostInterface::achievementsLoaded, this, &AchievementSettingsWidget::onAchievementsLoaded); connect(host_interface, &QtHostInterface::achievementsLoaded, this, &AchievementSettingsWidget::onAchievementsLoaded);
updateEnableState(); updateEnableState();
@ -51,9 +57,11 @@ AchievementSettingsWidget::~AchievementSettingsWidget() = default;
void AchievementSettingsWidget::updateEnableState() void AchievementSettingsWidget::updateEnableState()
{ {
const bool enabled = m_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false); const bool enabled = m_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false);
const bool challenge_mode = m_host_interface->GetBoolSettingValue("Cheevos", "ChallengeMode", false);
m_ui.testMode->setEnabled(enabled); m_ui.testMode->setEnabled(enabled);
m_ui.useFirstDiscFromPlaylist->setEnabled(enabled); m_ui.useFirstDiscFromPlaylist->setEnabled(enabled);
m_ui.richPresence->setEnabled(enabled); m_ui.richPresence->setEnabled(enabled);
m_ui.challengeMode->setEnabled(enabled);
} }
void AchievementSettingsWidget::updateLoginState() void AchievementSettingsWidget::updateLoginState()
@ -98,17 +106,73 @@ void AchievementSettingsWidget::onLoginLogoutPressed()
void AchievementSettingsWidget::onViewProfilePressed() void AchievementSettingsWidget::onViewProfilePressed()
{ {
if (!Cheevos::IsLoggedIn()) const std::string username(m_host_interface->GetStringSettingValue("Cheevos", "Username"));
if (username.empty())
return; return;
const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(Cheevos::GetUsername()))); const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username)));
QtUtils::OpenURL( QtUtils::OpenURL(
QtUtils::GetRootWidget(this), QtUtils::GetRootWidget(this),
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username)))); QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
} }
void AchievementSettingsWidget::onEnableToggled(bool checked)
{
const bool challenge_mode = m_host_interface->GetBoolSettingValue("Cheevos", "ChallengeMode", false);
const bool challenge_mode_active = checked && challenge_mode;
if (challenge_mode_active && !confirmChallengeModeEnable())
{
QSignalBlocker sb(m_ui.challengeMode);
m_ui.challengeMode->setChecked(false);
return;
}
m_host_interface->SetBoolSettingValue("Cheevos", "Enabled", checked);
m_host_interface->applySettings(false);
if (challenge_mode)
m_host_interface->getMainWindow()->onAchievementsChallengeModeToggled(challenge_mode_active);
updateEnableState();
}
void AchievementSettingsWidget::onChallengeModeToggled(bool checked)
{
if (checked && !confirmChallengeModeEnable())
{
QSignalBlocker sb(m_ui.challengeMode);
m_ui.challengeMode->setChecked(false);
return;
}
m_host_interface->SetBoolSettingValue("Cheevos", "ChallengeMode", checked);
m_host_interface->applySettings(false);
m_host_interface->getMainWindow()->onAchievementsChallengeModeToggled(checked);
}
void AchievementSettingsWidget::onAchievementsLoaded(quint32 id, const QString& game_info_string, quint32 total, void AchievementSettingsWidget::onAchievementsLoaded(quint32 id, const QString& game_info_string, quint32 total,
quint32 points) quint32 points)
{ {
m_ui.gameInfo->setText(game_info_string); m_ui.gameInfo->setText(game_info_string);
} }
bool AchievementSettingsWidget::confirmChallengeModeEnable()
{
if (!System::IsValid())
return true;
QString message = tr("Enabling hardcore mode will shut down your current game.\n\n");
if (m_host_interface->ShouldSaveResumeState())
{
message +=
tr("The current state will be saved, but you will be unable to load it until you disable hardcore mode.\n\n");
}
message += tr("Do you want to continue?");
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Enable Hardcore Mode"), message) != QMessageBox::Yes)
return false;
m_host_interface->synchronousPowerOffSystem();
return true;
}

View file

@ -14,13 +14,17 @@ public:
~AchievementSettingsWidget(); ~AchievementSettingsWidget();
private Q_SLOTS: private Q_SLOTS:
void updateEnableState(); void onEnableToggled(bool checked);
void updateLoginState(); void onChallengeModeToggled(bool checked);
void onLoginLogoutPressed(); void onLoginLogoutPressed();
void onViewProfilePressed(); void onViewProfilePressed();
void onAchievementsLoaded(quint32 id, const QString& game_info_string, quint32 total, quint32 points); void onAchievementsLoaded(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
private: private:
bool confirmChallengeModeEnable();
void updateEnableState();
void updateLoginState();
Ui::AchievementSettingsWidget m_ui; Ui::AchievementSettingsWidget m_ui;
QtHostInterface* m_host_interface; QtHostInterface* m_host_interface;

View file

@ -60,6 +60,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QCheckBox" name="challengeMode">
<property name="text">
<string>Enable Hardcore Mode</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -97,32 +104,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Account Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="hardcoreMode">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Enable Hardcore Mode</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Enabling hardcore mode will disable cheats, save sates, and debugging features.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_4"> <widget class="QGroupBox" name="groupBox_4">
<property name="minimumSize"> <property name="minimumSize">

View file

@ -354,7 +354,7 @@ void MainWindow::updateMouseMode(bool paused)
void MainWindow::onEmulationStarting() void MainWindow::onEmulationStarting()
{ {
m_emulation_running = true; m_emulation_running = true;
updateEmulationActions(true, false); updateEmulationActions(true, false, m_host_interface->IsCheevosChallengeModeActive());
// ensure it gets updated, since the boot can take a while // ensure it gets updated, since the boot can take a while
QGuiApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QGuiApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
@ -362,13 +362,13 @@ void MainWindow::onEmulationStarting()
void MainWindow::onEmulationStarted() void MainWindow::onEmulationStarted()
{ {
updateEmulationActions(false, true); updateEmulationActions(false, true, m_host_interface->IsCheevosChallengeModeActive());
} }
void MainWindow::onEmulationStopped() void MainWindow::onEmulationStopped()
{ {
m_emulation_running = false; m_emulation_running = false;
updateEmulationActions(false, false); updateEmulationActions(false, false, m_host_interface->IsCheevosChallengeModeActive());
switchToGameListView(); switchToGameListView();
if (m_cheat_manager_dialog) if (m_cheat_manager_dialog)
@ -610,7 +610,8 @@ void MainWindow::onGameListEntryDoubleClicked(const GameListEntry* entry)
QString path = QString::fromStdString(entry->path); QString path = QString::fromStdString(entry->path);
if (!m_emulation_running) if (!m_emulation_running)
{ {
if (!entry->code.empty() && m_host_interface->GetBoolSettingValue("Main", "SaveStateOnExit", true)) if (!entry->code.empty() && m_host_interface->GetBoolSettingValue("Main", "SaveStateOnExit", true) &&
!m_host_interface->IsCheevosChallengeModeActive())
{ {
m_host_interface->resumeSystemFromState(path, true); m_host_interface->resumeSystemFromState(path, true);
} }
@ -670,7 +671,7 @@ void MainWindow::onGameListContextMenuRequested(const QPoint& point, const GameL
m_host_interface->bootSystem(std::move(boot_params)); m_host_interface->bootSystem(std::move(boot_params));
}); });
if (m_ui.menuDebug->menuAction()->isVisible()) if (m_ui.menuDebug->menuAction()->isVisible() && !m_host_interface->IsCheevosChallengeModeActive())
{ {
connect(menu.addAction(tr("Boot and Debug")), &QAction::triggered, [this, entry]() { connect(menu.addAction(tr("Boot and Debug")), &QAction::triggered, [this, entry]() {
m_open_debugger_on_start = true; m_open_debugger_on_start = true;
@ -841,27 +842,27 @@ void MainWindow::setupAdditionalUi()
} }
} }
void MainWindow::updateEmulationActions(bool starting, bool running) void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode)
{ {
m_ui.actionStartDisc->setDisabled(starting || running); m_ui.actionStartDisc->setDisabled(starting || running);
m_ui.actionStartBios->setDisabled(starting || running); m_ui.actionStartBios->setDisabled(starting || running);
m_ui.actionResumeLastState->setDisabled(starting || running); m_ui.actionResumeLastState->setDisabled(starting || running || cheevos_challenge_mode);
m_ui.actionPowerOff->setDisabled(starting || !running); m_ui.actionPowerOff->setDisabled(starting || !running);
m_ui.actionPowerOffWithoutSaving->setDisabled(starting || !running); m_ui.actionPowerOffWithoutSaving->setDisabled(starting || !running);
m_ui.actionReset->setDisabled(starting || !running); m_ui.actionReset->setDisabled(starting || !running);
m_ui.actionPause->setDisabled(starting || !running); m_ui.actionPause->setDisabled(starting || !running);
m_ui.actionChangeDisc->setDisabled(starting || !running); m_ui.actionChangeDisc->setDisabled(starting || !running);
m_ui.actionCheats->setDisabled(starting || !running); m_ui.actionCheats->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionScreenshot->setDisabled(starting || !running); m_ui.actionScreenshot->setDisabled(starting || !running);
m_ui.actionViewSystemDisplay->setEnabled(starting || running); m_ui.actionViewSystemDisplay->setEnabled(starting || running);
m_ui.menuChangeDisc->setDisabled(starting || !running); m_ui.menuChangeDisc->setDisabled(starting || !running);
m_ui.menuCheats->setDisabled(starting || !running); m_ui.menuCheats->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionCheatManager->setDisabled(starting || !running); m_ui.actionCheatManager->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionCPUDebugger->setDisabled(starting || !running); m_ui.actionCPUDebugger->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionDumpRAM->setDisabled(starting || !running); m_ui.actionDumpRAM->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionDumpVRAM->setDisabled(starting || !running); m_ui.actionDumpVRAM->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionDumpSPURAM->setDisabled(starting || !running); m_ui.actionDumpSPURAM->setDisabled(starting || !running || cheevos_challenge_mode);
m_ui.actionSaveState->setDisabled(starting || !running); m_ui.actionSaveState->setDisabled(starting || !running);
m_ui.menuSaveState->setDisabled(starting || !running); m_ui.menuSaveState->setDisabled(starting || !running);
@ -869,6 +870,9 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
m_ui.actionFullscreen->setDisabled(starting || !running); m_ui.actionFullscreen->setDisabled(starting || !running);
m_ui.actionLoadState->setDisabled(cheevos_challenge_mode);
m_ui.menuLoadState->setDisabled(cheevos_challenge_mode);
if (running && m_status_speed_widget->isHidden()) if (running && m_status_speed_widget->isHidden())
{ {
m_status_speed_widget->show(); m_status_speed_widget->show();
@ -945,7 +949,7 @@ void MainWindow::switchToEmulationView()
void MainWindow::connectSignals() void MainWindow::connectSignals()
{ {
updateEmulationActions(false, false); updateEmulationActions(false, false, m_host_interface->IsCheevosChallengeModeActive());
onEmulationPaused(false); onEmulationPaused(false);
connect(qApp, &QGuiApplication::applicationStateChanged, this, &MainWindow::onApplicationStateChanged); connect(qApp, &QGuiApplication::applicationStateChanged, this, &MainWindow::onApplicationStateChanged);
@ -1458,6 +1462,28 @@ void MainWindow::openMemoryCardEditor(const QString& card_a_path, const QString&
} }
} }
void MainWindow::onAchievementsChallengeModeToggled(bool enabled)
{
if (enabled)
{
if (m_cheat_manager_dialog)
{
m_cheat_manager_dialog->close();
delete m_cheat_manager_dialog;
m_cheat_manager_dialog = nullptr;
}
if (m_debugger_window)
{
m_debugger_window->close();
delete m_debugger_window;
m_debugger_window = nullptr;
}
}
updateEmulationActions(false, System::IsValid(), enabled);
}
void MainWindow::onToolsMemoryCardEditorTriggered() void MainWindow::onToolsMemoryCardEditorTriggered()
{ {
openMemoryCardEditor(QString(), QString()); openMemoryCardEditor(QString(), QString());

View file

@ -40,6 +40,9 @@ public:
/// Opens memory card editor with the specified paths. /// Opens memory card editor with the specified paths.
void openMemoryCardEditor(const QString& card_a_path, const QString& card_b_path); void openMemoryCardEditor(const QString& card_a_path, const QString& card_b_path);
/// Updates the state of the controls which should be disabled by achievements challenge mode.
void onAchievementsChallengeModeToggled(bool enabled);
public Q_SLOTS: public Q_SLOTS:
/// Updates debug menu visibility (hides if disabled). /// Updates debug menu visibility (hides if disabled).
void updateDebugMenuVisibility(); void updateDebugMenuVisibility();
@ -113,7 +116,7 @@ private:
void setupAdditionalUi(); void setupAdditionalUi();
void connectSignals(); void connectSignals();
void addThemeToMenu(const QString& name, const QString& key); void addThemeToMenu(const QString& name, const QString& key);
void updateEmulationActions(bool starting, bool running); void updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode);
bool isShowingGameList() const; bool isShowingGameList() const;
void switchToGameListView(); void switchToGameListView();
void switchToEmulationView(); void switchToEmulationView();

View file

@ -957,6 +957,7 @@ void QtHostInterface::populateGameListContextMenu(const GameListEntry* entry, QW
{ {
const std::vector<SaveStateInfo> available_states(GetAvailableSaveStates(entry->code.c_str())); const std::vector<SaveStateInfo> available_states(GetAvailableSaveStates(entry->code.c_str()));
const QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat); const QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
const bool challenge_mode = IsCheevosChallengeModeActive();
for (const SaveStateInfo& ssi : available_states) for (const SaveStateInfo& ssi : available_states)
{ {
if (ssi.global) if (ssi.global)
@ -971,7 +972,7 @@ void QtHostInterface::populateGameListContextMenu(const GameListEntry* entry, QW
if (slot < 0) if (slot < 0)
{ {
resume_action->setText(tr("Resume (%1)").arg(timestamp_str)); resume_action->setText(tr("Resume (%1)").arg(timestamp_str));
resume_action->setEnabled(true); resume_action->setEnabled(!challenge_mode);
action = resume_action; action = resume_action;
} }
else else
@ -980,6 +981,7 @@ void QtHostInterface::populateGameListContextMenu(const GameListEntry* entry, QW
action = load_state_menu->addAction(tr("Game Save %1 (%2)").arg(slot).arg(timestamp_str)); action = load_state_menu->addAction(tr("Game Save %1 (%2)").arg(slot).arg(timestamp_str));
} }
action->setDisabled(challenge_mode);
connect(action, &QAction::triggered, [this, path]() { loadState(path); }); connect(action, &QAction::triggered, [this, path]() { loadState(path); });
} }
} }

View file

@ -49,12 +49,12 @@ static void SendPlaying();
static void GameChanged(); static void GameChanged();
bool g_active = false; bool g_active = false;
bool g_challenge_mode = false;
u32 g_game_id = 0; u32 g_game_id = 0;
static bool s_logged_in = false; static bool s_logged_in = false;
static bool s_test_mode = false; static bool s_test_mode = false;
static bool s_use_first_disc_from_playlist = true; static bool s_use_first_disc_from_playlist = true;
static bool s_hardcode_mode = false;
static bool s_rich_presence_enabled = false; static bool s_rich_presence_enabled = false;
static rc_runtime_t s_rcheevos_runtime; static rc_runtime_t s_rcheevos_runtime;
@ -210,7 +210,7 @@ static std::string GetUserAgent()
return StringUtil::StdStringFromFormat("DuckStation %s", g_scm_tag_str); return StringUtil::StdStringFromFormat("DuckStation %s", g_scm_tag_str);
} }
bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence) bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence, bool challenge_mode)
{ {
s_http_downloader = FrontendCommon::HTTPDownloader::Create(); s_http_downloader = FrontendCommon::HTTPDownloader::Create();
if (!s_http_downloader) if (!s_http_downloader)
@ -221,6 +221,7 @@ bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_r
s_http_downloader->SetUserAgent(GetUserAgent()); s_http_downloader->SetUserAgent(GetUserAgent());
g_active = true; g_active = true;
g_challenge_mode = challenge_mode;
s_test_mode = test_mode; s_test_mode = test_mode;
s_use_first_disc_from_playlist = use_first_disc_from_playlist; s_use_first_disc_from_playlist = use_first_disc_from_playlist;
s_rich_presence_enabled = enable_rich_presence; s_rich_presence_enabled = enable_rich_presence;
@ -318,6 +319,9 @@ const std::string& GetRichPresenceString()
static void LoginASyncCallback(s32 status_code, const FrontendCommon::HTTPDownloader::Request::Data& data) static void LoginASyncCallback(s32 status_code, const FrontendCommon::HTTPDownloader::Request::Data& data)
{ {
if (GetHostInterface()->IsFullscreenUIEnabled())
ImGuiFullscreen::CloseBackgroundProgressDialog("cheevos_async_login");
rapidjson::Document doc; rapidjson::Document doc;
if (!ParseResponseJSON("Login", status_code, data, doc)) if (!ParseResponseJSON("Login", status_code, data, doc))
return; return;
@ -341,7 +345,12 @@ static void LoginASyncCallback(s32 status_code, const FrontendCommon::HTTPDownlo
GetHostInterface()->GetSettingsInterface()->Save(); GetHostInterface()->GetSettingsInterface()->Save();
} }
GetHostInterface()->ReportFormattedMessage("Logged into RetroAchievements using username '%s'.", username.c_str()); if (GetHostInterface()->IsFullscreenUIEnabled())
{
GetHostInterface()->ReportFormattedMessage(
GetHostInterface()->TranslateString("Cheevos", "Logged into RetroAchievements using username '%s'."),
username.c_str());
}
if (g_active) if (g_active)
{ {
@ -371,6 +380,13 @@ bool LoginAsync(const char* username, const char* password)
if (s_logged_in || std::strlen(username) == 0 || std::strlen(password) == 0) if (s_logged_in || std::strlen(username) == 0 || std::strlen(password) == 0)
return false; return false;
if (GetHostInterface()->IsFullscreenUIEnabled())
{
ImGuiFullscreen::OpenBackgroundProgressDialog(
"cheevos_async_login", GetHostInterface()->TranslateStdString("Cheevos", "Logging in to RetroAchivements..."), 0,
1, 0);
}
SendLogin(username, password, s_http_downloader.get()); SendLogin(username, password, s_http_downloader.get());
return true; return true;
} }
@ -444,7 +460,7 @@ static void UpdateImageDownloadProgress()
if (!GetHostInterface()->IsFullscreenUIEnabled()) if (!GetHostInterface()->IsFullscreenUIEnabled())
return; return;
std::string message("Downloading achievement resources..."); std::string message(g_host_interface->TranslateStdString("Cheevos", "Downloading achievement resources..."));
if (!s_image_download_progress_active) if (!s_image_download_progress_active)
{ {
ImGuiFullscreen::OpenBackgroundProgressDialog(str_id, std::move(message), 0, ImGuiFullscreen::OpenBackgroundProgressDialog(str_id, std::move(message), 0,
@ -497,8 +513,8 @@ static std::string GetBadgeImageFilename(const char* badge_name, bool locked, bo
SmallString clean_name(badge_name); SmallString clean_name(badge_name);
FileSystem::SanitizeFileName(clean_name); FileSystem::SanitizeFileName(clean_name);
return GetHostInterface()->GetUserDirectoryRelativePath("cache" FS_OSPATH_SEPARATOR_STR return GetHostInterface()->GetUserDirectoryRelativePath("cache" FS_OSPATH_SEPARATOR_STR
"achievement_badge" FS_OSPATH_SEPARATOR_STR "%s%s.png", "achievement_badge" FS_OSPATH_SEPARATOR_STR "%s%s.png",
clean_name.GetCharArray(), locked ? "_lock" : ""); clean_name.GetCharArray(), locked ? "_lock" : "");
} }
} }
@ -520,19 +536,20 @@ static std::string ResolveBadgePath(const char* badge_name, bool locked)
static void DisplayAchievementSummary() static void DisplayAchievementSummary()
{ {
std::string title( std::string title = s_game_title;
StringUtil::StdStringFromFormat("%s%s", s_game_title.c_str(), s_hardcode_mode ? " (Hardcore Mode)" : "")); if (g_challenge_mode)
std::string summary; title += GetHostInterface()->TranslateString("Cheevos", " (Hardcore Mode)");
std::string summary;
if (GetAchievementCount() > 0) if (GetAchievementCount() > 0)
{ {
summary = StringUtil::StdStringFromFormat("You have earned %u of %u achievements, and %u of %u points.", summary = StringUtil::StdStringFromFormat(
GetUnlockedAchiementCount(), GetAchievementCount(), GetHostInterface()->TranslateString("Cheevos", "You have earned %u of %u achievements, and %u of %u points."),
GetCurrentPointsForGame(), GetMaximumPointsForGame()); GetUnlockedAchiementCount(), GetAchievementCount(), GetCurrentPointsForGame(), GetMaximumPointsForGame());
} }
else else
{ {
summary = "This game has no achievements."; summary = GetHostInterface()->TranslateString("Cheevos", "This game has no achievements.");
} }
ImGuiFullscreen::AddNotification(10.0f, std::move(title), std::move(summary), s_game_icon); ImGuiFullscreen::AddNotification(10.0f, std::move(title), std::move(summary), s_game_icon);
@ -588,7 +605,7 @@ static void GetUserUnlocks()
{ {
char url[256]; char url[256];
int res = rc_url_get_unlock_list(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), g_game_id, int res = rc_url_get_unlock_list(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), g_game_id,
static_cast<int>(s_hardcode_mode)); static_cast<int>(g_challenge_mode));
Assert(res == 0); Assert(res == 0);
s_http_downloader->CreateRequest(url, GetUserUnlocksCallback); s_http_downloader->CreateRequest(url, GetUserUnlocksCallback);
@ -642,19 +659,29 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
const auto achievements(patch_data["Achievements"].GetArray()); const auto achievements(patch_data["Achievements"].GetArray());
for (const auto& achievement : achievements) for (const auto& achievement : achievements)
{ {
if (!achievement.HasMember("ID") || !achievement["ID"].IsNumber() || !achievement.HasMember("MemAddr") || if (!achievement.HasMember("ID") || !achievement["ID"].IsNumber() || !achievement.HasMember("Flags") ||
!achievement["MemAddr"].IsString() || !achievement.HasMember("Title") || !achievement["Title"].IsString()) !achievement["Flags"].IsNumber() || !achievement.HasMember("MemAddr") || !achievement["MemAddr"].IsString() ||
!achievement.HasMember("Title") || !achievement["Title"].IsString())
{ {
continue; continue;
} }
const u32 id = achievement["ID"].GetUint(); const u32 id = achievement["ID"].GetUint();
const u32 category = achievement["Flags"].GetUint();
const char* memaddr = achievement["MemAddr"].GetString(); const char* memaddr = achievement["MemAddr"].GetString();
std::string title = achievement["Title"].GetString(); std::string title = achievement["Title"].GetString();
std::string description = GetOptionalString(achievement, "Description"); std::string description = GetOptionalString(achievement, "Description");
std::string badge_name = GetOptionalString(achievement, "BadgeName"); std::string badge_name = GetOptionalString(achievement, "BadgeName");
const u32 points = GetOptionalUInt(achievement, "Points"); const u32 points = GetOptionalUInt(achievement, "Points");
// Skip local and unofficial achievements for now.
if (static_cast<AchievementCategory>(category) == AchievementCategory::Local ||
static_cast<AchievementCategory>(category) == AchievementCategory::Unofficial)
{
Log_WarningPrintf("Skipping unofficial achievement %u (%s)", id, title.c_str());
continue;
}
if (GetAchievementByID(id)) if (GetAchievementByID(id))
{ {
Log_ErrorPrintf("Achievement %u already exists", id); Log_ErrorPrintf("Achievement %u already exists", id);
@ -681,7 +708,8 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
} }
// parse rich presence // parse rich presence
if (s_rich_presence_enabled && patch_data.HasMember("RichPresencePatch") && patch_data["RichPresencePatch"].IsString()) if (s_rich_presence_enabled && patch_data.HasMember("RichPresencePatch") &&
patch_data["RichPresencePatch"].IsString())
{ {
const char* patch = patch_data["RichPresencePatch"].GetString(); const char* patch = patch_data["RichPresencePatch"].GetString();
int res = rc_runtime_activate_richpresence(&s_rcheevos_runtime, patch, nullptr, 0); int res = rc_runtime_activate_richpresence(&s_rcheevos_runtime, patch, nullptr, 0);
@ -719,19 +747,11 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
static void GetPatches(u32 game_id) static void GetPatches(u32 game_id)
{ {
#if 1
char url[256] = {}; char url[256] = {};
int res = rc_url_get_patch(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), game_id); int res = rc_url_get_patch(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), game_id);
Assert(res == 0); Assert(res == 0);
s_http_downloader->CreateRequest(url, GetPatchesCallback); s_http_downloader->CreateRequest(url, GetPatchesCallback);
#else
std::optional<std::vector<u8>> f = FileSystem::ReadBinaryFile("D:\\10434.txt");
if (!f)
return;
GetPatchesCallback(200, *f);
#endif
} }
static std::string GetGameHash(CDImage* cdi) static std::string GetGameHash(CDImage* cdi)
@ -855,9 +875,9 @@ void GameChanged(const std::string& path, CDImage* image)
if (s_game_hash.empty()) if (s_game_hash.empty())
{ {
GetHostInterface()->AddOSDMessage( GetHostInterface()->AddOSDMessage(GetHostInterface()->TranslateStdString(
GetHostInterface()->TranslateStdString("OSDMessage", "Failed to read executable from disc. Achievements disabled."), "OSDMessage", "Failed to read executable from disc. Achievements disabled."),
10.0f); 10.0f);
return; return;
} }
@ -1086,7 +1106,7 @@ void UnlockAchievement(u32 achievement_id, bool add_notification /* = true*/)
char url[256]; char url[256];
rc_url_award_cheevo(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), achievement_id, rc_url_award_cheevo(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), achievement_id,
static_cast<int>(s_hardcode_mode), s_game_hash.c_str()); static_cast<int>(g_challenge_mode), s_game_hash.c_str());
s_http_downloader->CreateRequest(url, UnlockAchievementCallback); s_http_downloader->CreateRequest(url, UnlockAchievementCallback);
} }

View file

@ -7,6 +7,13 @@ class CDImage;
namespace Cheevos { namespace Cheevos {
enum class AchievementCategory : u32
{
Local = 0,
Core = 3,
Unofficial = 5
};
struct Achievement struct Achievement
{ {
u32 id; u32 id;
@ -21,6 +28,7 @@ struct Achievement
}; };
extern bool g_active; extern bool g_active;
extern bool g_challenge_mode;
extern u32 g_game_id; extern u32 g_game_id;
ALWAYS_INLINE bool IsActive() ALWAYS_INLINE bool IsActive()
@ -28,6 +36,16 @@ ALWAYS_INLINE bool IsActive()
return g_active; return g_active;
} }
ALWAYS_INLINE bool IsChallengeModeEnabled()
{
return g_challenge_mode;
}
ALWAYS_INLINE bool IsChallengeModeActive()
{
return g_active && g_challenge_mode;
}
ALWAYS_INLINE bool HasActiveGame() ALWAYS_INLINE bool HasActiveGame()
{ {
return g_game_id != 0; return g_game_id != 0;
@ -38,7 +56,7 @@ ALWAYS_INLINE u32 GetGameID()
return g_game_id; return g_game_id;
} }
bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence); bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence, bool challenge_mode);
void Reset(); void Reset();
void Shutdown(); void Shutdown();
void Update(); void Update();

View file

@ -985,7 +985,10 @@ void CommonHostInterface::OnRunningGameChanged(const std::string& path, CDImage*
{ {
System::SetCheatList(nullptr); System::SetCheatList(nullptr);
if (g_settings.auto_load_cheats) if (g_settings.auto_load_cheats)
{
DebugAssert(!IsCheevosChallengeModeActive());
LoadCheatListFromGameTitle(); LoadCheatListFromGameTitle();
}
} }
#ifdef WITH_DISCORD_PRESENCE #ifdef WITH_DISCORD_PRESENCE
@ -1018,7 +1021,8 @@ void CommonHostInterface::DrawImGuiWindows()
if (System::IsValid()) if (System::IsValid())
{ {
DrawDebugWindows(); if (!IsCheevosChallengeModeActive())
DrawDebugWindows();
DrawFPSWindow(); DrawFPSWindow();
} }
@ -1236,6 +1240,15 @@ void CommonHostInterface::DrawDebugWindows()
g_dma.DrawDebugStateWindow(); g_dma.DrawDebugStateWindow();
} }
bool CommonHostInterface::IsCheevosChallengeModeActive() const
{
#ifdef WITH_CHEEVOS
return Cheevos::IsChallengeModeActive();
#else
return false;
#endif
}
void CommonHostInterface::DoFrameStep() void CommonHostInterface::DoFrameStep()
{ {
if (System::IsShutdown()) if (System::IsShutdown())
@ -1766,6 +1779,12 @@ void CommonHostInterface::RegisterHotkeys()
RegisterAudioHotkeys(); RegisterAudioHotkeys();
} }
static void DisplayHotkeyBlockedByChallengeModeMessage()
{
g_host_interface->AddOSDMessage(g_host_interface->TranslateStdString(
"OSDMessage", "Hotkey unavailable because achievements hardcore mode is active."));
}
void CommonHostInterface::RegisterGeneralHotkeys() void CommonHostInterface::RegisterGeneralHotkeys()
{ {
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("OpenQuickMenu"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("OpenQuickMenu"),
@ -1807,7 +1826,12 @@ void CommonHostInterface::RegisterGeneralHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("ToggleCheats"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("ToggleCheats"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle Cheats")), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle Cheats")), [this](bool pressed) {
if (pressed && System::IsValid()) if (pressed && System::IsValid())
DoToggleCheats(); {
if (!IsCheevosChallengeModeActive())
DoToggleCheats();
else
DisplayHotkeyBlockedByChallengeModeMessage();
}
}); });
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("PowerOff"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("PowerOff"),
@ -1838,7 +1862,7 @@ void CommonHostInterface::RegisterGeneralHotkeys()
#else #else
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("TogglePatchCodes"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("TogglePatchCodes"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle Patch Codes")), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle Patch Codes")), [this](bool pressed) {
if (pressed && System::IsValid()) if (pressed && System::IsValid() && !IsCheevosChallengeModeActive())
DoToggleCheats(); DoToggleCheats();
}); });
#endif #endif
@ -1858,7 +1882,12 @@ void CommonHostInterface::RegisterGeneralHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("FrameStep"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("FrameStep"),
StaticString(TRANSLATABLE("Hotkeys", "Frame Step")), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Frame Step")), [this](bool pressed) {
if (pressed && System::IsValid()) if (pressed && System::IsValid())
DoFrameStep(); {
if (!IsCheevosChallengeModeActive())
DoFrameStep();
else
DisplayHotkeyBlockedByChallengeModeMessage();
}
}); });
#ifndef __ANDROID__ #ifndef __ANDROID__
@ -1866,10 +1895,17 @@ void CommonHostInterface::RegisterGeneralHotkeys()
StaticString(TRANSLATABLE("Hotkeys", "Rewind")), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Rewind")), [this](bool pressed) {
if (System::IsValid()) if (System::IsValid())
{ {
AddOSDMessage(pressed ? TranslateStdString("OSDMessage", "Rewinding...") : if (!IsCheevosChallengeModeActive())
TranslateStdString("OSDMessage", "Stopped rewinding."), {
5.0f); AddOSDMessage(pressed ? TranslateStdString("OSDMessage", "Rewinding...") :
System::SetRewinding(pressed); TranslateStdString("OSDMessage", "Stopped rewinding."),
5.0f);
System::SetRewinding(pressed);
}
else
{
DisplayHotkeyBlockedByChallengeModeMessage();
}
} }
}); });
#endif #endif
@ -1966,7 +2002,12 @@ void CommonHostInterface::RegisterSaveStateHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), StaticString("LoadSelectedSaveState"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), StaticString("LoadSelectedSaveState"),
StaticString(TRANSLATABLE("Hotkeys", "Load From Selected Slot")), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Load From Selected Slot")), [this](bool pressed) {
if (pressed) if (pressed)
m_save_state_selector_ui->LoadCurrentSlot(); {
if (!IsCheevosChallengeModeActive())
m_save_state_selector_ui->LoadCurrentSlot();
else
DisplayHotkeyBlockedByChallengeModeMessage();
}
}); });
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), StaticString("SaveSelectedSaveState"), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), StaticString("SaveSelectedSaveState"),
StaticString(TRANSLATABLE("Hotkeys", "Save To Selected Slot")), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Save To Selected Slot")), [this](bool pressed) {
@ -1990,7 +2031,12 @@ void CommonHostInterface::RegisterSaveStateHotkeys()
TinyString::FromFormat("LoadGameState%u", slot), TinyString::FromFormat("Load Game State %u", slot), TinyString::FromFormat("LoadGameState%u", slot), TinyString::FromFormat("Load Game State %u", slot),
[this, slot](bool pressed) { [this, slot](bool pressed) {
if (pressed) if (pressed)
LoadState(false, slot); {
if (!IsCheevosChallengeModeActive())
LoadState(false, slot);
else
DisplayHotkeyBlockedByChallengeModeMessage();
}
}); });
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")),
TinyString::FromFormat("SaveGameState%u", slot), TinyString::FromFormat("Save Game State %u", slot), TinyString::FromFormat("SaveGameState%u", slot), TinyString::FromFormat("Save Game State %u", slot),
@ -2006,7 +2052,12 @@ void CommonHostInterface::RegisterSaveStateHotkeys()
TinyString::FromFormat("LoadGlobalState%u", slot), TinyString::FromFormat("LoadGlobalState%u", slot),
TinyString::FromFormat("Load Global State %u", slot), [this, slot](bool pressed) { TinyString::FromFormat("Load Global State %u", slot), [this, slot](bool pressed) {
if (pressed) if (pressed)
LoadState(true, slot); {
if (!IsCheevosChallengeModeActive())
LoadState(true, slot);
else
DisplayHotkeyBlockedByChallengeModeMessage();
}
}); });
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")),
TinyString::FromFormat("SaveGlobalState%u", slot), TinyString::FromFormat("SaveGlobalState%u", slot),
@ -2606,6 +2657,31 @@ void CommonHostInterface::SaveSettings(SettingsInterface& si)
HostInterface::SaveSettings(si); HostInterface::SaveSettings(si);
} }
void CommonHostInterface::FixIncompatibleSettings(bool display_osd_messages)
{
// if challenge mode is enabled, disable things like rewind since they use save states
if (IsCheevosChallengeModeActive())
{
g_settings.emulation_speed = std::max(g_settings.emulation_speed, 1.0f);
g_settings.fast_forward_speed = std::max(g_settings.fast_forward_speed, 1.0f);
g_settings.turbo_speed = std::max(g_settings.turbo_speed, 1.0f);
g_settings.rewind_enable = false;
g_settings.auto_load_cheats = false;
g_settings.debugging.enable_gdb_server = false;
g_settings.debugging.show_vram = false;
g_settings.debugging.show_gpu_state = false;
g_settings.debugging.show_cdrom_state = false;
g_settings.debugging.show_spu_state = false;
g_settings.debugging.show_timers_state = false;
g_settings.debugging.show_mdec_state = false;
g_settings.debugging.show_dma_state = false;
g_settings.debugging.dump_cpu_to_vram_copies = false;
g_settings.debugging.dump_vram_to_cpu_copies = false;
}
HostInterface::FixIncompatibleSettings(display_osd_messages);
}
void CommonHostInterface::ApplySettings(bool display_osd_messages) void CommonHostInterface::ApplySettings(bool display_osd_messages)
{ {
Settings old_settings(std::move(g_settings)); Settings old_settings(std::move(g_settings));
@ -2967,6 +3043,9 @@ bool CommonHostInterface::LoadCheatList(const char* filename)
bool CommonHostInterface::LoadCheatListFromGameTitle() bool CommonHostInterface::LoadCheatListFromGameTitle()
{ {
if (IsCheevosChallengeModeActive())
return false;
const std::string filename(GetCheatFileName()); const std::string filename(GetCheatFileName());
if (filename.empty() || !FileSystem::FileExists(filename.c_str())) if (filename.empty() || !FileSystem::FileExists(filename.c_str()))
return false; return false;
@ -2976,7 +3055,7 @@ bool CommonHostInterface::LoadCheatListFromGameTitle()
bool CommonHostInterface::LoadCheatListFromDatabase() bool CommonHostInterface::LoadCheatListFromDatabase()
{ {
if (System::GetRunningCode().empty()) if (System::GetRunningCode().empty() || IsCheevosChallengeModeActive())
return false; return false;
std::unique_ptr<CheatList> cl = std::make_unique<CheatList>(); std::unique_ptr<CheatList> cl = std::make_unique<CheatList>();
@ -3308,15 +3387,18 @@ void CommonHostInterface::UpdateCheevosActive()
const bool cheevos_test_mode = GetBoolSettingValue("Cheevos", "TestMode", false); const bool cheevos_test_mode = GetBoolSettingValue("Cheevos", "TestMode", false);
const bool cheevos_use_first_disc_from_playlist = GetBoolSettingValue("Cheevos", "UseFirstDiscFromPlaylist", true); const bool cheevos_use_first_disc_from_playlist = GetBoolSettingValue("Cheevos", "UseFirstDiscFromPlaylist", true);
const bool cheevos_rich_presence = GetBoolSettingValue("Cheevos", "RichPresence", true); const bool cheevos_rich_presence = GetBoolSettingValue("Cheevos", "RichPresence", true);
const bool cheevos_hardcore = GetBoolSettingValue("Cheevos", "ChallengeMode", false);
if (cheevos_enabled != Cheevos::IsActive() || cheevos_test_mode != Cheevos::IsTestModeActive() || if (cheevos_enabled != Cheevos::IsActive() || cheevos_test_mode != Cheevos::IsTestModeActive() ||
cheevos_use_first_disc_from_playlist != Cheevos::IsUsingFirstDiscFromPlaylist() || cheevos_use_first_disc_from_playlist != Cheevos::IsUsingFirstDiscFromPlaylist() ||
cheevos_rich_presence != Cheevos::IsRichPresenceEnabled()) cheevos_rich_presence != Cheevos::IsRichPresenceEnabled() ||
cheevos_hardcore != Cheevos::IsChallengeModeEnabled())
{ {
Cheevos::Shutdown(); Cheevos::Shutdown();
if (cheevos_enabled) if (cheevos_enabled)
{ {
if (!Cheevos::Initialize(cheevos_test_mode, cheevos_use_first_disc_from_playlist, cheevos_rich_presence)) if (!Cheevos::Initialize(cheevos_test_mode, cheevos_use_first_disc_from_playlist, cheevos_rich_presence,
cheevos_hardcore))
ReportError("Failed to initialize cheevos after settings change."); ReportError("Failed to initialize cheevos after settings change.");
} }
} }

View file

@ -308,6 +308,9 @@ public:
void DrawOSDMessages(); void DrawOSDMessages();
void DrawDebugWindows(); void DrawDebugWindows();
/// Returns true if features such as save states should be disabled.
bool IsCheevosChallengeModeActive() const;
protected: protected:
enum : u32 enum : u32
{ {
@ -391,6 +394,9 @@ protected:
/// Saves current settings variables to ini. /// Saves current settings variables to ini.
virtual void SaveSettings(SettingsInterface& si) override; virtual void SaveSettings(SettingsInterface& si) override;
/// Checks and fixes up any incompatible settings.
virtual void FixIncompatibleSettings(bool display_osd_messages);
/// Checks for settings changes, std::move() the old settings away for comparing beforehand. /// Checks for settings changes, std::move() the old settings away for comparing beforehand.
virtual void CheckForSettingsChanges(const Settings& old_settings) override; virtual void CheckForSettingsChanges(const Settings& old_settings) override;

View file

@ -93,6 +93,15 @@ static void OpenAboutWindow();
static void SetDebugMenuEnabled(bool enabled); static void SetDebugMenuEnabled(bool enabled);
static void UpdateDebugMenuVisibility(); static void UpdateDebugMenuVisibility();
static ALWAYS_INLINE bool IsCheevosHardcoreModeActive()
{
#ifdef WITH_CHEEVOS
return Cheevos::IsChallengeModeActive();
#else
return false;
#endif
}
static CommonHostInterface* s_host_interface; static CommonHostInterface* s_host_interface;
static MainWindowType s_current_main_window = MainWindowType::Landing; static MainWindowType s_current_main_window = MainWindowType::Landing;
static std::bitset<static_cast<u32>(FrontendCommon::ControllerNavigationButton::Count)> s_nav_input_values{}; static std::bitset<static_cast<u32>(FrontendCommon::ControllerNavigationButton::Count)> s_nav_input_values{};
@ -285,14 +294,14 @@ void Shutdown()
void Render() void Render()
{ {
if (s_debug_menu_enabled) if (s_debug_menu_enabled)
{
DrawDebugMenu(); DrawDebugMenu();
if (System::IsValid())
s_host_interface->DrawDebugWindows(); if (System::IsValid())
}
else if (System::IsValid())
{ {
DrawStatsOverlay(); DrawStatsOverlay();
if (!IsCheevosHardcoreModeActive())
s_host_interface->DrawDebugWindows();
} }
ImGuiFullscreen::BeginLayout(); ImGuiFullscreen::BeginLayout();
@ -707,7 +716,7 @@ void DrawLandingWindow()
BeginMenuButtons(7, 0.5f); BeginMenuButtons(7, 0.5f);
if (MenuButton(" " ICON_FA_PLAY_CIRCLE " Resume", if (MenuButton(" " ICON_FA_PLAY_CIRCLE " Resume",
"Starts the console from where it was before it was last closed.")) "Starts the console from where it was before it was last closed.", !IsCheevosHardcoreModeActive()))
{ {
s_host_interface->RunLater([]() { s_host_interface->ResumeSystemFromMostRecentState(); }); s_host_interface->RunLater([]() { s_host_interface->ResumeSystemFromMostRecentState(); });
ClearImGuiFocus(); ClearImGuiFocus();
@ -725,7 +734,7 @@ void DrawLandingWindow()
if (MenuButton(" " ICON_FA_TOOLBOX " Start BIOS", "Start the console without any disc inserted.")) if (MenuButton(" " ICON_FA_TOOLBOX " Start BIOS", "Start the console without any disc inserted."))
s_host_interface->RunLater(DoStartBIOS); s_host_interface->RunLater(DoStartBIOS);
if (MenuButton(" " ICON_FA_UNDO " Load State", "Loads a global save state.")) if (MenuButton(" " ICON_FA_UNDO " Load State", "Loads a global save state.", !IsCheevosHardcoreModeActive()))
{ {
OpenSaveStateSelector(true); OpenSaveStateSelector(true);
} }
@ -1066,6 +1075,91 @@ static bool ToggleButtonForNonSetting(const char* title, const char* summary, co
return true; return true;
} }
static void DrawAchievementsLoginWindow()
{
ImGui::SetNextWindowSize(LayoutScale(700.0f, 0.0f));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(10.0f, 10.0f));
ImGui::PushFont(g_large_font);
bool is_open = true;
if (ImGui::BeginPopupModal("Achievements Login", &is_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize))
{
ImGui::TextWrapped("Please enter user name and password for retroachievements.org.");
ImGui::NewLine();
ImGui::TextWrapped(
"Your password will not be saved in DuckStation, an access token will be generated and used instead.");
ImGui::NewLine();
static char username[256] = {};
static char password[256] = {};
ImGui::Text("User Name: ");
ImGui::SameLine(LayoutScale(200.0f));
ImGui::InputText("##username", username, sizeof(username));
ImGui::Text("Password: ");
ImGui::SameLine(LayoutScale(200.0f));
ImGui::InputText("##password", password, sizeof(password), ImGuiInputTextFlags_Password);
ImGui::NewLine();
BeginMenuButtons();
const bool login_enabled = (std::strlen(username) > 0 && std::strlen(password) > 0);
if (ActiveButton(ICON_FA_KEY " Login", false, login_enabled))
{
Cheevos::LoginAsync(username, password);
std::memset(username, 0, sizeof(username));
std::memset(password, 0, sizeof(password));
ImGui::CloseCurrentPopup();
}
if (ActiveButton(ICON_FA_TIMES " Cancel", false))
ImGui::CloseCurrentPopup();
EndMenuButtons();
ImGui::EndPopup();
}
ImGui::PopFont();
ImGui::PopStyleVar(2);
}
static bool ConfirmChallengeModeEnable()
{
if (!System::IsValid())
return true;
const bool cheevos_enabled = s_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false);
const bool cheevos_hardcore = s_host_interface->GetBoolSettingValue("Cheevos", "ChallengeMode", false);
if (!cheevos_enabled || !cheevos_hardcore)
return true;
SmallString message;
message.AppendString("Enabling hardcore mode will shut down your current game.\n\n");
if (s_host_interface->ShouldSaveResumeState())
{
message.AppendString(
"The current state will be saved, but you will be unable to load it until you disable hardcore mode.\n\n");
}
message.AppendString("Do you want to continue?");
if (!s_host_interface->ConfirmMessage(message))
return false;
s_host_interface->PowerOffSystem(s_host_interface->ShouldSaveResumeState());
return true;
}
void DrawSettingsWindow() void DrawSettingsWindow()
{ {
BeginFullscreenColumns(); BeginFullscreenColumns();
@ -1954,23 +2048,45 @@ void DrawSettingsWindow()
BeginMenuButtons(); BeginMenuButtons();
MenuHeading("Settings"); MenuHeading("Settings");
if (ToggleButtonForNonSetting(ICON_FA_TROPHY " Enable RetroAchievements",
"When enabled and logged in, DuckStation will scan for achievements on startup.",
"Cheevos", "Enabled", false))
{
s_host_interface->RunLater([]() {
if (!ConfirmChallengeModeEnable())
s_host_interface->GetSettingsInterface()->SetBoolValue("Cheevos", "Enabled", false);
else
SaveAndApplySettings();
});
}
settings_changed |= ToggleButtonForNonSetting( settings_changed |= ToggleButtonForNonSetting(
"Enable RetroAchievements", "When enabled and logged in, DuckStation will scan for achievements on startup.", ICON_FA_USER_FRIENDS " Rich Presence",
"Cheevos", "Enabled", false);
settings_changed |= ToggleButtonForNonSetting(
"Rich Presence",
"When enabled, rich presence information will be collected and sent to the server where supported.", "When enabled, rich presence information will be collected and sent to the server where supported.",
"Cheevos", "RichPresence", true); "Cheevos", "RichPresence", true);
settings_changed |= settings_changed |=
ToggleButtonForNonSetting("Test Mode", ToggleButtonForNonSetting(ICON_FA_STETHOSCOPE " Test Mode",
"When enabled, DuckStation will assume all achievements are locked and not " "When enabled, DuckStation will assume all achievements are locked and not "
"send any unlock notifications to the server.", "send any unlock notifications to the server.",
"Cheevos", "TestMode", false); "Cheevos", "TestMode", false);
settings_changed |= ToggleButtonForNonSetting("Use First Disc From Playlist", settings_changed |= ToggleButtonForNonSetting(ICON_FA_COMPACT_DISC " Use First Disc From Playlist",
"When enabled, the first disc in a playlist will be used for " "When enabled, the first disc in a playlist will be used for "
"achievements, regardless of which disc is active.", "achievements, regardless of which disc is active.",
"Cheevos", "UseFirstDiscFromPlaylist", true); "Cheevos", "UseFirstDiscFromPlaylist", true);
if (ToggleButtonForNonSetting(ICON_FA_HARD_HAT " Hardcore Mode",
"\"Challenge\" mode for achievements. Disables save state, cheats, and slowdown "
"functions, but you receive double the achievement points.",
"Cheevos", "ChallengeMode", false))
{
s_host_interface->RunLater([]() {
if (!ConfirmChallengeModeEnable())
s_host_interface->GetSettingsInterface()->SetBoolValue("Cheevos", "ChallengeMode", false);
else
SaveAndApplySettings();
});
}
MenuHeading("Account"); MenuHeading("Account");
if (Cheevos::IsLoggedIn()) if (Cheevos::IsLoggedIn())
{ {
@ -1991,13 +2107,20 @@ void DrawSettingsWindow()
if (MenuButton(ICON_FA_KEY " Logout", "Logs out of RetroAchievements.")) if (MenuButton(ICON_FA_KEY " Logout", "Logs out of RetroAchievements."))
Cheevos::Logout(); Cheevos::Logout();
} }
else else if (Cheevos::IsActive())
{ {
ActiveButton(SmallString::FromFormat(ICON_FA_USER " Not Logged In", Cheevos::GetUsername().c_str()), false, ActiveButton(ICON_FA_USER " Not Logged In", false, false,
false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
if (MenuButton(ICON_FA_KEY " Login", "Logs in to RetroAchievements.")) if (MenuButton(ICON_FA_KEY " Login", "Logs in to RetroAchievements."))
Cheevos::LoginAsync("", ""); ImGui::OpenPopup("Achievements Login");
DrawAchievementsLoginWindow();
}
else
{
ActiveButton(ICON_FA_USER " Achievements are disabled.", false, false,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
} }
MenuHeading("Current Game"); MenuHeading("Current Game");
@ -2220,7 +2343,7 @@ void DrawQuickMenu(MainWindowType type)
s_host_interface->RunLater([]() { s_host_interface->SaveScreenshot(); }); s_host_interface->RunLater([]() { s_host_interface->SaveScreenshot(); });
} }
if (ActiveButton(ICON_FA_UNDO " Load State", false)) if (ActiveButton(ICON_FA_UNDO " Load State", false, !IsCheevosHardcoreModeActive()))
{ {
s_current_main_window = MainWindowType::None; s_current_main_window = MainWindowType::None;
OpenSaveStateSelector(true); OpenSaveStateSelector(true);
@ -2232,7 +2355,7 @@ void DrawQuickMenu(MainWindowType type)
OpenSaveStateSelector(false); OpenSaveStateSelector(false);
} }
if (ActiveButton(ICON_FA_FROWN_OPEN " Cheat List", false)) if (ActiveButton(ICON_FA_FROWN_OPEN " Cheat List", false, !IsCheevosHardcoreModeActive()))
{ {
s_current_main_window = MainWindowType::None; s_current_main_window = MainWindowType::None;
DoCheatsMenu(); DoCheatsMenu();
@ -3248,9 +3371,7 @@ void DrawDebugSystemMenu()
if (ImGui::MenuItem("Change Disc", nullptr, false, system_enabled)) if (ImGui::MenuItem("Change Disc", nullptr, false, system_enabled))
{ {
#if 0
DoChangeDisc(); DoChangeDisc();
#endif
ClearImGuiFocus(); ClearImGuiFocus();
} }
@ -3260,17 +3381,9 @@ void DrawDebugSystemMenu()
ClearImGuiFocus(); ClearImGuiFocus();
} }
if (ImGui::MenuItem("Frame Step", nullptr, false, system_enabled))
{
#if 0
s_host_interface->RunLater([]() { DoFrameStep(); });
#endif
ClearImGuiFocus();
}
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginMenu("Load State")) if (ImGui::BeginMenu("Load State", !IsCheevosHardcoreModeActive()))
{ {
for (u32 i = 1; i <= CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) for (u32 i = 1; i <= CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS; i++)
{ {
@ -3302,7 +3415,7 @@ void DrawDebugSystemMenu()
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginMenu("Cheats", system_enabled)) if (ImGui::BeginMenu("Cheats", system_enabled && !IsCheevosHardcoreModeActive()))
{ {
const bool has_cheat_file = System::HasCheatList(); const bool has_cheat_file = System::HasCheatList();
if (ImGui::BeginMenu("Enabled Cheats", has_cheat_file)) if (ImGui::BeginMenu("Enabled Cheats", has_cheat_file))
@ -3812,9 +3925,8 @@ void DrawAchievementWindow()
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize)); const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
text.Assign(Cheevos::GetGameTitle()); text.Assign(Cheevos::GetGameTitle());
const std::string& developer = Cheevos::GetGameDeveloper(); if (Cheevos::IsChallengeModeActive())
if (!developer.empty()) text.AppendString(" (Hardcore Mode)");
text.AppendFormattedString(" (%s)", developer.c_str());
top += g_large_font->FontSize + spacing; top += g_large_font->FontSize + spacing;