mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-25 20:01:58 +00:00
Merge branch 'main' into crash-check
This commit is contained in:
commit
c524733385
19 changed files with 524 additions and 92 deletions
|
|
@ -183,6 +183,8 @@ set(UNLEASHED_RECOMP_USER_CXX_SOURCES
|
||||||
"user/config.cpp"
|
"user/config.cpp"
|
||||||
"user/registry.cpp"
|
"user/registry.cpp"
|
||||||
"user/paths.cpp"
|
"user/paths.cpp"
|
||||||
|
"user/persistent_data.cpp"
|
||||||
|
"user/persistent_storage_manager.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(UNLEASHED_RECOMP_MOD_CXX_SOURCES
|
set(UNLEASHED_RECOMP_MOD_CXX_SOURCES
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,13 @@ namespace SWA
|
||||||
boost::shared_ptr<Hedgehog::Mirage::CRenderScene> m_spRenderScene;
|
boost::shared_ptr<Hedgehog::Mirage::CRenderScene> m_spRenderScene;
|
||||||
SWA_INSERT_PADDING(0x04);
|
SWA_INSERT_PADDING(0x04);
|
||||||
boost::shared_ptr<CGameParameter> m_spGameParameter;
|
boost::shared_ptr<CGameParameter> m_spGameParameter;
|
||||||
SWA_INSERT_PADDING(0x78);
|
SWA_INSERT_PADDING(0x0C);
|
||||||
|
boost::anonymous_shared_ptr m_spItemParamManager;
|
||||||
|
SWA_INSERT_PADDING(0x64);
|
||||||
boost::shared_ptr<Hedgehog::Base::CCriticalSection> m_spCriticalSection;
|
boost::shared_ptr<Hedgehog::Base::CCriticalSection> m_spCriticalSection;
|
||||||
SWA_INSERT_PADDING(0x20);
|
SWA_INSERT_PADDING(0x14);
|
||||||
|
bool m_ShowDLCInfo;
|
||||||
|
SWA_INSERT_PADDING(0x08);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Hedgehog::Base::TSynchronizedPtr<CApplicationDocument>
|
// TODO: Hedgehog::Base::TSynchronizedPtr<CApplicationDocument>
|
||||||
|
|
@ -111,7 +115,9 @@ namespace SWA
|
||||||
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_Field10C, 0x10C);
|
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_Field10C, 0x10C);
|
||||||
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spRenderScene, 0x12C);
|
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spRenderScene, 0x12C);
|
||||||
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spGameParameter, 0x138);
|
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spGameParameter, 0x138);
|
||||||
|
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spItemParamManager, 0x14C);
|
||||||
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spCriticalSection, 0x1B8);
|
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spCriticalSection, 0x1B8);
|
||||||
|
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_ShowDLCInfo, 0x1D4);
|
||||||
SWA_ASSERT_SIZEOF(CApplicationDocument::CMember, 0x1E0);
|
SWA_ASSERT_SIZEOF(CApplicationDocument::CMember, 0x1E0);
|
||||||
|
|
||||||
SWA_ASSERT_OFFSETOF(CApplicationDocument, m_pMember, 0x04);
|
SWA_ASSERT_OFFSETOF(CApplicationDocument, m_pMember, 0x04);
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,19 @@ namespace SWA
|
||||||
class CGameParameter // : public Hedgehog::Universe::CMessageActor
|
class CGameParameter // : public Hedgehog::Universe::CMessageActor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct SSaveData;
|
struct SSaveData
|
||||||
|
{
|
||||||
|
SWA_INSERT_PADDING(0x8600);
|
||||||
|
be<uint32_t> DLCFlags[8];
|
||||||
|
SWA_INSERT_PADDING(0x15C);
|
||||||
|
};
|
||||||
|
|
||||||
struct SStageParameter;
|
struct SStageParameter;
|
||||||
|
|
||||||
SWA_INSERT_PADDING(0x94);
|
SWA_INSERT_PADDING(0x94);
|
||||||
xpointer<SSaveData> m_pSaveData;
|
xpointer<SSaveData> m_pSaveData;
|
||||||
xpointer<SStageParameter> m_pStageParameter;
|
xpointer<SStageParameter> m_pStageParameter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SWA_ASSERT_OFFSETOF(CGameParameter::SSaveData, DLCFlags, 0x8600);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,8 @@ void hid::Init()
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1");
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1");
|
||||||
SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1");
|
SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1");
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); // Uses Button Labels. This hint is disabled for Nintendo Controllers.
|
||||||
|
|
||||||
SDL_InitSubSystem(SDL_INIT_EVENTS);
|
SDL_InitSubSystem(SDL_INIT_EVENTS);
|
||||||
SDL_AddEventWatch(HID_OnSDLEvent, nullptr);
|
SDL_AddEventWatch(HID_OnSDLEvent, nullptr);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,73 @@ static std::unique_ptr<VirtualFileSystem> createFileSystemFromPath(const std::fi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool checkFile(const FilePair &pair, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, std::vector<uint8_t> &fileData, Journal &journal, const std::function<bool()> &progressCallback, bool checkSizeOnly) {
|
||||||
|
const std::string fileName(pair.first);
|
||||||
|
const uint32_t hashCount = pair.second;
|
||||||
|
const std::filesystem::path filePath = targetDirectory / fileName;
|
||||||
|
if (!std::filesystem::exists(filePath))
|
||||||
|
{
|
||||||
|
journal.lastResult = Journal::Result::FileMissing;
|
||||||
|
journal.lastErrorMessage = fmt::format("File {} does not exist.", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
size_t fileSize = std::filesystem::file_size(filePath, ec);
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
journal.lastResult = Journal::Result::FileReadFailed;
|
||||||
|
journal.lastErrorMessage = fmt::format("Failed to read file size for {}.", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkSizeOnly)
|
||||||
|
{
|
||||||
|
journal.progressTotal += fileSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::ifstream fileStream(filePath, std::ios::binary);
|
||||||
|
if (fileStream.is_open())
|
||||||
|
{
|
||||||
|
fileData.resize(fileSize);
|
||||||
|
fileStream.read((char *)(fileData.data()), fileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileStream.is_open() || fileStream.bad())
|
||||||
|
{
|
||||||
|
journal.lastResult = Journal::Result::FileReadFailed;
|
||||||
|
journal.lastErrorMessage = fmt::format("Failed to read file {}.", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fileHash = XXH3_64bits(fileData.data(), fileSize);
|
||||||
|
bool fileHashFound = false;
|
||||||
|
for (uint32_t i = 0; i < hashCount && !fileHashFound; i++)
|
||||||
|
{
|
||||||
|
fileHashFound = fileHash == fileHashes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileHashFound)
|
||||||
|
{
|
||||||
|
journal.lastResult = Journal::Result::FileHashFailed;
|
||||||
|
journal.lastErrorMessage = fmt::format("File {} did not match any of the known hashes.", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
journal.progressCounter += fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!progressCallback())
|
||||||
|
{
|
||||||
|
journal.lastResult = Journal::Result::Cancelled;
|
||||||
|
journal.lastErrorMessage = "Check was cancelled.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<bool()> &progressCallback) {
|
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<bool()> &progressCallback) {
|
||||||
const std::string filename(pair.first);
|
const std::string filename(pair.first);
|
||||||
const uint32_t hashCount = pair.second;
|
const uint32_t hashCount = pair.second;
|
||||||
|
|
@ -204,6 +271,45 @@ static DLC detectDLC(const std::filesystem::path &sourcePath, VirtualFileSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool fillDLCSource(DLC dlc, Installer::DLCSource &dlcSource)
|
||||||
|
{
|
||||||
|
switch (dlc)
|
||||||
|
{
|
||||||
|
case DLC::Spagonia:
|
||||||
|
dlcSource.filePairs = { SpagoniaFiles, SpagoniaFilesSize };
|
||||||
|
dlcSource.fileHashes = SpagoniaHashes;
|
||||||
|
dlcSource.targetSubDirectory = SpagoniaDirectory;
|
||||||
|
return true;
|
||||||
|
case DLC::Chunnan:
|
||||||
|
dlcSource.filePairs = { ChunnanFiles, ChunnanFilesSize };
|
||||||
|
dlcSource.fileHashes = ChunnanHashes;
|
||||||
|
dlcSource.targetSubDirectory = ChunnanDirectory;
|
||||||
|
return true;
|
||||||
|
case DLC::Mazuri:
|
||||||
|
dlcSource.filePairs = { MazuriFiles, MazuriFilesSize };
|
||||||
|
dlcSource.fileHashes = MazuriHashes;
|
||||||
|
dlcSource.targetSubDirectory = MazuriDirectory;
|
||||||
|
return true;
|
||||||
|
case DLC::Holoska:
|
||||||
|
dlcSource.filePairs = { HoloskaFiles, HoloskaFilesSize };
|
||||||
|
dlcSource.fileHashes = HoloskaHashes;
|
||||||
|
dlcSource.targetSubDirectory = HoloskaDirectory;
|
||||||
|
return true;
|
||||||
|
case DLC::ApotosShamar:
|
||||||
|
dlcSource.filePairs = { ApotosShamarFiles, ApotosShamarFilesSize };
|
||||||
|
dlcSource.fileHashes = ApotosShamarHashes;
|
||||||
|
dlcSource.targetSubDirectory = ApotosShamarDirectory;
|
||||||
|
return true;
|
||||||
|
case DLC::EmpireCityAdabat:
|
||||||
|
dlcSource.filePairs = { EmpireCityAdabatFiles, EmpireCityAdabatFilesSize };
|
||||||
|
dlcSource.fileHashes = EmpireCityAdabatHashes;
|
||||||
|
dlcSource.targetSubDirectory = EmpireCityAdabatDirectory;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath)
|
bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath)
|
||||||
{
|
{
|
||||||
modulePath = baseDirectory / PatchedDirectory / GameExecutableFile;
|
modulePath = baseDirectory / PatchedDirectory / GameExecutableFile;
|
||||||
|
|
@ -254,6 +360,40 @@ bool Installer::checkAllDLC(const std::filesystem::path& baseDirectory)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Installer::checkInstallIntegrity(const std::filesystem::path &baseDirectory, Journal &journal, const std::function<bool()> &progressCallback)
|
||||||
|
{
|
||||||
|
// Run the file checks twice: once to fill out the progress counter and the file sizes, and another pass to do the hash integrity checks.
|
||||||
|
for (uint32_t checkPass = 0; checkPass < 2; checkPass++)
|
||||||
|
{
|
||||||
|
bool checkSizeOnly = (checkPass == 0);
|
||||||
|
if (!checkFiles({ GameFiles, GameFilesSize }, GameHashes, baseDirectory / GameDirectory, journal, progressCallback, checkSizeOnly))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, baseDirectory / UpdateDirectory, journal, progressCallback, checkSizeOnly))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < (int)DLC::Count; i++)
|
||||||
|
{
|
||||||
|
if (checkDLCInstall(baseDirectory, (DLC)i))
|
||||||
|
{
|
||||||
|
Installer::DLCSource dlcSource;
|
||||||
|
fillDLCSource((DLC)i, dlcSource);
|
||||||
|
|
||||||
|
if (!checkFiles(dlcSource.filePairs, dlcSource.fileHashes, baseDirectory / dlcSource.targetSubDirectory, journal, progressCallback, checkSizeOnly))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize)
|
bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize)
|
||||||
{
|
{
|
||||||
for (FilePair pair : filePairs)
|
for (FilePair pair : filePairs)
|
||||||
|
|
@ -272,6 +412,27 @@ bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Installer::checkFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<bool()> &progressCallback, bool checkSizeOnly)
|
||||||
|
{
|
||||||
|
FilePair validationPair = {};
|
||||||
|
uint32_t validationHashIndex = 0;
|
||||||
|
uint32_t hashIndex = 0;
|
||||||
|
uint32_t hashCount = 0;
|
||||||
|
std::vector<uint8_t> fileData;
|
||||||
|
for (FilePair pair : filePairs)
|
||||||
|
{
|
||||||
|
hashIndex = hashCount;
|
||||||
|
hashCount += pair.second;
|
||||||
|
|
||||||
|
if (!checkFile(pair, &fileHashes[hashIndex], targetDirectory, fileData, journal, progressCallback, checkSizeOnly))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback)
|
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback)
|
||||||
{
|
{
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
@ -387,39 +548,8 @@ bool Installer::parseSources(const Input &input, Journal &journal, Sources &sour
|
||||||
}
|
}
|
||||||
|
|
||||||
DLC dlc = detectDLC(path, *dlcSource.sourceVfs, journal);
|
DLC dlc = detectDLC(path, *dlcSource.sourceVfs, journal);
|
||||||
switch (dlc)
|
if (!fillDLCSource(dlc, dlcSource))
|
||||||
{
|
{
|
||||||
case DLC::Spagonia:
|
|
||||||
dlcSource.filePairs = { SpagoniaFiles, SpagoniaFilesSize };
|
|
||||||
dlcSource.fileHashes = SpagoniaHashes;
|
|
||||||
dlcSource.targetSubDirectory = SpagoniaDirectory;
|
|
||||||
break;
|
|
||||||
case DLC::Chunnan:
|
|
||||||
dlcSource.filePairs = { ChunnanFiles, ChunnanFilesSize };
|
|
||||||
dlcSource.fileHashes = ChunnanHashes;
|
|
||||||
dlcSource.targetSubDirectory = ChunnanDirectory;
|
|
||||||
break;
|
|
||||||
case DLC::Mazuri:
|
|
||||||
dlcSource.filePairs = { MazuriFiles, MazuriFilesSize };
|
|
||||||
dlcSource.fileHashes = MazuriHashes;
|
|
||||||
dlcSource.targetSubDirectory = MazuriDirectory;
|
|
||||||
break;
|
|
||||||
case DLC::Holoska:
|
|
||||||
dlcSource.filePairs = { HoloskaFiles, HoloskaFilesSize };
|
|
||||||
dlcSource.fileHashes = HoloskaHashes;
|
|
||||||
dlcSource.targetSubDirectory = HoloskaDirectory;
|
|
||||||
break;
|
|
||||||
case DLC::ApotosShamar:
|
|
||||||
dlcSource.filePairs = { ApotosShamarFiles, ApotosShamarFilesSize };
|
|
||||||
dlcSource.fileHashes = ApotosShamarHashes;
|
|
||||||
dlcSource.targetSubDirectory = ApotosShamarDirectory;
|
|
||||||
break;
|
|
||||||
case DLC::EmpireCityAdabat:
|
|
||||||
dlcSource.filePairs = { EmpireCityAdabatFiles, EmpireCityAdabatFilesSize };
|
|
||||||
dlcSource.fileHashes = EmpireCityAdabatHashes;
|
|
||||||
dlcSource.targetSubDirectory = EmpireCityAdabatDirectory;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,9 @@ struct Installer
|
||||||
static bool checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath);
|
static bool checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath);
|
||||||
static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc);
|
static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc);
|
||||||
static bool checkAllDLC(const std::filesystem::path &baseDirectory);
|
static bool checkAllDLC(const std::filesystem::path &baseDirectory);
|
||||||
|
static bool checkInstallIntegrity(const std::filesystem::path &baseDirectory, Journal &journal, const std::function<bool()> &progressCallback);
|
||||||
static bool computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize);
|
static bool computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize);
|
||||||
|
static bool checkFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<bool()> &progressCallback, bool checkSizeOnly);
|
||||||
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback);
|
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback);
|
||||||
static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal);
|
static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal);
|
||||||
static bool parseSources(const Input &input, Journal &journal, Sources &sources);
|
static bool parseSources(const Input &input, Journal &journal, Sources &sources);
|
||||||
|
|
|
||||||
|
|
@ -703,6 +703,28 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
|
||||||
{ ELanguage::Italian, "Impossibile trovare il modulo \"%s\".\n\nAssicurati che:\n\n- Hai estratto questa copia di Unleashed Recompiled correttamente e non solo il file *.exe.\n- Non stai eseguendo Unleashed Recompiled da un file *.zip." }
|
{ ELanguage::Italian, "Impossibile trovare il modulo \"%s\".\n\nAssicurati che:\n\n- Hai estratto questa copia di Unleashed Recompiled correttamente e non solo il file *.exe.\n- Non stai eseguendo Unleashed Recompiled da un file *.zip." }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"IntegrityCheck_Success",
|
||||||
|
{
|
||||||
|
{ ELanguage::English, "Installation check has finished.\n\nAll files seem to be correct.\n\nThe game will now close. Remove the launch argument to play the game." },
|
||||||
|
{ ELanguage::Japanese, "インストールチェックが終了しました\n\nすべてのファイルは正しいようです\n\nゲームは終了します、ゲームをプレイするには起動引数を削除してください" },
|
||||||
|
{ ELanguage::German, "Die Installation wurde überprüft.\n\nAlle Dateien scheinen korrekt zu sein.\n\nDas Spiel wird nun geschlossen. Entferne die Startoption, um das Spiel zu spielen." },
|
||||||
|
{ ELanguage::French, "La vérification de l'installation est terminée.\n\nTous les fichiers semblent corrects.\n\nL'application va maintenant se fermer. Retirez l'argument de lancement pour pouvoir lancer le jeu." },
|
||||||
|
{ ELanguage::Spanish, "La verificación de la instalación ha terminado.\n\nTodos los archivos parecen correctos.\n\nEl juego se cerrará ahora. Elimina el argumento de lanzamiento para jugar al juego." },
|
||||||
|
{ ELanguage::Italian, "La verifica dei file d'installazione è terminata.\n\nTutti i file sembrano corretti.\n\nIl gioco si chiuderà. Rimuovi l'argomento di avvio per poter giocare." }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IntegrityCheck_Failed",
|
||||||
|
{
|
||||||
|
{ ELanguage::English, "Installation check has failed.\n\nError: %s\n\nThe game will now close. Try reinstalling the game by using the --install launch argument." },
|
||||||
|
{ ELanguage::Japanese, "インストールチェックに失敗しました\n\nエラー:%s\n\nゲームは終了します、--install 起動引数を使用してゲームを再インストールしてください" },
|
||||||
|
{ ELanguage::German, "Die Installationsprüfung ist fehlgeschlagen.\n\nFehler: %s\n\nDas Spiel wird nun geschlossen. Versuche das Spiel durch Verwendung der Startoption --install neu zu installieren." },
|
||||||
|
{ ELanguage::French, "La vérification de l'installation a échoué.\n\nErreur : %s\n\nL'application va maintenant se fermer. Essayez de réinstaller le jeu en utilisant l'argument de lancement --install." },
|
||||||
|
{ ELanguage::Spanish, "La verificación de la instalación ha fallado.\n\nError: %s\n\nEl juego se cerrará ahora. Intenta reinstalar el juego utilizando el argumento de lanzamiento --install." },
|
||||||
|
{ ELanguage::Italian, "La verifica dei file d'installazione non è andata a buon fine.\n\nErrore: %s\n\nIl gioco si chiuderà. Prova a reinstallare il gioco utilizzando l'argomento di avvio --install." }
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Common_On",
|
"Common_On",
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <hid/hid.h>
|
#include <hid/hid.h>
|
||||||
#include <user/config.h>
|
#include <user/config.h>
|
||||||
#include <user/paths.h>
|
#include <user/paths.h>
|
||||||
|
#include <user/persistent_storage_manager.h>
|
||||||
#include <user/registry.h>
|
#include <user/registry.h>
|
||||||
#include <kernel/xdbf.h>
|
#include <kernel/xdbf.h>
|
||||||
#include <install/installer.h>
|
#include <install/installer.h>
|
||||||
|
|
@ -200,6 +201,7 @@ int main(int argc, char *argv[])
|
||||||
bool forceInstaller = false;
|
bool forceInstaller = false;
|
||||||
bool forceDLCInstaller = false;
|
bool forceDLCInstaller = false;
|
||||||
bool useDefaultWorkingDirectory = false;
|
bool useDefaultWorkingDirectory = false;
|
||||||
|
bool forceInstallationCheck = false;
|
||||||
bool graphicsApiRetry = false;
|
bool graphicsApiRetry = false;
|
||||||
const char *sdlVideoDriver = nullptr;
|
const char *sdlVideoDriver = nullptr;
|
||||||
|
|
||||||
|
|
@ -208,6 +210,7 @@ int main(int argc, char *argv[])
|
||||||
forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0);
|
forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0);
|
||||||
forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0);
|
forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0);
|
||||||
useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0);
|
useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0);
|
||||||
|
forceInstallationCheck = forceInstallationCheck || (strcmp(argv[i], "--install-check") == 0);
|
||||||
graphicsApiRetry = graphicsApiRetry || (strcmp(argv[i], "--graphics-api-retry") == 0);
|
graphicsApiRetry = graphicsApiRetry || (strcmp(argv[i], "--graphics-api-retry") == 0);
|
||||||
|
|
||||||
if (strcmp(argv[i], "--sdl-video-driver") == 0)
|
if (strcmp(argv[i], "--sdl-video-driver") == 0)
|
||||||
|
|
@ -228,6 +231,62 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
Config::Load();
|
Config::Load();
|
||||||
|
|
||||||
|
if (!PersistentStorageManager::LoadBinary())
|
||||||
|
LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus);
|
||||||
|
|
||||||
|
if (forceInstallationCheck)
|
||||||
|
{
|
||||||
|
// Create the console to show progress to the user, otherwise it will seem as if the game didn't boot at all.
|
||||||
|
os::process::ShowConsole();
|
||||||
|
|
||||||
|
Journal journal;
|
||||||
|
double lastProgressMiB = 0.0;
|
||||||
|
double lastTotalMib = 0.0;
|
||||||
|
Installer::checkInstallIntegrity(GAME_INSTALL_DIRECTORY, journal, [&]()
|
||||||
|
{
|
||||||
|
constexpr double MiBDivisor = 1024.0 * 1024.0;
|
||||||
|
constexpr double MiBProgressThreshold = 128.0;
|
||||||
|
double progressMiB = double(journal.progressCounter) / MiBDivisor;
|
||||||
|
double totalMiB = double(journal.progressTotal) / MiBDivisor;
|
||||||
|
if (journal.progressCounter > 0)
|
||||||
|
{
|
||||||
|
if ((progressMiB - lastProgressMiB) > MiBProgressThreshold)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "Checking files: %0.2f MiB / %0.2f MiB\n", progressMiB, totalMiB);
|
||||||
|
lastProgressMiB = progressMiB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((totalMiB - lastTotalMib) > MiBProgressThreshold)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "Scanning files: %0.2f MiB\n", totalMiB);
|
||||||
|
lastTotalMib = totalMiB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
char resultText[512];
|
||||||
|
uint32_t messageBoxStyle;
|
||||||
|
if (journal.lastResult == Journal::Result::Success)
|
||||||
|
{
|
||||||
|
snprintf(resultText, sizeof(resultText), "%s", Localise("IntegrityCheck_Success").c_str());
|
||||||
|
fprintf(stdout, "%s\n", resultText);
|
||||||
|
messageBoxStyle = SDL_MESSAGEBOX_INFORMATION;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(resultText, sizeof(resultText), Localise("IntegrityCheck_Failed").c_str(), journal.lastErrorMessage.c_str());
|
||||||
|
fprintf(stderr, "%s\n", resultText);
|
||||||
|
messageBoxStyle = SDL_MESSAGEBOX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_ShowSimpleMessageBox(messageBoxStyle, GameWindow::GetTitle(), resultText, GameWindow::s_pWindow);
|
||||||
|
std::_Exit(int(journal.lastResult));
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(UNLEASHED_RECOMP_D3D12)
|
#if defined(_WIN32) && defined(UNLEASHED_RECOMP_D3D12)
|
||||||
for (auto& dll : g_D3D12RequiredModules)
|
for (auto& dll : g_D3D12RequiredModules)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include <api/SWA.h>
|
#include <api/SWA.h>
|
||||||
#include <install/update_checker.h>
|
#include <install/update_checker.h>
|
||||||
#include <locale/locale.h>
|
#include <locale/locale.h>
|
||||||
|
#include <os/logger.h>
|
||||||
#include <ui/fader.h>
|
#include <ui/fader.h>
|
||||||
#include <ui/message_window.h>
|
#include <ui/message_window.h>
|
||||||
#include <user/achievement_manager.h>
|
#include <user/achievement_manager.h>
|
||||||
|
|
@ -64,16 +65,16 @@ static bool ProcessCorruptAchievementsMessage()
|
||||||
if (!g_corruptAchievementsMessageOpen)
|
if (!g_corruptAchievementsMessageOpen)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto message = AchievementManager::Status == EAchStatus::IOError
|
auto message = AchievementManager::BinStatus == EAchBinStatus::IOError
|
||||||
? Localise("Title_Message_AchievementDataIOError")
|
? Localise("Title_Message_AchievementDataIOError")
|
||||||
: Localise("Title_Message_AchievementDataCorrupt");
|
: Localise("Title_Message_AchievementDataCorrupt");
|
||||||
|
|
||||||
if (MessageWindow::Open(message, &g_corruptAchievementsMessageResult) == MSG_CLOSED)
|
if (MessageWindow::Open(message, &g_corruptAchievementsMessageResult) == MSG_CLOSED)
|
||||||
{
|
{
|
||||||
// Allow user to proceed if the achievement data couldn't be loaded.
|
// Create a new save file if the file was successfully loaded and failed validation.
|
||||||
// Restarting may fix this error, so it isn't worth clearing the data for.
|
// If the file couldn't be opened, restarting may fix this error, so it isn't worth clearing the data for.
|
||||||
if (AchievementManager::Status != EAchStatus::IOError)
|
if (AchievementManager::BinStatus != EAchBinStatus::IOError)
|
||||||
AchievementManager::Save(true);
|
AchievementManager::SaveBinary(true);
|
||||||
|
|
||||||
g_corruptAchievementsMessageOpen = false;
|
g_corruptAchievementsMessageOpen = false;
|
||||||
g_corruptAchievementsMessageOpen.notify_one();
|
g_corruptAchievementsMessageOpen.notify_one();
|
||||||
|
|
@ -135,9 +136,10 @@ void PressStartSaveLoadThreadMidAsmHook()
|
||||||
g_faderBegun.wait(true);
|
g_faderBegun.wait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
AchievementManager::Load();
|
if (!AchievementManager::LoadBinary())
|
||||||
|
LOGFN_ERROR("Failed to load achievement data... (status code {})", (int)AchievementManager::BinStatus);
|
||||||
|
|
||||||
if (AchievementManager::Status != EAchStatus::Success)
|
if (AchievementManager::BinStatus != EAchBinStatus::Success)
|
||||||
{
|
{
|
||||||
g_corruptAchievementsMessageOpen = true;
|
g_corruptAchievementsMessageOpen = true;
|
||||||
g_corruptAchievementsMessageOpen.wait(true);
|
g_corruptAchievementsMessageOpen.wait(true);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include <api/SWA.h>
|
#include <api/SWA.h>
|
||||||
#include <ui/game_window.h>
|
#include <ui/game_window.h>
|
||||||
#include <user/achievement_manager.h>
|
#include <user/achievement_manager.h>
|
||||||
|
#include <user/persistent_storage_manager.h>
|
||||||
#include <user/config.h>
|
#include <user/config.h>
|
||||||
|
|
||||||
void AchievementManagerUnlockMidAsmHook(PPCRegister& id)
|
void AchievementManagerUnlockMidAsmHook(PPCRegister& id)
|
||||||
|
|
@ -172,3 +173,23 @@ PPC_FUNC(sub_82B4DB48)
|
||||||
|
|
||||||
__imp__sub_82B4DB48(ctx, base);
|
__imp__sub_82B4DB48(ctx, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DLC save data flag check.
|
||||||
|
//
|
||||||
|
// The DLC checks are fundamentally broken in this game, resulting in this method always
|
||||||
|
// returning true and displaying the DLC info message when it shouldn't be.
|
||||||
|
//
|
||||||
|
// The original intent here seems to have been to display the message every time new DLC
|
||||||
|
// content is installed, but the flags in the save data never get written to properly,
|
||||||
|
// causing this function to always pass in some way.
|
||||||
|
//
|
||||||
|
// We bypass the save data completely and write to external persistent storage to store
|
||||||
|
// whether we've seen the DLC info message instead. This way we can retain the original
|
||||||
|
// broken game behaviour, whilst also providing a fix for this issue that is safe.
|
||||||
|
PPC_FUNC_IMPL(__imp__sub_824EE620);
|
||||||
|
PPC_FUNC(sub_824EE620)
|
||||||
|
{
|
||||||
|
__imp__sub_824EE620(ctx, base);
|
||||||
|
|
||||||
|
ctx.r3.u32 = PersistentStorageManager::ShouldDisplayDLCMessage(true);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
#include <hid/hid.h>
|
#include <hid/hid.h>
|
||||||
#include <os/logger.h>
|
#include <os/logger.h>
|
||||||
#include <user/achievement_manager.h>
|
#include <user/achievement_manager.h>
|
||||||
|
#include <user/persistent_storage_manager.h>
|
||||||
#include <user/config.h>
|
#include <user/config.h>
|
||||||
#include <app.h>
|
#include <app.h>
|
||||||
|
|
||||||
bool m_isSavedAchievementData = false;
|
|
||||||
|
|
||||||
// SWA::Message::MsgRequestStartLoading::Impl
|
// SWA::Message::MsgRequestStartLoading::Impl
|
||||||
PPC_FUNC_IMPL(__imp__sub_824DCF38);
|
PPC_FUNC_IMPL(__imp__sub_824DCF38);
|
||||||
PPC_FUNC(sub_824DCF38)
|
PPC_FUNC(sub_824DCF38)
|
||||||
|
|
@ -99,20 +98,23 @@ PPC_FUNC(sub_824E5170)
|
||||||
|
|
||||||
App::s_isSaving = pSaveIcon->m_IsVisible;
|
App::s_isSaving = pSaveIcon->m_IsVisible;
|
||||||
|
|
||||||
|
static bool isSavedExtraData = false;
|
||||||
|
|
||||||
if (pSaveIcon->m_IsVisible)
|
if (pSaveIcon->m_IsVisible)
|
||||||
{
|
{
|
||||||
App::s_isSaveDataCorrupt = false;
|
App::s_isSaveDataCorrupt = false;
|
||||||
|
|
||||||
if (!m_isSavedAchievementData)
|
if (!isSavedExtraData)
|
||||||
{
|
{
|
||||||
AchievementManager::Save();
|
AchievementManager::SaveBinary();
|
||||||
|
PersistentStorageManager::SaveBinary();
|
||||||
|
|
||||||
m_isSavedAchievementData = true;
|
isSavedExtraData = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_isSavedAchievementData = false;
|
isSavedExtraData = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ bool AchievementData::VerifySignature() const
|
||||||
|
|
||||||
bool AchievementData::VerifyVersion() const
|
bool AchievementData::VerifyVersion() const
|
||||||
{
|
{
|
||||||
return Version == AchVersion ACH_VERSION;
|
return Version <= ACH_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AchievementData::VerifyChecksum()
|
bool AchievementData::VerifyChecksum()
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,12 @@
|
||||||
|
|
||||||
#define ACH_FILENAME "ACH-DATA"
|
#define ACH_FILENAME "ACH-DATA"
|
||||||
#define ACH_SIGNATURE { 'A', 'C', 'H', ' ' }
|
#define ACH_SIGNATURE { 'A', 'C', 'H', ' ' }
|
||||||
#define ACH_VERSION { 1, 0, 0 }
|
#define ACH_VERSION 1
|
||||||
#define ACH_RECORDS 50
|
#define ACH_RECORDS 50
|
||||||
|
|
||||||
class AchievementData
|
class AchievementData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct AchVersion
|
|
||||||
{
|
|
||||||
uint8_t Major;
|
|
||||||
uint8_t Minor;
|
|
||||||
uint8_t Revision;
|
|
||||||
uint8_t Reserved;
|
|
||||||
|
|
||||||
bool operator==(const AchVersion& other) const
|
|
||||||
{
|
|
||||||
return Major == other.Major &&
|
|
||||||
Minor == other.Minor &&
|
|
||||||
Revision == other.Revision;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct AchRecord
|
struct AchRecord
|
||||||
{
|
{
|
||||||
|
|
@ -35,10 +20,10 @@ public:
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
char Signature[4] ACH_SIGNATURE;
|
char Signature[4] ACH_SIGNATURE;
|
||||||
AchVersion Version ACH_VERSION;
|
uint32_t Version{ ACH_VERSION };
|
||||||
uint32_t Checksum;
|
uint32_t Checksum{};
|
||||||
uint32_t Reserved;
|
uint32_t Reserved{};
|
||||||
AchRecord Records[ACH_RECORDS];
|
AchRecord Records[ACH_RECORDS]{};
|
||||||
|
|
||||||
bool VerifySignature() const;
|
bool VerifySignature() const;
|
||||||
bool VerifyVersion() const;
|
bool VerifyVersion() const;
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,11 @@ void AchievementManager::Reset()
|
||||||
*(bool*)g_memory.Translate(0x833647C4) = false;
|
*(bool*)g_memory.Translate(0x833647C4) = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementManager::Load()
|
bool AchievementManager::LoadBinary()
|
||||||
{
|
{
|
||||||
AchievementManager::Reset();
|
AchievementManager::Reset();
|
||||||
|
|
||||||
Status = EAchStatus::Success;
|
BinStatus = EAchBinStatus::Success;
|
||||||
|
|
||||||
auto dataPath = GetDataPath(true);
|
auto dataPath = GetDataPath(true);
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ void AchievementManager::Load()
|
||||||
dataPath = GetDataPath(false);
|
dataPath = GetDataPath(false);
|
||||||
|
|
||||||
if (!std::filesystem::exists(dataPath))
|
if (!std::filesystem::exists(dataPath))
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
@ -128,16 +128,16 @@ void AchievementManager::Load()
|
||||||
|
|
||||||
if (fileSize != dataSize)
|
if (fileSize != dataSize)
|
||||||
{
|
{
|
||||||
Status = EAchStatus::BadFileSize;
|
BinStatus = EAchBinStatus::BadFileSize;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream file(dataPath, std::ios::binary);
|
std::ifstream file(dataPath, std::ios::binary);
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
{
|
{
|
||||||
Status = EAchStatus::IOError;
|
BinStatus = EAchBinStatus::IOError;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AchievementData data{};
|
AchievementData data{};
|
||||||
|
|
@ -146,19 +146,18 @@ void AchievementManager::Load()
|
||||||
|
|
||||||
if (!data.VerifySignature())
|
if (!data.VerifySignature())
|
||||||
{
|
{
|
||||||
Status = EAchStatus::BadSignature;
|
BinStatus = EAchBinStatus::BadSignature;
|
||||||
file.close();
|
file.close();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.read((char*)&data.Version, sizeof(data.Version));
|
file.read((char*)&data.Version, sizeof(data.Version));
|
||||||
|
|
||||||
// TODO: upgrade in future if the version changes.
|
|
||||||
if (!data.VerifyVersion())
|
if (!data.VerifyVersion())
|
||||||
{
|
{
|
||||||
Status = EAchStatus::BadVersion;
|
BinStatus = EAchBinStatus::BadVersion;
|
||||||
file.close();
|
file.close();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.seekg(0);
|
file.seekg(0);
|
||||||
|
|
@ -166,22 +165,24 @@ void AchievementManager::Load()
|
||||||
|
|
||||||
if (!data.VerifyChecksum())
|
if (!data.VerifyChecksum())
|
||||||
{
|
{
|
||||||
Status = EAchStatus::BadChecksum;
|
BinStatus = EAchBinStatus::BadChecksum;
|
||||||
file.close();
|
file.close();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
memcpy(&Data, &data, dataSize);
|
memcpy(&Data, &data, dataSize);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementManager::Save(bool ignoreStatus)
|
bool AchievementManager::SaveBinary(bool ignoreStatus)
|
||||||
{
|
{
|
||||||
if (!ignoreStatus && Status != EAchStatus::Success)
|
if (!ignoreStatus && BinStatus != EAchBinStatus::Success)
|
||||||
{
|
{
|
||||||
LOGN_WARNING("Achievement data will not be saved in this session!");
|
LOGN_WARNING("Achievement data will not be saved in this session!");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGN("Saving achievements...");
|
LOGN("Saving achievements...");
|
||||||
|
|
@ -191,7 +192,7 @@ void AchievementManager::Save(bool ignoreStatus)
|
||||||
if (!file)
|
if (!file)
|
||||||
{
|
{
|
||||||
LOGN_ERROR("Failed to write achievement data.");
|
LOGN_ERROR("Failed to write achievement data.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Data.Checksum = Data.CalculateChecksum();
|
Data.Checksum = Data.CalculateChecksum();
|
||||||
|
|
@ -199,5 +200,7 @@ void AchievementManager::Save(bool ignoreStatus)
|
||||||
file.write((const char*)&Data, sizeof(AchievementData));
|
file.write((const char*)&Data, sizeof(AchievementData));
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
Status = EAchStatus::Success;
|
BinStatus = EAchBinStatus::Success;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <user/achievement_data.h>
|
#include <user/achievement_data.h>
|
||||||
|
|
||||||
enum class EAchStatus
|
enum class EAchBinStatus
|
||||||
{
|
{
|
||||||
Success,
|
Success,
|
||||||
IOError,
|
IOError,
|
||||||
|
|
@ -16,7 +16,7 @@ class AchievementManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static inline AchievementData Data{};
|
static inline AchievementData Data{};
|
||||||
static inline EAchStatus Status{};
|
static inline EAchBinStatus BinStatus{ EAchBinStatus::Success };
|
||||||
|
|
||||||
static std::filesystem::path GetDataPath(bool checkForMods)
|
static std::filesystem::path GetDataPath(bool checkForMods)
|
||||||
{
|
{
|
||||||
|
|
@ -29,6 +29,6 @@ public:
|
||||||
static void Unlock(uint16_t id);
|
static void Unlock(uint16_t id);
|
||||||
static void UnlockAll();
|
static void UnlockAll();
|
||||||
static void Reset();
|
static void Reset();
|
||||||
static void Load();
|
static bool LoadBinary();
|
||||||
static void Save(bool ignoreStatus = false);
|
static bool SaveBinary(bool ignoreStatus = false);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
13
UnleashedRecomp/user/persistent_data.cpp
Normal file
13
UnleashedRecomp/user/persistent_data.cpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#include "persistent_data.h"
|
||||||
|
|
||||||
|
bool PersistentData::VerifySignature() const
|
||||||
|
{
|
||||||
|
char sig[4] = EXT_SIGNATURE;
|
||||||
|
|
||||||
|
return memcmp(Signature, sig, sizeof(Signature)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PersistentData::VerifyVersion() const
|
||||||
|
{
|
||||||
|
return Version <= EXT_VERSION;
|
||||||
|
}
|
||||||
30
UnleashedRecomp/user/persistent_data.h
Normal file
30
UnleashedRecomp/user/persistent_data.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <user/paths.h>
|
||||||
|
|
||||||
|
#define EXT_FILENAME "EXT-DATA"
|
||||||
|
#define EXT_SIGNATURE { 'E', 'X', 'T', ' ' }
|
||||||
|
#define EXT_VERSION 1
|
||||||
|
|
||||||
|
enum class EDLCFlag
|
||||||
|
{
|
||||||
|
ApotosAndShamar,
|
||||||
|
Spagonia,
|
||||||
|
Chunnan,
|
||||||
|
Mazuri,
|
||||||
|
Holoska,
|
||||||
|
EmpireCityAndAdabat,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
class PersistentData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
char Signature[4] EXT_SIGNATURE;
|
||||||
|
uint32_t Version{ EXT_VERSION };
|
||||||
|
uint64_t Reserved{};
|
||||||
|
bool DLCFlags[6]{};
|
||||||
|
|
||||||
|
bool VerifySignature() const;
|
||||||
|
bool VerifyVersion() const;
|
||||||
|
};
|
||||||
117
UnleashedRecomp/user/persistent_storage_manager.cpp
Normal file
117
UnleashedRecomp/user/persistent_storage_manager.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
#include "persistent_storage_manager.h"
|
||||||
|
#include <install/installer.h>
|
||||||
|
#include <os/logger.h>
|
||||||
|
#include <user/paths.h>
|
||||||
|
|
||||||
|
bool PersistentStorageManager::ShouldDisplayDLCMessage(bool setOffendingDLCFlag)
|
||||||
|
{
|
||||||
|
if (BinStatus != EExtBinStatus::Success)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
static std::unordered_map<EDLCFlag, DLC> flags =
|
||||||
|
{
|
||||||
|
{ EDLCFlag::ApotosAndShamar, DLC::ApotosShamar },
|
||||||
|
{ EDLCFlag::Spagonia, DLC::Spagonia },
|
||||||
|
{ EDLCFlag::Chunnan, DLC::Chunnan },
|
||||||
|
{ EDLCFlag::Mazuri, DLC::Mazuri },
|
||||||
|
{ EDLCFlag::Holoska, DLC::Holoska },
|
||||||
|
{ EDLCFlag::EmpireCityAndAdabat, DLC::EmpireCityAdabat }
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = false;
|
||||||
|
|
||||||
|
for (auto& pair : flags)
|
||||||
|
{
|
||||||
|
if (!Data.DLCFlags[(int)pair.first] && Installer::checkDLCInstall(GetGamePath(), pair.second))
|
||||||
|
{
|
||||||
|
if (setOffendingDLCFlag)
|
||||||
|
Data.DLCFlags[(int)pair.first] = true;
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PersistentStorageManager::LoadBinary()
|
||||||
|
{
|
||||||
|
BinStatus = EExtBinStatus::Success;
|
||||||
|
|
||||||
|
auto dataPath = GetDataPath(true);
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(dataPath))
|
||||||
|
{
|
||||||
|
// Try loading base persistent data as fallback.
|
||||||
|
dataPath = GetDataPath(false);
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(dataPath))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
auto fileSize = std::filesystem::file_size(dataPath, ec);
|
||||||
|
auto dataSize = sizeof(PersistentData);
|
||||||
|
|
||||||
|
if (fileSize != dataSize)
|
||||||
|
{
|
||||||
|
BinStatus = EExtBinStatus::BadFileSize;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream file(dataPath, std::ios::binary);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
BinStatus = EExtBinStatus::IOError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistentData data{};
|
||||||
|
|
||||||
|
file.read((char*)&data.Signature, sizeof(data.Signature));
|
||||||
|
|
||||||
|
if (!data.VerifySignature())
|
||||||
|
{
|
||||||
|
BinStatus = EExtBinStatus::BadSignature;
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.read((char*)&data.Version, sizeof(data.Version));
|
||||||
|
|
||||||
|
if (!data.VerifyVersion())
|
||||||
|
{
|
||||||
|
BinStatus = EExtBinStatus::BadVersion;
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seekg(0);
|
||||||
|
file.read((char*)&data, sizeof(data));
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
memcpy(&Data, &data, dataSize);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PersistentStorageManager::SaveBinary()
|
||||||
|
{
|
||||||
|
LOGN("Saving persistent storage binary...");
|
||||||
|
|
||||||
|
std::ofstream file(GetDataPath(true), std::ios::binary);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
LOGN_ERROR("Failed to write persistent storage binary.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write((const char*)&Data, sizeof(PersistentData));
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
BinStatus = EExtBinStatus::Success;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
28
UnleashedRecomp/user/persistent_storage_manager.h
Normal file
28
UnleashedRecomp/user/persistent_storage_manager.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <user/persistent_data.h>
|
||||||
|
|
||||||
|
enum class EExtBinStatus
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
IOError,
|
||||||
|
BadFileSize,
|
||||||
|
BadSignature,
|
||||||
|
BadVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
class PersistentStorageManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static inline PersistentData Data{};
|
||||||
|
static inline EExtBinStatus BinStatus{ EExtBinStatus::Success };
|
||||||
|
|
||||||
|
static std::filesystem::path GetDataPath(bool checkForMods)
|
||||||
|
{
|
||||||
|
return GetSavePath(checkForMods) / EXT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ShouldDisplayDLCMessage(bool setOffendingDLCFlag);
|
||||||
|
static bool LoadBinary();
|
||||||
|
static bool SaveBinary();
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue