mirror of https://github.com/PCSX2/pcsx2.git
Qt: Add RetroAchievements implementation
This commit is contained in:
parent
843b0b3eb1
commit
0419de4baf
|
@ -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.
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);;"
|
||||
|
@ -128,8 +132,8 @@ void MainWindow::initialize()
|
|||
CocoaTools::AddThemeChangeHandler(this, [](void* ctx) {
|
||||
// This handler is called *before* the style change has propagated far enough for Qt to see it
|
||||
// Use RunOnUIThread to delay until it has
|
||||
QtHost::RunOnUIThread([ctx = static_cast<MainWindow*>(ctx)]{
|
||||
ctx->updateTheme();// Qt won't notice the style change without us touching the palette in some way
|
||||
QtHost::RunOnUIThread([ctx = static_cast<MainWindow*>(ctx)] {
|
||||
ctx->updateTheme(); // Qt won't notice the style change without us touching the palette in some way
|
||||
});
|
||||
});
|
||||
#endif
|
||||
|
@ -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)
|
||||
|
|
|
@ -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>&Graphics</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAchievementSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="trophy-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>A&chievements</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPostProcessingSettings">
|
||||
<property name="text">
|
||||
<string>&Post-Processing Settings...</string>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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><html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html></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>
|
|
@ -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>
|
||||
|
||||
|
@ -127,7 +132,7 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
|
|||
addWidget(m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"),
|
||||
QStringLiteral("sd-card-line"), tr("<strong>Memory Card Settings</strong><hr>Create and configure Memory Cards here.<br><br>Mouse over an option for additional information."));
|
||||
}
|
||||
|
||||
|
||||
addWidget(m_dev9_settings = new DEV9SettingsWidget(this, m_ui.settingsContainer), tr("Network & HDD"), QStringLiteral("dashboard-line"),
|
||||
tr("<strong>Network & HDD Settings</strong><hr>These options control the network connectivity and internal HDD storage of the console.<br><br>"
|
||||
"Mouse over an option for additional information."));
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
@ -426,4 +450,4 @@
|
|||
<Filter>Settings</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
Console.WriteLn(Color_Red, " ... not found '%s'!", name);
|
||||
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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue