public async Task <PatchInfo[]> RunAsync(DownloadConfiguration downloadConfiguration, PatchCache patchCache, CancellationToken ct = default) { Progress.Progress = 0; Progress.IsIndeterminate = true; App.Logger.Info(nameof(ComparePhase), "Fetching patches"); var patches = await PatchInfo.FetchPatchInfosAsync(_InstallConfiguration, downloadConfiguration, ct); App.Logger.Info(nameof(ComparePhase), "Fetching cache data"); var cacheData = await patchCache.SelectAllAsync(); App.Logger.Info(nameof(ComparePhase), "Pre-fetching file times"); var preFetchedFileTimes = await Task.Run(() => _PreFetchFileTimes()); var workingPatches = patches .Select(p => new UpdateInfo { PatchInfo = p, ShouldUpdate = true, LastWriteFileTime = preFetchedFileTimes.ContainsKey(p.Name) ? preFetchedFileTimes[p.Name] : 0 }) .ToArray(); var nextIndexAtomic = 0; var progressValueAtomic = 0; var errorTokenSource = new CancellationTokenSource(); var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct, errorTokenSource.Token); App.Logger.Info(nameof(ComparePhase), "Comparing files"); Progress.IsIndeterminate = false; void ProcessLoop() { try { var pso2ExecutableFileName = Path.GetFileName(_InstallConfiguration.PSO2Executable); while (true) { var index = Interlocked.Increment(ref nextIndexAtomic) - 1; if (index >= workingPatches.Length) { return; } combinedTokenSource.Token.ThrowIfCancellationRequested(); try { ref var patch = ref workingPatches[index]; var relativeFilePath = patch.PatchInfo.Name .Substring(0, patch.PatchInfo.Name.Length - 4) .Replace('/', Path.DirectorySeparatorChar) .ToLower(); var filePath = Path.Combine(_InstallConfiguration.PSO2BinDirectory, relativeFilePath); var fileName = Path.GetFileName(relativeFilePath); // skip this if mod files are not enabled so they are marked as invalid if (Properties.Settings.Default.ModFilesEnabled) { // skip file if a file with the same name exists in the mods folder if (Path.GetDirectoryName(filePath).ToLower() == _InstallConfiguration.DataWin32Directory.ToLower()) { var modFilePath = Path.Combine(_InstallConfiguration.ModsDirectory, fileName); if (File.Exists(modFilePath)) { patch.ShouldUpdate = false; continue; } } } if (!cacheData.ContainsKey(patch.PatchInfo.Name)) { continue; } var cacheEntry = cacheData[patch.PatchInfo.Name]; if (patch.PatchInfo.Hash != cacheEntry.Hash) { continue; } // skip pso2.exe if the file is large address aware patched if (fileName == pso2ExecutableFileName && File.Exists(filePath) && Properties.Settings.Default.LargeAddressAwareEnabled && LargeAddressAware.IsLargeAddressAwarePactchApplied(_InstallConfiguration, patch.PatchInfo.Hash)) { patch.ShouldUpdate = false; continue; } if (patch.LastWriteFileTime == 0) { patch.LastWriteFileTime = new FileInfo(filePath).LastWriteTimeUtc.ToFileTimeUtc(); } patch.ShouldUpdate = patch.LastWriteFileTime != cacheEntry.LastWriteTime; } finally { Interlocked.Increment(ref progressValueAtomic); } } }
private async Task _InstallAsync(PatchCache patchCache, PluginInfo pluginInfo, CancellationToken ct = default) { App.Current.Logger.Info(nameof(EnglishPatchPhase), "Downloading english translation information"); var translation = await TranslationInfo.FetchEnglishAsync(ct); App.Current.Logger.Info(nameof(EnglishPatchPhase), "Getting data from patch cache"); var cacheData = await patchCache.SelectAllAsync(); string CreateRelativePath(string path) { var root = new Uri(_InstallConfiguration.PSO2BinDirectory); var relative = root.MakeRelativeUri(new Uri(path)); return(relative.OriginalString); } bool Verify(string path, string hash) { var relative = CreateRelativePath(path); var info = new FileInfo(path); if (info.Exists == false) { return(false); } if (cacheData.ContainsKey(relative) == false) { return(false); } var data = cacheData[relative]; if (data.Hash != hash) { return(false); } if (data.LastWriteTime != info.LastWriteTimeUtc.ToFileTimeUtc()) { return(false); } return(true); } using (var client = new ArksLayerHttpClient()) { async Task VerifyAndDownlodRar(string path, string downloadHash, Uri downloadPath) { if (Verify(path, downloadHash) == false) { App.Current.Logger.Info(nameof(EnglishPatchPhase), $"Downloading \"{Path.GetFileName(downloadPath.LocalPath)}\""); using (var response = await client.GetAsync(downloadPath)) using (var stream = await response.Content.ReadAsStreamAsync()) using (var archive = RarArchive.Open(stream)) { foreach (var file in archive.Entries.Where(e => !e.IsDirectory)) { var filePath = Path.Combine(_InstallConfiguration.ArksLayer.PatchesDirectory, file.Key); using (var fs = File.Create(filePath, 4096, FileOptions.Asynchronous)) await file.OpenEntryStream().CopyToAsync(fs); await patchCache.InsertUnderTransactionAsync(new[] { new PatchCacheEntry() { Name = CreateRelativePath(filePath), Hash = downloadHash, LastWriteTime = new FileInfo(filePath).LastWriteTimeUtc.ToFileTimeUtc() } }); } } } } await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishBlockPatch, translation.BlockMD5, new Uri(translation.BlockPatch)); await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishItemPatch, translation.ItemMD5, new Uri(translation.ItemPatch)); await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishTextPatch, translation.TextMD5, new Uri(translation.TextPatch)); await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishTitlePatch, translation.TitleMD5, new Uri(translation.TitlePatch)); } App.Current.Logger.Info(nameof(EnglishPatchPhase), "Validating plugin dlls"); await pluginInfo.PSO2BlockRenameDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2BlockRenameDll, ct); await pluginInfo.PSO2ItemTranslatorDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2ItemTranslatorDll, ct); await pluginInfo.PSO2TitleTranslatorDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2TitleTranslatorDll, ct); await pluginInfo.PSO2RAISERSystemDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2RAISERSystemDll, ct); }