Qt: Add RetroAchievements implementation

This commit is contained in:
Connor McLaughlin 2022-04-18 23:35:14 +10:00 committed by refractionpcsx2
parent 843b0b3eb1
commit 0419de4baf
44 changed files with 4745 additions and 32 deletions

View File

@ -0,0 +1,2 @@
lbsubmit.wav: https://freesound.org/people/Eponn/sounds/636656/
unlock.wav and message.wav are from https://github.com/RetroAchievements/RAInterface

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,6 +19,7 @@
#include "CocoaTools.h"
#include "Console.h"
#include "General.h"
#include "WindowInfo.h"
#include <vector>
#include <Cocoa/Cocoa.h>
@ -125,3 +126,12 @@ void CocoaTools::RemoveThemeChangeHandler(void* ctx)
assert([NSThread isMainThread]);
[s_themeChangeHandler removeCallback:ctx];
}
// MARK: - Sound playback
bool Common::PlaySoundAsync(const char* path)
{
NSString* nspath = [[NSString alloc] initWithUTF8String:path];
NSSound* sound = [[NSSound alloc] initWithContentsOfFile:nspath byReference:YES];
return [sound play];
}

View File

@ -166,3 +166,10 @@ extern const u32 SPIN_TIME_NS;
extern std::string GetOSVersionString();
void ScreensaverAllow(bool allow);
namespace Common
{
/// Abstracts platform-specific code for asynchronously playing a sound.
/// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound.
bool PlaySoundAsync(const char* path);
} // namespace Common

View File

@ -17,7 +17,9 @@
#include <ctype.h>
#include <time.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "common/Pcsx2Types.h"
#include "common/General.h"
@ -64,4 +66,21 @@ void ScreensaverAllow(bool allow)
{
// no-op
}
bool Common::PlaySoundAsync(const char* path)
{
#ifdef __linux__
// This is... pretty awful. But I can't think of a better way without linking to e.g. gstreamer.
const char* cmdname = "aplay";
const char* argv[] = {cmdname, path, nullptr};
pid_t pid;
// Since we set SA_NOCLDWAIT in Qt, we don't need to wait here.
int res = posix_spawnp(&pid, cmdname, nullptr, nullptr, const_cast<char**>(argv), environ);
return (res == 0);
#else
return false;
#endif
}
#endif

View File

@ -19,9 +19,12 @@
#include "common/RedtapeWindows.h"
#include "common/Exceptions.h"
#include "common/StringUtil.h"
#include "common/General.h"
#include "fmt/core.h"
#include <mmsystem.h>
#pragma comment(lib, "User32.lib")
alignas(16) static LARGE_INTEGER lfreq;
@ -116,4 +119,11 @@ void ScreensaverAllow(bool allow)
flags |= ES_DISPLAY_REQUIRED;
SetThreadExecutionState(flags);
}
bool Common::PlaySoundAsync(const char* path)
{
const std::wstring wpath(StringUtil::UTF8StringToWideString(path));
return PlaySoundW(wpath.c_str(), NULL, SND_ASYNC | SND_NODEFAULT);
}
#endif

View File

@ -104,12 +104,51 @@ static inline std::optional<T> ReadFileInZipToContainer(zip_t* zip, const char*
return ret;
}
template <typename T>
static inline std::optional<T> ReadFileInZipToContainer(zip_file_t* file, u32 chunk_size = 4096)
{
std::optional<T> ret = T();
for (;;)
{
const size_t pos = ret->size();
ret->resize(pos + chunk_size);
const s64 read = zip_fread(file, ret->data() + pos, chunk_size);
if (read < 0)
{
// read error
ret.reset();
break;
}
// if less than chunk size, we're EOF
if (read != static_cast<s64>(chunk_size))
{
ret->resize(pos + static_cast<size_t>(read));
break;
}
}
return ret;
}
static inline std::optional<std::string> ReadFileInZipToString(zip_t* zip, const char* name)
{
return ReadFileInZipToContainer<std::string>(zip, name);
}
static inline std::optional<std::string> ReadFileInZipToString(zip_file_t* file, u32 chunk_size = 4096)
{
return ReadFileInZipToContainer<std::string>(file, chunk_size);
}
static inline std::optional<std::vector<u8>> ReadBinaryFileInZip(zip_t* zip, const char* name)
{
return ReadFileInZipToContainer<std::vector<u8>>(zip, name);
}
static inline std::optional<std::vector<u8>> ReadBinaryFileInZip(zip_file_t* file, u32 chunk_size = 4096)
{
return ReadFileInZipToContainer<std::vector<u8>>(file, chunk_size);
}

View File

@ -121,6 +121,17 @@ target_sources(pcsx2-qt PRIVATE
resources/resources.qrc
)
if(USE_ACHIEVEMENTS)
target_sources(pcsx2-qt PRIVATE
Settings/AchievementLoginDialog.cpp
Settings/AchievementLoginDialog.h
Settings/AchievementLoginDialog.ui
Settings/AchievementSettingsWidget.cpp
Settings/AchievementSettingsWidget.h
Settings/AchievementSettingsWidget.ui
)
endif()
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
target_include_directories(pcsx2-qt PRIVATE

View File

@ -54,6 +54,10 @@
#include "svnrev.h"
#include "Tools/InputRecording/NewInputRecordingDlg.h"
#ifdef ENABLE_RAINTEGRATION
#include "pcsx2/Frontend/Achievements.h"
#endif
static constexpr char OPEN_FILE_FILTER[] =
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.elf *.irx *.m3u *.gs *.gs.xz *.gs.zst *.dump);;"
@ -238,6 +242,38 @@ void MainWindow::setupAdditionalUi()
updateEmulationActions(false, false);
updateDisplayRelatedActions(false, false, false);
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
{
QMenu* raMenu = new QMenu(QStringLiteral("RAIntegration"), m_ui.menuDebug);
connect(raMenu, &QMenu::aboutToShow, this, [this, raMenu]() {
raMenu->clear();
const auto items = Achievements::RAIntegration::GetMenuItems();
for (const auto& [id, title, checked] : items)
{
if (id == 0)
{
raMenu->addSeparator();
continue;
}
QAction* raAction = raMenu->addAction(QString::fromUtf8(title));
if (checked)
{
raAction->setCheckable(true);
raAction->setChecked(checked);
}
connect(raAction, &QAction::triggered, this, [id = id]() {
Host::RunOnCPUThread([id]() { Achievements::RAIntegration::ActivateMenuItem(id); }, false);
});
}
});
m_ui.menuDebug->insertMenu(m_ui.actionToggleSoftwareRendering, raMenu);
}
#endif
}
void MainWindow::connectSignals()
@ -271,6 +307,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionMemoryCardSettings, &QAction::triggered, [this]() { doSettings("Memory Cards"); });
connect(m_ui.actionDEV9Settings, &QAction::triggered, [this]() { doSettings("Network & HDD"); });
connect(m_ui.actionFolderSettings, &QAction::triggered, [this]() { doSettings("Folders"); });
connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); });
connect(
m_ui.actionControllerSettings, &QAction::triggered, [this]() { doControllerSettings(ControllerSettingsDialog::Category::GlobalSettings); });
connect(m_ui.actionHotkeySettings, &QAction::triggered, [this]() { doControllerSettings(ControllerSettingsDialog::Category::HotkeySettings); });
@ -1690,6 +1727,11 @@ void MainWindow::showEvent(QShowEvent* event)
// the rest of the window... so, instead, let's just force it to be resized on show.
if (isShowingGameList())
m_game_list_widget->resizeTableViewColumnsToFit();
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
Achievements::RAIntegration::MainWindowChanged((void*)winId());
#endif
}
void MainWindow::closeEvent(QCloseEvent* event)

View File

@ -98,6 +98,7 @@
<addaction name="actionFolderSettings"/>
<addaction name="actionControllerSettings"/>
<addaction name="actionHotkeySettings"/>
<addaction name="actionAchievementSettings"/>
<addaction name="separator"/>
<addaction name="actionAddGameDirectory"/>
<addaction name="actionScanForNewGames"/>
@ -409,6 +410,15 @@
<string>&amp;Graphics</string>
</property>
</action>
<action name="actionAchievementSettings">
<property name="icon">
<iconset theme="trophy-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>A&amp;chievements</string>
</property>
</action>
<action name="actionPostProcessingSettings">
<property name="text">
<string>&amp;Post-Processing Settings...</string>

View File

@ -63,6 +63,10 @@
#include "QtUtils.h"
#include "svnrev.h"
#ifdef ENABLE_ACHIEVEMENTS
#include "Frontend/Achievements.h"
#endif
static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
EmuThread* g_emu_thread = nullptr;
@ -1111,6 +1115,45 @@ void Host::OnSaveStateSaved(const std::string_view& filename)
emit g_emu_thread->onSaveStateSaved(QtUtils::StringViewToQString(filename));
}
#ifdef ENABLE_ACHIEVEMENTS
void Host::OnAchievementsRefreshed()
{
u32 game_id = 0;
u32 achievement_count = 0;
u32 max_points = 0;
QString game_info;
if (Achievements::HasActiveGame())
{
game_id = Achievements::GetGameID();
achievement_count = Achievements::GetAchievementCount();
max_points = Achievements::GetMaximumPointsForGame();
game_info = qApp->translate("EmuThread",
"Game ID: %1\n"
"Game Title: %2\n"
"Achievements: %5 (%6)\n\n")
.arg(game_id)
.arg(QString::fromStdString(Achievements::GetGameTitle()))
.arg(achievement_count)
.arg(qApp->translate("EmuThread", "%n points", "", max_points));
const std::string rich_presence_string(Achievements::GetRichPresenceString());
if (!rich_presence_string.empty())
game_info.append(QString::fromStdString(rich_presence_string));
else
game_info.append(qApp->translate("EmuThread", "Rich presence inactive or unsupported."));
}
else
{
game_info = qApp->translate("EmuThread", "Game not loaded or no RetroAchievements available.");
}
emit g_emu_thread->onAchievementsRefreshed(game_id, game_info, achievement_count, max_points);
}
#endif
void Host::CPUThreadVSync()
{
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
@ -1475,6 +1518,14 @@ void QtHost::HookSignals()
{
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
#ifdef __linux__
// Ignore SIGCHLD by default on Linux, since we kick off aplay asynchronously.
struct sigaction sa_chld = {};
sigemptyset(&sa_chld.sa_mask);
sa_chld.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
sigaction(SIGCHLD, &sa_chld, nullptr);
#endif
}
void QtHost::PrintCommandLineVersion()
@ -1504,6 +1555,9 @@ void QtHost::PrintCommandLineHelp(const std::string_view& progname)
std::fprintf(stderr, " -fullscreen: Enters fullscreen mode immediately after starting.\n");
std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
std::fprintf(stderr, " -earlyconsolelog: Forces logging of early console messages to console.\n");
#ifdef ENABLE_RAINTEGRATION
std::fprintf(stderr, " -raintegration: Use RAIntegration instead of built-in achievement support.\n");
#endif
std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
" parameters make up the filename. Use when the filename contains\n"
" spaces or starts with a dash.\n");
@ -1613,6 +1667,13 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
s_start_fullscreen_ui = true;
continue;
}
#ifdef ENABLE_RAINTEGRATION
else if (CHECK_ARG(QStringLiteral("-raintegration")))
{
Achievements::SwitchToRAIntegration();
continue;
}
#endif
else if (CHECK_ARG(QStringLiteral("--")))
{
no_more_args = true;

View File

@ -151,6 +151,9 @@ Q_SIGNALS:
/// just signifies that the save has started, not necessarily completed.
void onSaveStateSaved(const QString& path);
/// Called when achievements are reloaded/refreshed (e.g. game change, login, option change).
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
protected:
void run();

View File

@ -0,0 +1,91 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "AchievementLoginDialog.h"
#include "pcsx2/Frontend/Achievements.h"
#include "QtHost.h"
#include <QtWidgets/QMessageBox>
AchievementLoginDialog::AchievementLoginDialog(QWidget* parent)
: QDialog(parent)
{
m_ui.setupUi(this);
m_ui.loginIcon->setPixmap(QIcon::fromTheme("login-box-line").pixmap(32));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
m_login = m_ui.buttonBox->addButton(tr("&Login"), QDialogButtonBox::AcceptRole);
m_login->setEnabled(false);
connectUi();
}
AchievementLoginDialog::~AchievementLoginDialog() = default;
void AchievementLoginDialog::loginClicked()
{
std::string username(m_ui.userName->text().toStdString());
std::string password(m_ui.password->text().toStdString());
// TODO: Make cancellable.
m_ui.status->setText(tr("Logging in..."));
enableUI(false);
Host::RunOnCPUThread([this, username = std::move(username), password = std::move(password)]() {
const bool result = Achievements::Login(username.c_str(), password.c_str());
QMetaObject::invokeMethod(this, "processLoginResult", Qt::QueuedConnection, Q_ARG(bool, result));
});
}
void AchievementLoginDialog::cancelClicked()
{
done(1);
}
void AchievementLoginDialog::processLoginResult(bool result)
{
if (!result)
{
QMessageBox::critical(this, tr("Login Error"),
tr("Login failed. Please check your username and password, and try again."));
m_ui.status->setText(tr("Login failed."));
enableUI(true);
return;
}
done(0);
}
void AchievementLoginDialog::connectUi()
{
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &AchievementLoginDialog::loginClicked);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &AchievementLoginDialog::cancelClicked);
auto enableLoginButton = [this](const QString&) { m_login->setEnabled(canEnableLoginButton()); };
connect(m_ui.userName, &QLineEdit::textChanged, enableLoginButton);
connect(m_ui.password, &QLineEdit::textChanged, enableLoginButton);
}
void AchievementLoginDialog::enableUI(bool enabled)
{
m_ui.userName->setEnabled(enabled);
m_ui.password->setEnabled(enabled);
m_ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled);
m_login->setEnabled(enabled && canEnableLoginButton());
}
bool AchievementLoginDialog::canEnableLoginButton() const
{
return !m_ui.userName->text().isEmpty() && !m_ui.password->text().isEmpty();
}

View File

