Skip to content

Commit 27ee349

Browse files
committed
Implemented achievements I/O, overlay and error handling
1 parent 5b19d1c commit 27ee349

15 files changed

Lines changed: 410 additions & 356 deletions

MarathonRecomp/gpu/video.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,6 @@ static void CreateImGuiBackend()
15771577

15781578
InitImGuiUtils();
15791579
AchievementMenu::Init();
1580-
AchievementOverlay::Init();
15811580
OptionsMenu::Init();
15821581
InstallerWizard::Init();
15831582

@@ -2311,17 +2310,17 @@ static uint32_t getSetAddress(uint32_t base, int index) {
23112310
static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, be<uint32_t>* a6)
23122311
{
23132312
LOGF_WARNING("{:p} {:p} {:p} {:p} {:p} {:p}\n", reinterpret_cast<void*>(a1), reinterpret_cast<void*>(a2), reinterpret_cast<void*>(a3), reinterpret_cast<void*>(a4), reinterpret_cast<void*>(a5), reinterpret_cast<void*>(a6));
2314-
g_xdbfTextureCache = std::unordered_map<uint16_t, GuestTexture *>();
2313+
2314+
g_xdbfTextureCache = std::unordered_map<uint16_t, GuestTexture*>();
23152315

2316-
// for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))
2317-
// {
2318-
// // huh?
2319-
// if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
2320-
// continue;
2316+
for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))
2317+
{
2318+
if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
2319+
continue;
23212320

2322-
// g_xdbfTextureCache[achievement.ID] =
2323-
// LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release();
2324-
// }
2321+
g_xdbfTextureCache[achievement.ID] =
2322+
LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release();
2323+
}
23252324

23262325
// Move backbuffer to guest memory.
23272326
assert(!g_memory.IsInMemoryRange(g_backBuffer) && g_backBufferHolder != nullptr);
@@ -4458,10 +4457,13 @@ static void SanitizePipelineState(PipelineState& pipelineState)
44584457
pipelineState.blendOpAlpha = RenderBlendOperation::ADD;
44594458
}
44604459

4461-
for (size_t i = 0; i < 16; i++)
4460+
if (pipelineState.vertexDeclaration)
44624461
{
4463-
if (!pipelineState.vertexDeclaration->vertexStreams[i])
4464-
pipelineState.vertexStrides[i] = 0;
4462+
for (size_t i = 0; i < 16; i++)
4463+
{
4464+
if (!pipelineState.vertexDeclaration->vertexStreams[i])
4465+
pipelineState.vertexStrides[i] = 0;
4466+
}
44654467
}
44664468

44674469
uint32_t specConstantsMask = 0;

MarathonRecomp/locale/locale.cpp

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -635,29 +635,42 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
635635
}
636636
},
637637
{
638-
// Notes: message appears when ACH-DATA is corrupted (mismatching file size, bad signature, incorrect version or invalid checksum) upon pressing start at the title screen.
638+
// Notes: message appears when SonicNextAchievementData.bin is corrupted (mismatching file size, bad signature, incorrect version or invalid checksum) upon loading save data.
639639
// To make this occur, open the file in any editor and just remove a large chunk of data.
640-
"Title_Message_AchievementDataCorrupt",
640+
"Title_Message_LoadAchievementDataCorrupt",
641641
{
642-
{ ELanguage::English, "The achievement data appears to be\ncorrupted and cannot be loaded.\n\nProceeding from this point will\nclear your achievement data." },
643-
{ ELanguage::Japanese, "実績データが破損しているため\n読み込むことができません\n\nこの先に進むと実績データが\n消去されます" },
644-
{ ELanguage::German, "Die Erfolgsdaten sind möglicherweise\nfehlerhaft und können nicht\ngeladen werden.\n\nDurch das Fortfahren werden\ndeine bisherigen Erfolgsdaten gelöscht." },
645-
{ ELanguage::French, "Les données des succès semblent être\nendommagées et ne peuvent être\nchargées.\n\nSi vous continuez, vos données\nseront écrasées." },
646-
{ ELanguage::Spanish, "Los datos de logros parecen estar\ncorruptos y no pueden cargarse.\n\nContinuar a partir de este punto\neliminará los datos de logros." },
647-
{ ELanguage::Italian, "I file degli obiettivi sembrano danneggiati\ne non possono essere caricati.\n\nSe prosegui da questo punto\ni tuoi obiettivi verranno cancellati." }
642+
{ ELanguage::English, "Load failed. Achievement data is corrupted.\nIf you continue your achievement progress will\nbe lost." },
643+
{ ELanguage::Japanese, "DUMMY" },
644+
{ ELanguage::German, "DUMMY" },
645+
{ ELanguage::French, "DUMMY" },
646+
{ ELanguage::Spanish, "DUMMY" },
647+
{ ELanguage::Italian, "DUMMY" }
648648
}
649649
},
650650
{
651-
// Notes: message appears when ACH-DATA cannot be loaded upon pressing start at the title screen.
652-
// To make this occur, lock the ACH-DATA file using an external program so that it cannot be accessed by the game.
653-
"Title_Message_AchievementDataIOError",
651+
// Notes: message appears when SonicNextAchievementData.bin cannot be loaded upon loading save data.
652+
// To make this occur, lock the SonicNextAchievementData.bin file using an external program so that it cannot be accessed by the game.
653+
"Title_Message_LoadAchievementDataIOError",
654654
{
655-
{ ELanguage::English, "The achievement data could not be loaded.\nYour achievements will not be saved." },
656-
{ ELanguage::Japanese, "実績データを読み込めませんでした\n実績は保存されません" },
657-
{ ELanguage::German, "Die Erfolgsdaten konnten nicht geladen werden.\nDeine Erfolge werden nicht gespeichert." },
658-
{ ELanguage::French, "Les données des succès ne\npeuvent être chargées.\nVos succès ne seront pas\nsauvegardés." },
659-
{ ELanguage::Spanish, "Los datos de logros no pueden cargarse.\nTus logros no serán guardados." },
660-
{ ELanguage::Italian, "I file degli obiettivi non possono essere caricati.\nI tuoi obiettivi non verranno salvati." }
655+
{ ELanguage::English, "Load failed. Achievement data cannot be loaded.\nIf you continue you will not be able to save\nyour achievement progress." },
656+
{ ELanguage::Japanese, "DUMMY" },
657+
{ ELanguage::German, "DUMMY" },
658+
{ ELanguage::French, "DUMMY" },
659+
{ ELanguage::Spanish, "DUMMY" },
660+
{ ELanguage::Italian, "DUMMY" }
661+
}
662+
},
663+
{
664+
// Notes: message appears when SonicNextAchievementData.bin cannot be saved upon saving save data.
665+
// To make this occur, lock the SonicNextAchievementData.bin file using an external program so that it cannot be accessed by the game.
666+
"Title_Message_SaveAchievementDataIOError",
667+
{
668+
{ ELanguage::English, "Save failed. Achievement data cannot be saved.\nIf you continue you will not be able to save\nyour achievement progress." },
669+
{ ELanguage::Japanese, "DUMMY" },
670+
{ ELanguage::German, "DUMMY" },
671+
{ ELanguage::French, "DUMMY" },
672+
{ ELanguage::Spanish, "DUMMY" },
673+
{ ELanguage::Italian, "DUMMY" }
661674
}
662675
},
663676
{
@@ -858,6 +871,28 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
858871
{ ELanguage::Italian, "Cambia" }
859872
}
860873
},
874+
{
875+
"Common_Retry",
876+
{
877+
{ ELanguage::English, "Retry" },
878+
{ ELanguage::Japanese, "DUMMY" },
879+
{ ELanguage::German, "DUMMY" },
880+
{ ELanguage::French, "DUMMY" },
881+
{ ELanguage::Spanish, "DUMMY" },
882+
{ ELanguage::Italian, "DUMMY" }
883+
}
884+
},
885+
{
886+
"Common_ContinueWithoutSaving",
887+
{
888+
{ ELanguage::English, "Continue without saving." },
889+
{ ELanguage::Japanese, "DUMMY" },
890+
{ ELanguage::German, "DUMMY" },
891+
{ ELanguage::French, "DUMMY" },
892+
{ ELanguage::Spanish, "DUMMY" },
893+
{ ELanguage::Italian, "DUMMY" }
894+
}
895+
},
861896
{
862897
"Button_Cancel",
863898
{

MarathonRecomp/patches/SaveDataTask_patches.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
#include <api/Marathon.h>
2+
#include <ui/message_window.h>
3+
#include <user/achievement_manager.h>
24
#include <user/config.h>
35

6+
#include <os/logger.h>
7+
8+
enum
9+
{
10+
ACH_ERROR_SUCCESS,
11+
ACH_ERROR_READ,
12+
ACH_ERROR_WRITE
13+
};
14+
415
static Sonicteam::SaveDataTaskXENON::SaveDataOperation g_currentAlert{};
16+
static int g_achError{ ACH_ERROR_SUCCESS };
17+
static int g_achErrorIgnored{ ACH_ERROR_SUCCESS };
518

619
// Sonicteam::SaveDataTaskXENON::RunOperation (speculatory)
720
PPC_FUNC_IMPL(__imp__sub_8238CB18);
@@ -35,9 +48,106 @@ PPC_FUNC(sub_8238CB18)
3548
}
3649
}
3750

51+
// Attempt to load achievement data on save read.
52+
if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData)
53+
{
54+
if (!AchievementManager::LoadBinary())
55+
{
56+
// This condition will always be met if the achievement data is corrupted (!= IOError),
57+
// or as long as "Continue without saving." has not been chosen on load in this session.
58+
if (AchievementManager::BinStatus != EAchBinStatus::IOError || g_achErrorIgnored != ACH_ERROR_READ)
59+
{
60+
g_achError = ACH_ERROR_READ;
61+
return;
62+
}
63+
}
64+
}
65+
66+
// Attempt to save achievement data on save write.
67+
if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData)
68+
{
69+
// This condition will be met if the achievement data failed to save (any error status),
70+
// and as long as "Continue without saving." has not been chosen on save in this session.
71+
if (!AchievementManager::SaveBinary() && g_achErrorIgnored != ACH_ERROR_WRITE)
72+
{
73+
g_achError = ACH_ERROR_WRITE;
74+
return;
75+
}
76+
}
77+
3878
__imp__sub_8238CB18(ctx, base);
3979
}
4080

81+
// Sonicteam::SaveDataTaskXENON::Update
82+
PPC_FUNC_IMPL(__imp__sub_8238D5C8);
83+
PPC_FUNC(sub_8238D5C8)
84+
{
85+
auto pSaveDataTaskXENON = (Sonicteam::SaveDataTaskXENON*)(base + ctx.r3.u32);
86+
87+
if (g_achError != ACH_ERROR_SUCCESS)
88+
{
89+
static auto s_achErrorMessageResult = -1;
90+
91+
auto& message = Localise("Title_Message_LoadAchievementDataIOError");
92+
std::vector<std::string> options = { Localise("Common_Retry"), Localise("Common_ContinueWithoutSaving") };
93+
94+
switch (g_achError)
95+
{
96+
case ACH_ERROR_READ:
97+
{
98+
// Achievement data is unrecoverable, force user to overwrite it.
99+
if (AchievementManager::BinStatus != EAchBinStatus::IOError)
100+
{
101+
message = Localise("Title_Message_LoadAchievementDataCorrupt");
102+
options[0] = Localise("Common_OK");
103+
options.pop_back();
104+
}
105+
106+
break;
107+
}
108+
109+
case ACH_ERROR_WRITE:
110+
message = Localise("Title_Message_SaveAchievementDataIOError");
111+
break;
112+
}
113+
114+
if (MessageWindow::Open(message, &s_achErrorMessageResult, options, 0) == MSG_CLOSED)
115+
{
116+
auto operation = g_achError == ACH_ERROR_READ
117+
? Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData
118+
: Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData;
119+
120+
switch (s_achErrorMessageResult)
121+
{
122+
// Retry / OK
123+
case 0:
124+
{
125+
// Create new achievement data file.
126+
AchievementManager::SaveBinary(true);
127+
128+
// Display any future errors for new files.
129+
g_achErrorIgnored = ACH_ERROR_SUCCESS;
130+
131+
break;
132+
}
133+
134+
// Continue without saving.
135+
// Ignore any future errors unrelated to corruption for this file.
136+
case 1:
137+
g_achErrorIgnored = g_achError;
138+
break;
139+
}
140+
141+
g_achError = ACH_ERROR_SUCCESS;
142+
s_achErrorMessageResult = -1;
143+
144+
GuestToHostFunction<int>(sub_8238CB18, pSaveDataTaskXENON, operation);
145+
}
146+
}
147+
148+
__imp__sub_8238D5C8(ctx, base);
149+
}
150+
41151
void SaveAlertThreeOptionRemoveDeviceSelect(PPCRegister& r5)
42152
{
43153
auto options = (uint64_t*)g_memory.Translate(r5.u32 + 8);

MarathonRecomp/patches/TitleTask_patches.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ enum
3232

3333
bool ProcessQuitMessage(Sonicteam::TitleTask* pTitleTask)
3434
{
35-
static int s_quitMessageResult = -1;
35+
static auto s_quitMessageResult = -1;
3636
static std::atomic<bool> s_faderBegun = false;
3737

3838
if (!g_quitMessageOpen)

MarathonRecomp/patches/misc_patches.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
#include <user/achievement_manager.h>
66
#include <app.h>
77

8-
// TODO (Hyper): implement achievements menu.
9-
void AchievementManagerUnlockMidAsmHook(PPCRegister& id)
8+
void UnlockAchievement(PPCRegister& id)
109
{
1110
AchievementManager::Unlock(id.u32);
1211
}

0 commit comments

Comments
 (0)