mirror of
				https://github.com/hedge-dev/UnleashedRecomp.git
				synced 2025-10-30 07:11:05 +00:00 
			
		
		
		
	Add CLI installation check option. (#1387)
* Add CLI installation check option. * Show the console for Windows Release builds. --------- Co-authored-by: Skyth <19259897+blueskythlikesclouds@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									1dd5ba7fcd
								
							
						
					
					
						commit
						53596b8e5c
					
				
					 4 changed files with 241 additions and 32 deletions
				
			
		|  | @ -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) { | ||||
|     const std::string filename(pair.first); | ||||
|     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) | ||||
| { | ||||
|     modulePath = baseDirectory / PatchedDirectory / GameExecutableFile; | ||||
|  | @ -254,6 +360,40 @@ bool Installer::checkAllDLC(const std::filesystem::path& baseDirectory) | |||
|     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) | ||||
| { | ||||
|     for (FilePair pair : filePairs) | ||||
|  | @ -272,6 +412,27 @@ bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint | |||
|     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) | ||||
| { | ||||
|     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); | ||||
|         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; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -75,7 +75,9 @@ 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 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 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 parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal); | ||||
|     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." } | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "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", | ||||
|         { | ||||
|  |  | |||
|  | @ -201,6 +201,7 @@ int main(int argc, char *argv[]) | |||
|     bool forceInstaller = false; | ||||
|     bool forceDLCInstaller = false; | ||||
|     bool useDefaultWorkingDirectory = false; | ||||
|     bool forceInstallationCheck = false; | ||||
|     const char *sdlVideoDriver = nullptr; | ||||
| 
 | ||||
|     for (uint32_t i = 1; i < argc; i++) | ||||
|  | @ -208,6 +209,7 @@ int main(int argc, char *argv[]) | |||
|         forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0); | ||||
|         forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0); | ||||
|         useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0); | ||||
|         forceInstallationCheck = forceInstallationCheck || (strcmp(argv[i], "--install-check") == 0); | ||||
| 
 | ||||
|         if (strcmp(argv[i], "--sdl-video-driver") == 0) | ||||
|         { | ||||
|  | @ -230,6 +232,59 @@ int main(int argc, char *argv[]) | |||
|     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) | ||||
|     for (auto& dll : g_D3D12RequiredModules) | ||||
|     { | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Darío
						Darío