@ -0,0 +1,41 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ui_AchievementLoginDialog.h"
#include <QtWidgets/QDialog>
#include <QtWidgets/QPushButton>
class AchievementLoginDialog : public QDialog
{
Q_OBJECT
public:
AchievementLoginDialog(QWidget* parent);
~AchievementLoginDialog();
private Q_SLOTS:
void loginClicked();
void cancelClicked();
void processLoginResult(bool result);
private:
void connectUi();
void enableUI(bool enabled);
bool canEnableLoginButton() const;
Ui::AchievementLoginDialog m_ui;
QPushButton* m_login;
};

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AchievementLoginDialog</class>
<widget class="QDialog" name="AchievementLoginDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>410</width>
<height>190</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>410</width>
<height>190</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>410</width>
<height>190</height>
</size>
</property>
<property name="windowTitle">
<string comment="Window title">RetroAchievements Login</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<widget class="QLabel" name="loginIcon">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>:/icons/black/48/login-box-line.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>14</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string comment="Header text">RetroAchievements Login</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Please enter user name and password for retroachievements.org below. Your password will not be saved in PCSX2, an access token will be generated and used instead.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>User Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="userName"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<item>
<widget class="QLabel" name="status">
<property name="text">
<string>Ready...</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,194 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "AchievementSettingsWidget.h"
#include "AchievementLoginDialog.h"
#include "MainWindow.h"
#include "SettingsDialog.h"
#include "SettingWidgetBinder.h"
#include "QtUtils.h"
#include "pcsx2/Frontend/Achievements.h"
#include "pcsx2/HostSettings.h"
#include "common/StringUtil.h"
#include <QtCore/QDateTime>
#include <QtWidgets/QMessageBox>
AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent)
: QWidget(parent)
, m_dialog(dialog)
{
m_ui.setupUi(this);
SettingsInterface* sif = dialog->getSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Achievements", "Enabled", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.richPresence, "Achievements", "RichPresence", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.challengeMode, "Achievements", "ChallengeMode", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboards, "Achievements", "Leaderboards", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.testMode, "Achievements", "TestMode", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialTestMode, "Achievements",
"UnofficialTestMode", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Achievements",
"SoundEffects", true);
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
tr("When enabled and logged in, PCSX2 will scan for achievements on game load."));
dialog->registerWidgetHelp(m_ui.testMode, tr("Enable Test Mode"), tr("Unchecked"),
tr("When enabled, PCSX2 will assume all achievements are locked and not send any "
"unlock notifications to the server."));
dialog->registerWidgetHelp(
m_ui.unofficialTestMode, tr("Test Unofficial Achievements"), tr("Unchecked"),
tr("When enabled, PCSX2 will list achievements from unofficial sets. Please note that these achievements are "
"not tracked by RetroAchievements, so they unlock every time."));
dialog->registerWidgetHelp(
m_ui.richPresence, tr("Enable Rich Presence"), tr("Unchecked"),
tr("When enabled, rich presence information will be collected and sent to the server where supported."));
dialog->registerWidgetHelp(m_ui.challengeMode, tr("Enable Hardcore Mode"), tr("Unchecked"),
tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."));
dialog->registerWidgetHelp(m_ui.leaderboards, tr("Enable Leaderboards"), tr("Checked"),
tr("Enables tracking and submission of leaderboards in supported games. If leaderboards are disabled, you will still "
"be able to view the leaderboard and scores, but no scores will be uploaded."));
dialog->registerWidgetHelp(
m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"),
tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions."));
connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::onChallengeModeStateChanged);
if (!m_dialog->isPerGameSettings())
{
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
connect(g_emu_thread, &EmuThread::onAchievementsRefreshed, this, &AchievementSettingsWidget::onAchievementsRefreshed);
updateLoginState();
// force a refresh of game info
Host::RunOnCPUThread(Host::OnAchievementsRefreshed);
}
else
{
// remove login and game info, not relevant for per-game
m_ui.verticalLayout->removeWidget(m_ui.gameInfoBox);
m_ui.gameInfoBox->deleteLater();
m_ui.gameInfoBox = nullptr;
m_ui.verticalLayout->removeWidget(m_ui.loginBox);
m_ui.loginBox->deleteLater();
m_ui.loginBox = nullptr;
}
updateEnableState();
}
AchievementSettingsWidget::~AchievementSettingsWidget() = default;
void AchievementSettingsWidget::updateEnableState()
{
const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false);
const bool challenge = m_dialog->getEffectiveBoolValue("Achievements", "ChallengeMode", false);
m_ui.testMode->setEnabled(enabled);
m_ui.unofficialTestMode->setEnabled(enabled);
m_ui.richPresence->setEnabled(enabled);
m_ui.challengeMode->setEnabled(enabled);
m_ui.leaderboards->setEnabled(enabled && challenge);
m_ui.soundEffects->setEnabled(enabled);
}
void AchievementSettingsWidget::onChallengeModeStateChanged()
{
if (!QtHost::IsVMValid())
return;
const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false);
const bool challenge = m_dialog->getEffectiveBoolValue("Achievements", "ChallengeMode", false);
if (!enabled || !challenge)
return;
// don't bother prompting if the game doesn't have achievements
auto lock = Achievements::GetLock();
if (!Achievements::HasActiveGame())
return;
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Reset System"),
tr("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?")) != QMessageBox::Yes)
{
return;
}
g_emu_thread->resetVM();
}
void AchievementSettingsWidget::updateLoginState()
{
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
const bool logged_in = !username.empty();
if (logged_in)
{
const u64 login_unix_timestamp = StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Achievements", "LoginTimestamp", "0")).value_or(0);
const QDateTime login_timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(login_unix_timestamp)));
m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.")
.arg(QString::fromStdString(username))
.arg(login_timestamp.toString(Qt::TextDate)));
m_ui.loginButton->setText(tr("Logout"));
}
else
{
m_ui.loginStatus->setText(tr("Not Logged In."));
m_ui.loginButton->setText(tr("Login..."));
}
m_ui.viewProfile->setEnabled(logged_in);
}
void AchievementSettingsWidget::onLoginLogoutPressed()
{
if (!Host::GetBaseStringSettingValue("Achievements", "Username").empty())
{
Host::RunOnCPUThread(Achievements::Logout, true);
updateLoginState();
return;
}
AchievementLoginDialog login(this);
int res = login.exec();
if (res != 0)
return;
updateLoginState();
}
void AchievementSettingsWidget::onViewProfilePressed()
{
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
if (username.empty())
return;
const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username)));
QtUtils::OpenURL(
QtUtils::GetRootWidget(this),
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
}
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total,
quint32 points)
{
m_ui.gameInfo->setText(game_info_string);
}

View File

@ -0,0 +1,42 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QtWidgets/QWidget>
#include "ui_AchievementSettingsWidget.h"
class SettingsDialog;
class AchievementSettingsWidget : public QWidget
{
Q_OBJECT
public:
explicit AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent);
~AchievementSettingsWidget();
private Q_SLOTS:
void updateEnableState();
void onChallengeModeStateChanged();
void onLoginLogoutPressed();
void onViewProfilePressed();
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
private:
void updateLoginState();
Ui::AchievementSettingsWidget m_ui;
SettingsDialog* m_dialog;
};

View File

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AchievementSettingsWidget</class>
<widget class="QWidget" name="AchievementSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>648</width>
<height>456</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Global Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QCheckBox" name="leaderboards">
<property name="text">
<string>Enable Leaderboards</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="enable">
<property name="text">
<string>Enable Achievements</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="richPresence">
<property name="text">
<string>Enable Rich Presence</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="challengeMode">
<property name="text">
<string>Enable Hardcore Mode</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="testMode">
<property name="text">
<string>Enable Test Mode</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="soundEffects">
<property name="text">
<string>Enable Sound Effects</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="unofficialTestMode">
<property name="text">
<string>Test Unofficial Achievements</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="loginBox">
<property name="title">
<string>Account</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<item>
<widget class="QLabel" name="loginStatus">
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="loginButton">
<property name="text">
<string>Login...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="viewProfile">
<property name="text">
<string>View Profile...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gameInfoBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>160</height>
</size>
</property>
<property name="title">
<string>Game Info</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="gameInfo">
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at &lt;a href=&quot;https://retroachievements.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;retroachievements.org&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;To view the achievement list in-game, press the hotkey for &lt;span style=&quot; font-weight:600;&quot;&gt;Open Pause Menu&lt;/span&gt; and select &lt;span style=&quot; font-weight:600;&quot;&gt;Achievements&lt;/span&gt; from the menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="margin">
<number>8</number>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -43,6 +43,11 @@
#include "MemoryCardSettingsWidget.h"
#include "SystemSettingsWidget.h"
#ifdef ENABLE_ACHIEVEMENTS
#include "AchievementSettingsWidget.h"
#include "pcsx2/Frontend/Achievements.h"
#endif
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QTextEdit>
@ -138,6 +143,34 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
tr("<strong>Folder Settings</strong><hr>These options control where PCSX2 will save runtime data files."));
}
{
QString title = tr("Achievements");
QString icon_text(QStringLiteral("trophy-line"));
QString help_text = tr(
"<strong>Achievements Settings</strong><hr>"
"These options control the RetroAchievements implementation in PCSX2, allowing you to earn achievements in your games.");
#ifdef ENABLE_ACHIEVEMENTS
if (Achievements::IsUsingRAIntegration())
{
QLabel* placeholder_label =
new QLabel(tr("RAIntegration is being used, built-in RetroAchievements support is disabled."),
m_ui.settingsContainer);
placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text));
}
else
{
addWidget((m_achievement_settings = new AchievementSettingsWidget(this, m_ui.settingsContainer)),
std::move(title), std::move(icon_text), std::move(help_text));
}
#else
QLabel* placeholder_label =
new QLabel(tr("This PCSX2 build was not compiled with RetroAchievements support."), m_ui.settingsContainer);
placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text));
#endif
}
m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_ui.settingsCategory->setCurrentRow(0);
m_ui.settingsContainer->setCurrentIndex(0);

View File

@ -41,6 +41,7 @@ class AudioSettingsWidget;
class MemoryCardSettingsWidget;
class FolderSettingsWidget;
class DEV9SettingsWidget;
class AchievementSettingsWidget;
class SettingsDialog final : public QDialog
{
@ -68,6 +69,7 @@ public:
__fi MemoryCardSettingsWidget* getMemoryCardSettingsWidget() const { return m_memory_card_settings; }
__fi FolderSettingsWidget* getFolderSettingsWidget() const { return m_folder_settings; }
__fi DEV9SettingsWidget* getDEV9SettingsWidget() const { return m_dev9_settings; }
__fi AchievementSettingsWidget* getAchievementSettingsWidget() const { return m_achievement_settings; }
void registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text);
bool eventFilter(QObject* object, QEvent* event) override;
@ -104,7 +106,7 @@ protected:
private:
enum : u32
{
MAX_SETTINGS_WIDGETS = 11
MAX_SETTINGS_WIDGETS = 12
};
void setupUi(const GameList::Entry* game);
@ -127,6 +129,7 @@ private:
MemoryCardSettingsWidget* m_memory_card_settings = nullptr;
FolderSettingsWidget* m_folder_settings = nullptr;
DEV9SettingsWidget* m_dev9_settings = nullptr;
AchievementSettingsWidget* m_achievement_settings = nullptr;
std::array<QString, MAX_SETTINGS_WIDGETS> m_category_help_text;

View File

@ -51,7 +51,7 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Debug))">PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Devel))">PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Release))">NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -162,6 +162,8 @@
<ClCompile Include="Settings\DEV9UiCommon.cpp" />
<ClCompile Include="Settings\HddCreateQt.cpp" />
<ClCompile Include="Settings\GameSummaryWidget.cpp" />
<ClCompile Include="Settings\AchievementLoginDialog.cpp" />
<ClCompile Include="Settings\AchievementSettingsWidget.cpp" />
<ClCompile Include="GameList\GameListModel.cpp" />
<ClCompile Include="GameList\GameListRefreshThread.cpp" />
<ClCompile Include="GameList\GameListWidget.cpp" />
@ -202,6 +204,8 @@
<ClInclude Include="Settings\HddCreateQt.h" />
<QtMoc Include="Settings\GameSummaryWidget.h" />
<QtMoc Include="Settings\CreateMemoryCardDialog.h" />
<QtMoc Include="Settings\AchievementLoginDialog.h" />
<QtMoc Include="Settings\AchievementSettingsWidget.h" />
<QtMoc Include="GameList\GameListModel.h" />
<QtMoc Include="GameList\GameListWidget.h" />
<QtMoc Include="GameList\GameListRefreshThread.h" />
@ -247,6 +251,8 @@
<ClCompile Include="$(IntDir)Settings\moc_DEV9SettingsWidget.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_DEV9UiCommon.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_GameSummaryWidget.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_AchievementLoginDialog.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_AchievementSettingsWidget.cpp" />
<ClCompile Include="$(IntDir)GameList\moc_GameListModel.cpp" />
<ClCompile Include="$(IntDir)GameList\moc_GameListRefreshThread.cpp" />
<ClCompile Include="$(IntDir)GameList\moc_GameListWidget.cpp" />
@ -341,6 +347,12 @@
<QtUi Include="Tools\InputRecording\NewInputRecordingDlg.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\AchievementLoginDialog.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\AchievementSettingsWidget.ui">
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<ItemGroup>
<QtUi Include="Settings\ControllerMacroEditWidget.ui">

View File

@ -229,6 +229,18 @@
<ClCompile Include="$(IntDir)moc_QtProgressCallback.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="Settings\AchievementSettingsWidget.cpp">
<Filter>Settings</Filter>
</ClCompile>
<ClCompile Include="Settings\AchievementLoginDialog.cpp">
<Filter>Settings</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Settings\moc_AchievementSettingsWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Settings\moc_AchievementLoginDialog.cpp">
<Filter>moc</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
@ -335,6 +347,12 @@
<QtMoc Include="QtHost.h" />
<QtMoc Include="CoverDownloadDialog.h" />
<QtMoc Include="QtProgressCallback.h" />
<QtMoc Include="Settings\AchievementLoginDialog.h">
<Filter>Settings</Filter>
</QtMoc>
<QtMoc Include="Settings\AchievementSettingsWidget.h">
<Filter>Settings</Filter>
</QtMoc>
</ItemGroup>
<ItemGroup>
<QtResource Include="resources\resources.qrc">
@ -419,6 +437,12 @@
<QtUi Include="Settings\ControllerMacroEditWidget.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\AchievementSettingsWidget.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\AchievementLoginDialog.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="CoverDownloadDialog.ui" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M4 15h2v5h12V4H6v5H4V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6zm6-4V8l5 4-5 4v-3H2v-2h8z" fill="#000000"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 286 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M13 16.938V19h5v2H6v-2h5v-2.062A8.001 8.001 0 0 1 4 9V3h16v6a8.001 8.001 0 0 1-7 7.938zM6 5v4a6 6 0 1 0 12 0V5H6zM1 5h2v4H1V5zm20 0h2v4h-2V5z" fill="#000000"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 312 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M4 15h2v5h12V4H6v5H4V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6zm6-4V8l5 4-5 4v-3H2v-2h8z" fill="#ffffff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 286 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M13 16.938V19h5v2H6v-2h5v-2.062A8.001 8.001 0 0 1 4 9V3h16v6a8.001 8.001 0 0 1-7 7.938zM6 5v4a6 6 0 1 0 12 0V5H6zM1 5h2v4H1V5zm20 0h2v4h-2V5z" fill="#ffffff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 312 B

View File

@ -37,6 +37,7 @@
<file>icons/black/svg/keyboard-line.svg</file>
<file>icons/black/svg/layout-grid-line.svg</file>
<file>icons/black/svg/list-check.svg</file>
<file>icons/black/svg/login-box-line.svg</file>
<file>icons/black/svg/pause-line.svg</file>
<file>icons/black/svg/play-line.svg</file>
<file>icons/black/svg/price-tag-3-line.svg</file>
@ -47,6 +48,7 @@
<file>icons/black/svg/sd-card-line.svg</file>
<file>icons/black/svg/settings-3-line.svg</file>
<file>icons/black/svg/shut-down-line.svg</file>
<file>icons/black/svg/trophy-line.svg</file>
<file>icons/black/svg/tv-2-line.svg</file>
<file>icons/black/svg/volume-up-line.svg</file>
<file>icons/black/svg/window-2-line.svg</file>
@ -92,6 +94,7 @@
<file>icons/white/svg/keyboard-line.svg</file>
<file>icons/white/svg/layout-grid-line.svg</file>
<file>icons/white/svg/list-check.svg</file>
<file>icons/white/svg/login-box-line.svg</file>
<file>icons/white/svg/pause-line.svg</file>
<file>icons/white/svg/play-line.svg</file>
<file>icons/white/svg/price-tag-3-line.svg</file>
@ -102,6 +105,7 @@
<file>icons/white/svg/sd-card-line.svg</file>
<file>icons/white/svg/settings-3-line.svg</file>
<file>icons/white/svg/shut-down-line.svg</file>
<file>icons/white/svg/trophy-line.svg</file>
<file>icons/white/svg/tv-2-line.svg</file>
<file>icons/white/svg/volume-up-line.svg</file>
<file>icons/white/svg/window-2-line.svg</file>

75
pcsx2/Achievements.h Normal file
View File

@ -0,0 +1,75 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Types.h"
#include <vector>
namespace Achievements
{
#ifdef ENABLE_ACHIEVEMENTS
// Implemented in Host.
extern bool OnReset();
extern void LoadState(const u8* state_data, u32 state_data_size);
extern std::vector<u8> SaveState();
extern void GameChanged(u32 crc);
/// Re-enables hardcode mode if it is enabled in the settings.
extern void ResetChallengeMode();
/// Forces hardcore mode off until next reset.
extern void DisableChallengeMode();
/// Prompts the user to disable hardcore mode, if they agree, returns true.
extern bool ConfirmChallengeModeDisable(const char* trigger);
/// Returns true if features such as save states should be disabled.
extern bool ChallengeModeActive();
#else
// Make noops when compiling without cheevos.
static inline bool OnReset()
{
return true;
}
static inline void LoadState(const u8* state_data, u32 state_data_size)
{
}
static inline std::vector<u8> SaveState()
{
return {};
}
static inline void GameChanged()
{
}
static constexpr inline bool ChallengeModeActive()
{
return false;
}
static inline void ResetChallengeMode() {}
static inline void DisableChallengeMode() {}
static inline bool ConfirmChallengeModeDisable(const char* trigger)
{
return true;
}
#endif
} // namespace Achievements

View File

@ -199,6 +199,7 @@ set(pcsx2Sources
# Main pcsx2 header
set(pcsx2Headers
Achievements.h
AsyncFileReader.h
Cache.h
Common.h
@ -1090,6 +1091,21 @@ if(PCSX2_CORE)
HostSettings.h
INISettingsInterface.h
VMManager.h)
if(USE_ACHIEVEMENTS)
list(APPEND pcsx2FrontendSources
Frontend/Achievements.cpp
)
list(APPEND pcsx2FrontendHeaders
Frontend/Achievements.h
)
target_compile_definitions(PCSX2_FLAGS INTERFACE
ENABLE_ACHIEVEMENTS
)
target_link_libraries(PCSX2_FLAGS INTERFACE
rcheevos
)
endif()
endif()
# gui sources

View File

@ -941,6 +941,39 @@ struct Pcsx2Config
MemoryCardType Type; // the memory card implementation that should be used
};
// ------------------------------------------------------------------------
#ifdef ENABLE_ACHIEVEMENTS
struct AchievementsOptions
{
BITFIELD32()
bool
Enabled : 1,
TestMode : 1,
UnofficialTestMode : 1,
RichPresence : 1,
ChallengeMode : 1,
Leaderboards : 1,
SoundEffects : 1;
BITFIELD_END
AchievementsOptions();
void LoadSave(SettingsWrapper& wrap);
bool operator==(const AchievementsOptions& right) const
{
return OpEqu(bitset);
}
bool operator!=(const AchievementsOptions& right) const
{
return !this->operator==(right);
}
};
#endif
// ------------------------------------------------------------------------
BITFIELD32()
bool
CdvdVerboseReads : 1, // enables cdvd read activity verbosely dumped to the console
@ -993,6 +1026,10 @@ struct Pcsx2Config
FilenameOptions BaseFilenames;
#ifdef ENABLE_ACHIEVEMENTS
AchievementsOptions Achievements;
#endif
// Memorycard options - first 2 are default slots, last 6 are multitap 1 and 2
// slots (3 each)
McdOptions Mcd[8];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,171 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "pcsx2/Achievements.h"
#include "Config.h"
#include <functional>
#include <mutex>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
namespace Achievements
{
enum class AchievementCategory : u8
{
Local = 0,
Core = 3,
Unofficial = 5
};
struct Achievement
{
u32 id;
std::string title;
std::string description;
std::string memaddr;
std::string badge_name;
// badge paths are mutable because they're resolved when they're needed.
mutable std::string locked_badge_path;
mutable std::string unlocked_badge_path;
u32 points;
AchievementCategory category;
bool locked;
bool active;
bool primed;
};
struct Leaderboard
{
u32 id;
std::string title;
std::string description;
int format;
};
struct LeaderboardEntry
{
std::string user;
std::string formatted_score;
u32 rank;
bool is_self;
};
// RAIntegration only exists for Windows, so no point checking it on other platforms.
#ifdef ENABLE_RAINTEGRATION
bool IsUsingRAIntegration();
#else
static __fi bool IsUsingRAIntegration() { return false; }
#endif
bool IsActive();
bool IsLoggedIn();
bool ChallengeModeActive();
bool LeaderboardsActive();
bool IsTestModeActive();
bool IsUnofficialTestModeActive();
bool IsRichPresenceEnabled();
bool HasActiveGame();
u32 GetGameID();
/// Acquires the achievements lock. Must be held when accessing any achievement state from another thread.
std::unique_lock<std::recursive_mutex> GetLock();
void Initialize();
void UpdateSettings(const Pcsx2Config::AchievementsOptions& old_config);
/// Called when the system is being reset. If it returns false, the reset should be aborted.
bool OnReset();
/// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted.
bool Shutdown();
/// Called when the system is being paused and resumed.
void OnPaused(bool paused);
/// Called once a frame at vsync time on the CPU thread.
void VSyncUpdate();
/// Called to process pending HTTP requests when the VM is paused, because otherwise the vsync event won't fire.
void ProcessPendingHTTPRequestsFromGSThread();
void LoadState(const u8* state_data, u32 state_data_size);
std::vector<u8> SaveState();
/// Returns true if the current game has any achievements or leaderboards.
/// Does not need to have the lock held.
bool SafeHasAchievementsOrLeaderboards();
const std::string& GetUsername();
const std::string& GetRichPresenceString();
bool LoginAsync(const char* username, const char* password);
bool Login(const char* username, const char* password);
void Logout();
void GameChanged(u32 crc);
const std::string& GetGameTitle();
const std::string& GetGameIcon();
bool EnumerateAchievements(std::function<bool(const Achievement&)> callback);
u32 GetUnlockedAchiementCount();
u32 GetAchievementCount();
u32 GetMaximumPointsForGame();
u32 GetCurrentPointsForGame();
bool EnumerateLeaderboards(std::function<bool(const Leaderboard&)> callback);
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry&)> callback);
const Leaderboard* GetLeaderboardByID(u32 id);
u32 GetLeaderboardCount();
bool IsLeaderboardTimeType(const Leaderboard& leaderboard);
const Achievement* GetAchievementByID(u32 id);
std::pair<u32, u32> GetAchievementProgress(const Achievement& achievement);
std::string GetAchievementProgressText(const Achievement& achievement);
const std::string& GetAchievementBadgePath(const Achievement& achievement, bool download_if_missing = true);
std::string GetAchievementBadgeURL(const Achievement& achievement);
u32 GetPrimedAchievementCount();
#ifdef ENABLE_RAINTEGRATION
void SwitchToRAIntegration();
namespace RAIntegration
{
void MainWindowChanged(void* new_handle);
void GameChanged();
std::vector<std::tuple<int, std::string, bool>> GetMenuItems();
void ActivateMenuItem(int item);
} // namespace RAIntegration
#endif
} // namespace Achievements
/// Functions implemented in the frontend.
namespace Host
{
void OnAchievementsRefreshed();
void OnAchievementsChallengeModeChanged();
} // namespace Host

