diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h
index a7fa964a0c..808967b518 100644
--- a/Common/Serialize/Serializer.h
+++ b/Common/Serialize/Serializer.h
@@ -147,6 +147,11 @@ public:
void DoMarker(const char *prevName, u32 arbitraryNumber = 0x42);
+ void SkipBytes(size_t bytes) {
+ // Should work in all modes.
+ *ptr += bytes;
+ }
+
size_t Offset() const { return *ptr - ptrStart_; }
private:
diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp
index 5ff26c0461..60317157d9 100644
--- a/Core/HLE/sceKernel.cpp
+++ b/Core/HLE/sceKernel.cpp
@@ -85,8 +85,7 @@
#include "sceDmac.h"
#include "sceMp4.h"
#include "sceOpenPSID.h"
-
-#include "../Util/PPGeDraw.h"
+#include "Core/Util/PPGeDraw.h"
/*
17: [MIPS32 R4K 00000000 ]: Loader: Type: 1 Vaddr: 00000000 Filesz: 2856816 Memsz: 2856816
diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp
index e4c9300ec5..7b7d01960b 100644
--- a/Core/RetroAchievements.cpp
+++ b/Core/RetroAchievements.cpp
@@ -354,6 +354,11 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t *
}
void Initialize() {
+ if (!g_Config.bAchievementsEnable) {
+ _dbg_assert_(!g_rcClient);
+ INFO_LOG(ACHIEVEMENTS, "Achievements are disabled, not initializing.");
+ return;
+ }
_assert_msg_(g_Config.bAchievementsEnable, "Achievements are enabled");
g_challengeMode = true; // the default
@@ -453,14 +458,27 @@ void Idle() {
}
void DoState(PointerWrap &p) {
- auto sw = p.Section("Achievements", 1);
+ auto sw = p.Section("Achievements", 0, 1);
if (!sw) {
// Save state is missing the section.
// Reset the runtime.
+ if (HasAchievementsOrLeaderboards()) {
+ auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
+ g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("Save state loaded without achievement data"), 5.0f);
+ }
+ rc_client_reset(g_rcClient);
return;
}
uint32_t data_size = 0;
+
+ if (!g_activeGame) {
+ WARN_LOG(ACHIEVEMENTS, "Save state contained achievement data, but achievements are not active. Ignore.");
+ Do(p, data_size);
+ p.SkipBytes(data_size);
+ return;
+ }
+
if (p.mode == PointerWrap::MODE_MEASURE || p.mode == PointerWrap::MODE_WRITE || p.mode == PointerWrap::MODE_VERIFY || p.mode == PointerWrap::MODE_NOOP) {
data_size = (uint32_t)(g_rcClient ? rc_client_progress_size(g_rcClient) : 0);
}
@@ -473,18 +491,35 @@ void DoState(PointerWrap &p) {
case PointerWrap::MODE_MEASURE:
case PointerWrap::MODE_WRITE:
case PointerWrap::MODE_VERIFY:
- rc_client_serialize_progress(g_rcClient, buffer);
+ {
+ int retval = rc_client_serialize_progress(g_rcClient, buffer);
+ if (retval != RC_OK) {
+ ERROR_LOG(ACHIEVEMENTS, "Error %d serializing achievement data. Ignoring.", retval);
+ }
break;
}
+ }
DoArray(p, buffer, data_size);
switch (p.mode) {
case PointerWrap::MODE_READ:
- rc_client_deserialize_progress(g_rcClient, buffer);
+ {
+ int retval = rc_client_deserialize_progress(g_rcClient, buffer);
+ if (retval != RC_OK) {
+ // TODO: What should we really do here?
+ ERROR_LOG(ACHIEVEMENTS, "Error %d deserializing achievement data. Ignoring.", retval);
+ }
break;
}
+ }
delete[] buffer;
+ } else {
+ if (HasAchievementsOrLeaderboards()) {
+ auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
+ g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("Save state loaded without achievement data"), 5.0f);
+ }
+ rc_client_reset(g_rcClient);
}
}
@@ -610,6 +645,7 @@ void UnloadGame() {
if (g_rcClient) {
rc_client_unload_game(g_rcClient);
}
+ g_activeGame = false;
}
void change_media_callback(int result, const char *error_message, rc_client_t *client, void *userdata) {
diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp
index bc68cebd40..4d689460b4 100644
--- a/Core/SaveState.cpp
+++ b/Core/SaveState.cpp
@@ -47,6 +47,7 @@
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
+#include "Core/RetroAchievements.h"
#include "HW/MemoryStick.h"
#include "GPU/GPUState.h"
@@ -391,6 +392,7 @@ namespace SaveState
currentMIPS->DoState(p);
HLEDoState(p);
__KernelDoState(p);
+ Achievements::DoState(p);
// Kernel object destructors might close open files, so do the filesystem last.
pspFileSystem.DoState(p);
}
diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp
index 3a04ed6d29..81ab01ee64 100644
--- a/UI/RetroAchievementScreens.cpp
+++ b/UI/RetroAchievementScreens.cpp
@@ -196,7 +196,9 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup)
using namespace UI;
- if (Achievements::IsLoggedIn()) {
+ if (!g_Config.bAchievementsEnable) {
+ viewGroup->Add(new TextView(ac->T("Achievements are disabled")));
+ } else if (Achievements::IsLoggedIn()) {
const rc_client_user_t *info = rc_client_get_user_info(Achievements::GetClient());
// In the future, RetroAchievements will support display names. Prepare for that.
@@ -255,9 +257,14 @@ void RetroAchievementsSettingsScreen::CreateSettingsTab(UI::ViewGroup *viewGroup
using namespace UI;
viewGroup->Add(new ItemHeader(ac->T("Settings")));
- viewGroup->Add(new CheckBox(&g_Config.bAchievementsRichPresence, ac->T("Rich Presence")));
- viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects"))); // not yet implemented
- viewGroup->Add(new CheckBox(&g_Config.bAchievementsLogBadMemReads, ac->T("Log bad memory accesses")));
+ viewGroup->Add(new CheckBox(&g_Config.bAchievementsEnable, ac->T("Achievements enabled")))->OnClick.Add([&](UI::EventParams &e) -> UI::EventReturn {
+ Achievements::UpdateSettings();
+ RecreateViews();
+ return UI::EVENT_DONE;
+ });
+ viewGroup->Add(new CheckBox(&g_Config.bAchievementsRichPresence, ac->T("Rich Presence")))->SetEnabledPtr(&g_Config.bAchievementsEnable);
+ viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects")))->SetEnabledPtr(&g_Config.bAchievementsEnable); // not yet implemented
+ viewGroup->Add(new CheckBox(&g_Config.bAchievementsLogBadMemReads, ac->T("Log bad memory accesses")))->SetEnabledPtr(&g_Config.bAchievementsEnable);
// Not yet fully implemented
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsChallengeMode, ac->T("Challenge Mode (no savestates)")));
diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj
index 7cac1f3c79..68cf5bac73 100644
--- a/UI/UI.vcxproj
+++ b/UI/UI.vcxproj
@@ -106,6 +106,9 @@
+
+ {533f1d30-d04d-47cc-ad71-20f658907e36}
+
{beb0a821-3c7f-410f-a525-63afbc69bf8f}
diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini
index 1800175eb9..93180ad81c 100644
--- a/assets/lang/en_US.ini
+++ b/assets/lang/en_US.ini
@@ -26,6 +26,7 @@
%d achievements, %d points = %d achievements, %d points
Account = Account
Achievements = Achievements
+Achievements are disabled = Achievements are disabled
Challenge Mode = Challenge Mode
Challenge Mode (no savestates) = Challenge Mode (no savestates)
Earned = You have earned %d of %d achievements, and %d of %d points
@@ -33,7 +34,6 @@ How to use RetroAchievements = How to use RetroAchievements
Leaderboard submission is enabled = Leaderboard submission is enabled
Leaderboards = Leaderboards
Links = Links
-Local = Local
Locked achievements = Locked achievements
Log bad memory accesses = Log bad memory accesses
Mastered %1 = Mastered %1
@@ -41,6 +41,7 @@ Register on www.retroachievements.org = Register on www.retroachievements.org
RetroAchievements are not available for this game = RetroAchievements are not available for this game
RetroAchievements website = RetroAchievements website
Rich Presence = Rich Presence
+Save state loaded without achievement data = Save state loaded without achievement data
Sound Effects = Sound Effects
Statistics = Statistics
Submitted Score = Submitted Score
diff --git a/headless/Headless.cpp b/headless/Headless.cpp
index a59ebafc45..4a79bad5d8 100644
--- a/headless/Headless.cpp
+++ b/headless/Headless.cpp
@@ -143,6 +143,12 @@ void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\
void System_AudioClear() {}
void System_AudioPushSamples(const s32 *audio, int numSamples) {}
+// TODO: To avoid having to define these here, these should probably be turned into system "requests".
+void NativeSaveSecret(const char *nameOfSecret, const std::string &data) {}
+std::string NativeLoadSecret(const char *nameOfSecret) {
+ return "";
+}
+
int printUsage(const char *progname, const char *reason)
{
if (reason != NULL)
diff --git a/headless/Headless.vcxproj b/headless/Headless.vcxproj
index 7329860ba5..695f20dbe4 100644
--- a/headless/Headless.vcxproj
+++ b/headless/Headless.vcxproj
@@ -508,6 +508,9 @@
{8bfd8150-94d5-4bf9-8a50-7bd9929a0850}
+
+ {31694510-a8c0-40f6-b09b-e8df825adefa}
+
{457f45d2-556f-47bc-a31d-aff0d15beaed}
diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp
index ca588ea6c4..66e7fe5a15 100644
--- a/unittest/UnitTest.cpp
+++ b/unittest/UnitTest.cpp
@@ -103,6 +103,12 @@ void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\
void System_AudioClear() {}
void System_AudioPushSamples(const s32 *audio, int numSamples) {}
+// TODO: To avoid having to define these here, these should probably be turned into system "requests".
+void NativeSaveSecret(const char *nameOfSecret, const std::string &data) {}
+std::string NativeLoadSecret(const char *nameOfSecret) {
+ return "";
+}
+
#if PPSSPP_PLATFORM(ANDROID)
JNIEnv *getEnv() {
return nullptr;
diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj
index 687314e43d..e96bfd7892 100644
--- a/unittest/UnitTests.vcxproj
+++ b/unittest/UnitTests.vcxproj
@@ -426,15 +426,15 @@
{8bfd8150-94d5-4bf9-8a50-7bd9929a0850}
+
+ {31694510-a8c0-40f6-b09b-e8df825adefa}
+
{f761046e-6c38-4428-a5f1-38391a37bb34}
{457f45d2-556f-47bc-a31d-aff0d15beaed}
-
- {004b8d11-2be3-4bd9-ab40-2be04cf2096f}
-