From f462e2fecdea6fdd2fdbfb2e0c3b0b7f7c2e1b8c Mon Sep 17 00:00:00 2001 From: aperezro Date: Mon, 8 Jun 2026 17:49:23 -0600 Subject: [PATCH] Harden iOS install validation diagnostics --- UnleashedRecomp/install/installer.cpp | 38 ++++++++++++++++++++ UnleashedRecomp/install/installer.h | 1 + UnleashedRecomp/main.cpp | 50 +++++++++++++++++++++++++-- UnleashedRecomp/res/version.txt | 2 +- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/UnleashedRecomp/install/installer.cpp b/UnleashedRecomp/install/installer.cpp index 35465bbd..1a1f9a4e 100644 --- a/UnleashedRecomp/install/installer.cpp +++ b/UnleashedRecomp/install/installer.cpp @@ -357,6 +357,15 @@ bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory, std #ifdef UNLEASHED_RECOMP_IOS if (!std::filesystem::exists(baseDirectory / InstallValidationFile)) return false; + + Journal journal; + if (!checkInstallCompleteness(baseDirectory, journal, []() + { + return true; + })) + { + return false; + } #endif return true; @@ -396,6 +405,35 @@ bool Installer::checkAllDLC(const std::filesystem::path& baseDirectory) return result; } +bool Installer::checkInstallCompleteness(const std::filesystem::path &baseDirectory, Journal &journal, const std::function &progressCallback) +{ + if (!checkFiles({ GameFiles, GameFilesSize }, GameHashes, baseDirectory / GameDirectory, journal, progressCallback, true)) + { + return false; + } + + if (!checkFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, baseDirectory / UpdateDirectory, journal, progressCallback, true)) + { + 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, true)) + { + return false; + } + } + } + + return true; +} + bool Installer::checkInstallIntegrity(const std::filesystem::path &baseDirectory, Journal &journal, const std::function &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. diff --git a/UnleashedRecomp/install/installer.h b/UnleashedRecomp/install/installer.h index b39c583f..9d9d6e1e 100644 --- a/UnleashedRecomp/install/installer.h +++ b/UnleashedRecomp/install/installer.h @@ -75,6 +75,7 @@ struct Installer static bool checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath); static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc); static bool checkAllDLC(const std::filesystem::path &baseDirectory); + static bool checkInstallCompleteness(const std::filesystem::path &baseDirectory, Journal &journal, const std::function &progressCallback); static bool checkInstallIntegrity(const std::filesystem::path &baseDirectory, Journal &journal, const std::function &progressCallback); static bool computeTotalSize(std::span filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize); static bool checkFiles(std::span filePairs, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, Journal &journal, const std::function &progressCallback, bool checkSizeOnly); diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index ad2526e8..eddf33e4 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -49,6 +49,12 @@ Heap g_userHeap; XDBFWrapper g_xdbfWrapper; std::unordered_map g_xdbfTextureCache; +static std::string PathToUTF8(const std::filesystem::path& path) +{ + auto pathU8 = path.u8string(); + return std::string(pathU8.begin(), pathU8.end()); +} + void HostStartup() { #ifdef _WIN32 @@ -207,6 +213,10 @@ int main(int argc, char *argv[]) os::logger::Init(); +#ifdef UNLEASHED_RECOMP_IOS + LOGN("iOS startup build: install-validation-v3"); +#endif + PreloadContext preloadContext; preloadContext.PreloadExecutable(); @@ -248,10 +258,14 @@ int main(int argc, char *argv[]) // 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(); + const auto installCheckPath = GetGamePath(); + fprintf(stdout, "Checking installation at %s\n", PathToUTF8(installCheckPath).c_str()); + LOGFN("Checking installation at {}", PathToUTF8(installCheckPath)); + Journal journal; double lastProgressMiB = 0.0; double lastTotalMib = 0.0; - Installer::checkInstallIntegrity(GAME_INSTALL_DIRECTORY, journal, [&]() + Installer::checkInstallIntegrity(installCheckPath, journal, [&]() { constexpr double MiBDivisor = 1024.0 * 1024.0; constexpr double MiBProgressThreshold = 128.0; @@ -326,8 +340,16 @@ int main(int argc, char *argv[]) HostStartup(); + const auto gamePath = GetGamePath(); std::filesystem::path modulePath; - bool isGameInstalled = Installer::checkGameInstall(GetGamePath(), modulePath); + LOGFN("Game path: {}", PathToUTF8(gamePath)); + bool isGameInstalled = Installer::checkGameInstall(gamePath, modulePath); + LOGFN("Install status before wizard: installed={}, forceInstaller={}, forceDLCInstaller={}, modulePath={}", + isGameInstalled, + forceInstaller, + forceDLCInstaller, + PathToUTF8(modulePath)); + bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled; if (runInstallerWizard) { @@ -337,10 +359,21 @@ int main(int argc, char *argv[]) std::_Exit(1); } - if (!InstallerWizard::Run(GetGamePath(), isGameInstalled && forceDLCInstaller)) + if (!InstallerWizard::Run(gamePath, isGameInstalled && forceDLCInstaller)) { + LOGN("Installer wizard cancelled."); std::_Exit(0); } + + isGameInstalled = Installer::checkGameInstall(gamePath, modulePath); + LOGFN("Install status after wizard: installed={}, modulePath={}", isGameInstalled, PathToUTF8(modulePath)); + if (!isGameInstalled) + { + const std::string errorMessage = "The game data installation did not finish correctly. Please run the installer again and select the base game ISO plus update file."; + LOGN_ERROR(errorMessage); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), errorMessage.c_str(), GameWindow::s_pWindow); + std::_Exit(4); + } } ModLoader::Init(); @@ -350,7 +383,16 @@ int main(int argc, char *argv[]) KiSystemStartup(); + LOGFN("Loading module from {}", PathToUTF8(modulePath)); uint32_t entry = LdrLoadModule(modulePath); + if (entry == 0) + { + const std::string errorMessage = "Failed to load the patched game executable. Please run the installer again."; + LOGN_ERROR(errorMessage); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), errorMessage.c_str(), GameWindow::s_pWindow); + std::_Exit(4); + } + LOGFN("Loaded module entry: 0x{:08X}", entry); if (!runInstallerWizard) { @@ -361,8 +403,10 @@ int main(int argc, char *argv[]) } } + LOGN("Starting pipeline precompilation."); Video::StartPipelinePrecompilation(); + LOGFN("Starting guest thread at 0x{:08X}", entry); GuestThread::Start({ entry, 0, 0 }); return 0; diff --git a/UnleashedRecomp/res/version.txt b/UnleashedRecomp/res/version.txt index d9130f4a..ce05700d 100644 --- a/UnleashedRecomp/res/version.txt +++ b/UnleashedRecomp/res/version.txt @@ -1,4 +1,4 @@ VERSION_MILESTONE="" VERSION_MAJOR=1 VERSION_MINOR=0 -VERSION_REVISION=3 +VERSION_REVISION=4