View File

@ -36,6 +36,10 @@
#include "Sio.h"
#include "VMManager.h"
#ifdef ENABLE_ACHIEVEMENTS
#include "Frontend/Achievements.h"
#endif
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <KnownFolders.h>
@ -183,6 +187,12 @@ void CommonHost::LoadStartupSettings()
EmuFolders::LoadConfig(*bsi);
EmuFolders::EnsureFoldersExist();
UpdateLogging(*bsi);
#ifdef ENABLE_RAINTEGRATION
// RAIntegration switch must happen before the UI is created.
if (Host::GetBaseBoolSettingValue("Achievements", "UseRAIntegration", false))
Achievements::SwitchToRAIntegration();
#endif
}
void CommonHost::SetDefaultSettings(SettingsInterface& si, bool folders, bool core, bool controllers, bool hotkeys, bool ui)
@ -222,10 +232,19 @@ void CommonHost::CPUThreadInitialize()
// We want settings loaded so we choose the correct renderer for big picture mode.
// This also sorts out input sources.
VMManager::LoadSettings();
#ifdef ENABLE_ACHIEVEMENTS
if (EmuConfig.Achievements.Enabled)
Achievements::Initialize();
#endif
}
void CommonHost::CPUThreadShutdown()
{
#ifdef ENABLE_ACHIEVEMENTS
Achievements::Shutdown();
#endif
InputManager::CloseSources();
VMManager::WaitForSaveStateFlush();
VMManager::Internal::ReleaseMemory();
@ -238,12 +257,18 @@ void CommonHost::LoadSettings(SettingsInterface& si, std::unique_lock<std::mutex
SettingsInterface* binding_si = Host::GetSettingsInterfaceForBindings();
InputManager::ReloadSources(si, lock);
InputManager::ReloadBindings(si, *binding_si);
UpdateLogging(si);
}
void CommonHost::CheckForSettingsChanges(const Pcsx2Config& old_config)
{
// Nothing yet.
#ifdef ENABLE_ACHIEVEMENTS
if (EmuConfig.Achievements != old_config.Achievements)
Achievements::UpdateSettings(old_config.Achievements);
#endif
FullscreenUI::CheckForConfigChanges(old_config);
}
void CommonHost::OnVMStarting()
@ -265,11 +290,19 @@ void CommonHost::OnVMPaused()
{
InputManager::PauseVibration();
FullscreenUI::OnVMPaused();
#ifdef ENABLE_ACHIEVEMENTS
Achievements::OnPaused(true);
#endif
}
void CommonHost::OnVMResumed()
{
FullscreenUI::OnVMResumed();
#ifdef ENABLE_ACHIEVEMENTS
Achievements::OnPaused(false);
#endif
}
void CommonHost::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, u32 game_crc)
@ -280,10 +313,19 @@ void CommonHost::OnGameChanged(const std::string& disc_path, const std::string&
FullscreenUI::OnRunningGameChanged(std::move(disc_path), std::move(game_serial), std::move(game_name), game_crc);
});
}
#ifdef ENABLE_ACHIEVEMENTS
Achievements::GameChanged(game_crc);
#endif
}
void CommonHost::CPUThreadVSync()
{
#ifdef ENABLE_ACHIEVEMENTS
if (Achievements::IsActive())
Achievements::VSyncUpdate();
#endif
InputManager::PollSources();
}

View File

@ -26,6 +26,10 @@
#include "Recording/InputRecordingControls.h"
#include "VMManager.h"
#ifdef ENABLE_ACHIEVEMENTS
#include "Frontend/Achievements.h"
#endif
static s32 s_current_save_slot = 1;
static std::optional<LimiterModeType> s_limiter_mode_prior_to_hold_interaction;
@ -121,6 +125,16 @@ DEFINE_HOTKEY("OpenPauseMenu", "System", "Open Pause Menu", [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
FullscreenUI::OpenPauseMenu();
})
#ifdef ENABLE_ACHIEVEMENTS
DEFINE_HOTKEY("OpenAchievementsList", "System", "Open Achievements List", [](s32 pressed) {
if (!pressed)
FullscreenUI::OpenAchievementsWindow();
})
DEFINE_HOTKEY("OpenLeaderboardsList", "System", "Open Leaderboards List", [](s32 pressed) {
if (!pressed)
FullscreenUI::OpenLeaderboardsWindow();
})
#endif
DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
VMManager::SetPaused(VMManager::GetState() != VMState::Paused);

File diff suppressed because it is too large Load Diff

View File

@ -21,17 +21,22 @@
class HostDisplayTexture;
struct Pcsx2Config;
namespace FullscreenUI
{
bool Initialize();
bool IsInitialized();
bool HasActiveWindow();
void CheckForConfigChanges(const Pcsx2Config& old_config);
void OnVMStarted();
void OnVMPaused();
void OnVMResumed();
void OnVMDestroyed();
void OnRunningGameChanged(std::string path, std::string serial, std::string title, u32 crc);
void OpenPauseMenu();
bool OpenAchievementsWindow();
bool OpenLeaderboardsWindow();
void Shutdown();
void Render();

View File

@ -418,7 +418,7 @@ ImFont* ImGuiManager::AddFixedFont(float size)
bool ImGuiManager::AddIconFonts(float size)
{
static constexpr ImWchar range_fa[] = { 0xf001,0xf002,0xf005,0xf005,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf021,0xf025,0xf025,0xf028,0xf028,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf059,0xf059,0xf05e,0xf05e,0xf065,0xf065,0xf067,0xf067,0xf06a,0xf06a,0xf071,0xf071,0xf07b,0xf07c,0xf085,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0d0,0xf0d0,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf119,0xf119,0xf11b,0xf11c,0xf121,0xf121,0xf133,0xf133,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf188,0xf188,0xf192,0xf192,0xf1c9,0xf1c9,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f5,0xf2f5,0xf302,0xf302,0xf3fd,0xf3fd,0xf410,0xf410,0xf466,0xf466,0xf479,0xf479,0xf517,0xf517,0xf51f,0xf51f,0xf543,0xf543,0xf545,0xf545,0xf547,0xf548,0xf552,0xf552,0xf5aa,0xf5aa,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf756,0xf756,0xf7c2,0xf7c2,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 };
static constexpr ImWchar range_fa[] = { 0xf001,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf021,0xf023,0xf023,0xf025,0xf025,0xf028,0xf028,0xf02d,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf059,0xf059,0xf05e,0xf05e,0xf065,0xf065,0xf067,0xf067,0xf06a,0xf06a,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f1,0xf0f1,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf119,0xf119,0xf11b,0xf11c,0xf121,0xf121,0xf133,0xf133,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf188,0xf188,0xf191,0xf192,0xf1c9,0xf1c9,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf2f5,0xf2f5,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf466,0xf466,0xf479,0xf479,0xf500,0xf500,0xf517,0xf517,0xf51f,0xf51f,0xf543,0xf543,0xf545,0xf545,0xf547,0xf548,0xf552,0xf552,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf756,0xf756,0xf7c2,0xf7c2,0xf807,0xf807,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 };
ImFontConfig cfg;
cfg.MergeMode = true;

View File

@ -1016,6 +1016,34 @@ void Pcsx2Config::FramerateOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapEntry(SlomoScalar);
}
#ifdef ENABLE_ACHIEVEMENTS
Pcsx2Config::AchievementsOptions::AchievementsOptions()
{
Enabled = false;
TestMode = false;
UnofficialTestMode = false;
RichPresence = true;
ChallengeMode = false;
Leaderboards = true;
SoundEffects = true;
}
void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap)
{
SettingsWrapSection("Achievements");
SettingsWrapBitBool(Enabled);
SettingsWrapBitBool(TestMode);
SettingsWrapBitBool(UnofficialTestMode);
SettingsWrapBitBool(RichPresence);
SettingsWrapBitBool(ChallengeMode);
SettingsWrapBitBool(Leaderboards);
SettingsWrapBitBool(SoundEffects);
}
#endif
Pcsx2Config::Pcsx2Config()
{
bitset = 0;
@ -1102,6 +1130,10 @@ void Pcsx2Config::LoadSave(SettingsWrapper& wrap)
Debugger.LoadSave(wrap);
Trace.LoadSave(wrap);
#ifdef ENABLE_ACHIEVEMENTS
Achievements.LoadSave(wrap);
#endif
SettingsWrapEntry(GzipIsoIndexTemplate);
// For now, this in the derived config for backwards ini compatibility.

View File

@ -51,6 +51,10 @@
#include "VMManager.h"
#endif
#ifdef ENABLE_ACHIEVEMENTS
#include "Frontend/Achievements.h"
#endif
#include "fmt/core.h"
#include <csetjmp>
@ -429,6 +433,9 @@ static void SysState_ComponentFreezeOutRoot(void* dest, SysState_Component comp)
static void SysState_ComponentFreezeIn(zip_file_t* zf, SysState_Component comp)
{
if (!zf)
return;
freezeData fP = { 0, nullptr };
if (comp.freeze(FreezeAction::Size, &fP) != 0)
fP.size = 0;
@ -653,7 +660,44 @@ public:
bool IsRequired() const { return true; }
};
#ifdef ENABLE_ACHIEVEMENTS
class SaveStateEntry_Achievements : public BaseSavestateEntry
{
virtual ~SaveStateEntry_Achievements() override = default;
const char* GetFilename() const override { return "Achievements.bin"; }
void FreezeIn(zip_file_t* zf) const override
{
if (!Achievements::IsActive())
return;
std::optional<std::vector<u8>> data;
if (zf)
data = ReadBinaryFileInZip(zf);
if (data.has_value() && !data->empty())
Achievements::LoadState(data->data(), data->size());
else
Achievements::LoadState(nullptr, 0);
}
void FreezeOut(SaveStateBase& writer) const override
{
if (!Achievements::IsActive())
return;
std::vector<u8> data(Achievements::SaveState());
if (!data.empty())
{
writer.PrepBlock(static_cast<int>(data.size()));
std::memcpy(writer.GetBlockPtr(), data.data(), data.size());
writer.CommitBlock(static_cast<int>(data.size()));
}
}
bool IsRequired() const override { return false; }
};
#endif
// (cpuRegs, iopRegs, VPU/GIF/DMAC structures should all remain as part of a larger unified
// block, since they're all PCSX2-dependent and having separate files in the archie for them
@ -676,6 +720,9 @@ static const std::unique_ptr<BaseSavestateEntry> SavestateEntries[] = {
#endif
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_PAD),
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_GS),
#ifdef ENABLE_ACHIEVEMENTS
std::unique_ptr<BaseSavestateEntry>(new SaveStateEntry_Achievements),
#endif
};
std::unique_ptr<ArchiveEntryList> SaveState_DownloadState()
@ -1000,7 +1047,7 @@ static void CheckVersion(const std::string& filename, zip_t* zf)
.SetUserMsg("Cannot load this savestate. The state is an unsupported version.");
}
static zip_int64_t CheckFileExistsInState(zip_t* zf, const char* name)
static zip_int64_t CheckFileExistsInState(zip_t* zf, const char* name, bool required)
{
zip_int64_t index = zip_name_locate(zf, name, /*ZIP_FL_NOCASE*/ 0);
if (index >= 0)
@ -1009,7 +1056,11 @@ static zip_int64_t CheckFileExistsInState(zip_t* zf, const char* name)
return index;
}
if (required)
Console.WriteLn(Color_Red, " ... not found '%s'!", name);
else
DevCon.WriteLn(Color_Red, " ... not found '%s'!", name);
return index;
}
@ -1048,15 +1099,16 @@ void SaveState_UnzipFromDisk(const std::string& filename)
CheckVersion(filename, zf.get());
// check that all parts are included
const s64 internal_index = CheckFileExistsInState(zf.get(), EntryFilename_InternalStructures);
const s64 internal_index = CheckFileExistsInState(zf.get(), EntryFilename_InternalStructures, true);
s64 entryIndices[std::size(SavestateEntries)];
// Log any parts and pieces that are missing, and then generate an exception.
bool throwIt = (internal_index < 0);
for (u32 i = 0; i < std::size(SavestateEntries); i++)
{
entryIndices[i] = CheckFileExistsInState(zf.get(), SavestateEntries[i]->GetFilename());
if (entryIndices[i] < 0 && SavestateEntries[i]->IsRequired())
const bool required = SavestateEntries[i]->IsRequired();
entryIndices[i] = CheckFileExistsInState(zf.get(), SavestateEntries[i]->GetFilename(), required);
if (entryIndices[i] < 0 && required)
throwIt = true;
}
@ -1071,7 +1123,10 @@ void SaveState_UnzipFromDisk(const std::string& filename)
for (u32 i = 0; i < std::size(SavestateEntries); ++i)
{
if (entryIndices[i] < 0)
{
SavestateEntries[i]->FreezeIn(nullptr);
continue;
}
auto zff = zip_fopen_index_managed(zf.get(), entryIndices[i], 0);
if (!zff)

View File

@ -30,6 +30,7 @@
#include "common/Threading.h"
#include "fmt/core.h"
#include "Achievements.h"
#include "Counters.h"
#include "CDVD/CDVD.h"
#include "DEV9/DEV9.h"
@ -80,6 +81,7 @@ namespace VMManager
static void CheckForSPU2ConfigChanges(const Pcsx2Config& old_config);
static void CheckForDEV9ConfigChanges(const Pcsx2Config& old_config);
static void CheckForMemoryCardConfigChanges(const Pcsx2Config& old_config);
static void EnforceAchievementsChallengeModeSettings();
static void WarnAboutUnsafeSettings();
static bool AutoDetectSource(const std::string& filename);
@ -183,7 +185,8 @@ void VMManager::SetState(VMState state)
if (state != VMState::Stopping && (state == VMState::Paused || old_state == VMState::Paused))
{
if (state == VMState::Paused)
const bool paused = (state == VMState::Paused);
if (paused)
{
if (THREAD_VU1)
vu1Thread.WaitVU();
@ -312,6 +315,9 @@ void VMManager::LoadSettings()
PAD::LoadConfig(*si);
Host::LoadSettings(*si, lock);
// Achievements hardcore mode disallows setting some configuration options.
EnforceAchievementsChallengeModeSettings();
// Remove any user-specified hacks in the config (we don't want stale/conflicting values when it's globally disabled).
EmuConfig.GS.MaskUserHacks();
EmuConfig.GS.MaskUpscalingHacks();
@ -532,7 +538,7 @@ void VMManager::LoadPatches(const std::string& serial, u32 crc, bool show_messag
// wide screen patches
if (EmuConfig.EnableWideScreenPatches && crc != 0)
{
if (s_active_widescreen_patches = LoadPatchesFromDir(crc_string, EmuFolders::CheatsWS, "Widescreen hacks", false))
if (!Achievements::ChallengeModeActive() && (s_active_widescreen_patches = LoadPatchesFromDir(crc_string, EmuFolders::CheatsWS, "Widescreen hacks", false) > 0))
{
Console.WriteLn(Color_Gray, "Found widescreen patches in the cheats_ws folder --> skipping cheats_ws.zip");
}
@ -574,7 +580,7 @@ void VMManager::LoadPatches(const std::string& serial, u32 crc, bool show_messag
// no-interlacing patches
if (EmuConfig.EnableNoInterlacingPatches && crc != 0)
{
if (s_active_no_interlacing_patches = LoadPatchesFromDir(crc_string, EmuFolders::CheatsNI, "No-interlacing patches", false))
if (!Achievements::ChallengeModeActive() && (s_active_no_interlacing_patches = LoadPatchesFromDir(crc_string, EmuFolders::CheatsNI, "No-interlacing patches", false)) > 0)
{
Console.WriteLn(Color_Gray, "Found no-interlacing patches in the cheats_ni folder --> skipping cheats_ni.zip");
}
@ -781,6 +787,16 @@ bool VMManager::ApplyBootParameters(VMBootParameters params, std::string* state_
}
}
#ifdef ENABLE_ACHIEVEMENTS
// Check for resuming with hardcore mode.
Achievements::ResetChallengeMode();
if (!state_to_load->empty() && Achievements::ChallengeModeActive() &&
!Achievements::ConfirmChallengeModeDisable("Resuming state"))
{
return false;
}
#endif
// resolve source type
if (params.source_type.has_value())
{
@ -1085,6 +1101,11 @@ void VMManager::Shutdown(bool save_resume_state)
void VMManager::Reset()
{
#ifdef ENABLE_ACHIEVEMENTS
if (!Achievements::OnReset())
return;
#endif
const bool game_was_started = g_GameStarted;
s_active_game_fixes = 0;
@ -1256,6 +1277,14 @@ void VMManager::WaitForSaveStateFlush()
bool VMManager::LoadState(const char* filename)
{
#ifdef ENABLE_ACHIEVEMENTS
if (Achievements::ChallengeModeActive() &&
!Achievements::ConfirmChallengeModeDisable("Loading state"))
{
return false;
}
#endif
// TODO: Save the current state so we don't need to reset.
if (DoLoadState(filename))
return true;
@ -1273,6 +1302,14 @@ bool VMManager::LoadStateFromSlot(s32 slot)
return false;
}
#ifdef ENABLE_ACHIEVEMENTS
if (Achievements::ChallengeModeActive() &&
!Achievements::ConfirmChallengeModeDisable("Loading state"))
{
return false;
}
#endif
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN, fmt::format("Loading state from slot {}...", slot), 5.0f);
return DoLoadState(filename.c_str());
}
@ -1313,6 +1350,11 @@ void VMManager::FrameAdvance(u32 num_frames /*= 1*/)
if (!HasValidVM())
return;
#ifdef ENABLE_ACHIEVEMENTS
if (Achievements::ChallengeModeActive() && !Achievements::ConfirmChallengeModeDisable("Frame advancing"))
return;
#endif
s_frame_advance_count = num_frames;
SetState(VMState::Running);
}
@ -1708,6 +1750,41 @@ void VMManager::SetDefaultSettings(SettingsInterface& si)
SetHardwareDependentDefaultSettings(si);
}
void VMManager::EnforceAchievementsChallengeModeSettings()
{
if (!Achievements::ChallengeModeActive())
return;
static constexpr auto ClampSpeed = [](float& rate) {
if (rate > 0.0f && rate < 1.0f)
rate = 1.0f;
};
// Can't use slow motion.
ClampSpeed(EmuConfig.Framerate.NominalScalar);
ClampSpeed(EmuConfig.Framerate.TurboScalar);
ClampSpeed(EmuConfig.Framerate.SlomoScalar);
// Can't use cheats.
if (EmuConfig.EnableCheats)
{
Host::AddKeyedOSDMessage("ChallengeDisableCheats", "Cheats have been disabled due to achievements hardcore mode.", 10.0f);
EmuConfig.EnableCheats = false;
}
// Input recording/playback is probably an issue.
EmuConfig.EnableRecordingTools = false;
EmuConfig.EnablePINE = false;
// Framerates should be at default.
EmuConfig.GS.FramerateNTSC = Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_NTSC;
EmuConfig.GS.FrameratePAL = Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL;
// You can overclock, but not underclock (since that might slow down the game and make it easier).
EmuConfig.Speedhacks.EECycleRate = std::max<decltype(EmuConfig.Speedhacks.EECycleRate)>(EmuConfig.Speedhacks.EECycleRate, 0);
EmuConfig.Speedhacks.EECycleSkip = 0;
}
void VMManager::WarnAboutUnsafeSettings()
{
std::string messages;

View File

@ -50,13 +50,14 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rcheevos\rcheevos\include;$(SolutionDir)3rdparty\rainterface</AdditionalIncludeDirectories>
<ExceptionHandling>Async</ExceptionHandling>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>PrecompiledHeader.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:externConstexpr %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;ZIP_STATIC;LZMA_API_STATIC;BUILD_DX=1;ENABLE_OPENGL;ENABLE_VULKAN;SPU2X_CUBEB;SDL_BUILD;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;ZIP_STATIC;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_OPENGL;ENABLE_VULKAN;SPU2X_CUBEB;SDL_BUILD;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Debug))">PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Devel))">PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Release))">NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -205,6 +206,7 @@
<ClCompile Include="Frontend\LayeredSettingsInterface.cpp" />
<ClCompile Include="Frontend\LogSink.cpp" />
<ClCompile Include="Frontend\OpenGLHostDisplay.cpp" />
<ClCompile Include="Frontend\Achievements.cpp" />
<ClCompile Include="Frontend\SDLInputSource.cpp" />
<ClCompile Include="Frontend\VulkanHostDisplay.cpp" />
<ClCompile Include="Frontend\XInputSource.cpp" />
@ -453,6 +455,7 @@
<ClCompile Include="CDVD\IsoFS\IsoFSCDVD.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Achievements.h" />
<ClInclude Include="AsyncFileReader.h" />
<ClInclude Include="CDVD\CDVDdiscReader.h" />
<ClInclude Include="CDVD\ChunksCache.h" />
@ -528,6 +531,7 @@
<ClInclude Include="Frontend\LayeredSettingsInterface.h" />
<ClInclude Include="Frontend\LogSink.h" />
<ClInclude Include="Frontend\OpenGLHostDisplay.h" />
<ClInclude Include="Frontend\Achievements.h" />
<ClInclude Include="Frontend\SDLInputSource.h" />
<ClInclude Include="Frontend\VulkanHostDisplay.h" />
<ClInclude Include="Frontend\XInputSource.h" />
@ -807,6 +811,12 @@
<ProjectReference Include="..\3rdparty\cpuinfo\cpuinfo.vcxproj">
<Project>{7e183337-a7e9-460c-9d3d-568bc9f9bcc1}</Project>
</ProjectReference>
<ProjectReference Include="..\3rdparty\rainterface\rainterface.vcxproj">
<Project>{95dd0a0c-d14d-4cff-a593-820ef26efcc8}</Project>
</ProjectReference>
<ProjectReference Include="..\3rdparty\rcheevos\rcheevos.vcxproj">
<Project>{6d5b5ad9-1525-459b-939f-a5e1082af6b3}</Project>
</ProjectReference>
<ProjectReference Include="..\3rdparty\sdl2\SDL.vcxproj">
<Project>{812b4434-fd6b-4cb2-8865-5fd8eb34b046}</Project>
</ProjectReference>

View File

@ -1287,6 +1287,9 @@
<ClCompile Include="Frontend\CommonHotkeys.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\Achievements.cpp">
<Filter>Host</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Patch.h">
@ -2140,6 +2143,10 @@
<ClInclude Include="Frontend\CommonHost.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\Achievements.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Achievements.h" />
</ItemGroup>
<ItemGroup>
<CustomBuildStep Include="rdebug\deci2.h">