/// <summary> /// Attempts to load a game in a specified path, returning a <see cref="PackageGroup"/> object with the /// packages in the path and an <see cref="SerializedTree"/> with a fully qualified node tree of the /// package group. /// If no packages are found, then this method will return null in both fields. /// </summary> /// <param name="gameAlias">The alias of the game at the path.</param> /// <param name="gamePath">The path to load as a game.</param> /// <param name="ct">A cancellation token.</param> /// <param name="progress">An <see cref="IProgress{GameLoadingProgress}"/> object for progress reporting.</param> /// <returns>A tuple with a package group and a node tree for the requested game.</returns> public async Task <(PackageGroup?packageGroup, SerializedTree?nodeTree)> LoadGameAsync ( string gameAlias, string gamePath, CancellationToken ct, IProgress <GameLoadingProgress>?progress = null ) { progress?.Report(new GameLoadingProgress { CompletionPercentage = 0.0f, State = GameLoadingState.SettingUp, Alias = gameAlias }); var packagePaths = Directory.EnumerateFiles ( gamePath, "*", SearchOption.AllDirectories ) .Where(p => p.EndsWith(".mpq", StringComparison.InvariantCultureIgnoreCase)) .OrderBy(p => p) .ToList(); if (packagePaths.Count == 0) { return(null, null); } var packageSetHash = GeneratePathSetHash(packagePaths); var packageTreeFilename = $".{packageSetHash}.tree"; var packageTreeFilePath = Path.Combine(gamePath, packageTreeFilename); var packageGroup = new PackageGroup(packageSetHash); SerializedTree?nodeTree = null; var generateTree = true; if (File.Exists(packageTreeFilePath)) { progress?.Report(new GameLoadingProgress { CompletionPercentage = 0, State = GameLoadingState.LoadingNodeTree, Alias = gameAlias }); try { // Load tree nodeTree = new SerializedTree(File.OpenRead(packageTreeFilePath)); generateTree = false; } catch (FileNotFoundException) { Log.Error("No file for the node tree found at the given location."); } catch (NotSupportedException) { Log.Info("Unsupported node tree version present. Deleting and regenerating."); File.Delete(packageTreeFilePath); } } if (generateTree) { // Internal counters for progress reporting double completedSteps = 0; double totalSteps = packagePaths.Count * 2; // Load packages var packages = new List <(string packageName, IPackage package)>(); foreach (var packagePath in packagePaths) { ct.ThrowIfCancellationRequested(); progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.LoadingPackages, Alias = gameAlias }); try { var package = await PackageInteractionHandler.LoadAsync(packagePath); packages.Add((Path.GetFileNameWithoutExtension(packagePath), package)); } catch (FileLoadException fex) { Log.Warn($"Failed to load archive {Path.GetFileNameWithoutExtension(packagePath)}: {fex.Message}"); } ++completedSteps; } // Load dictionary if neccesary if (_dictionary == null) { progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.LoadingDictionary, Alias = gameAlias }); _dictionary = await LoadDictionaryAsync(ct); } // Generate node tree var builder = new TreeBuilder(); foreach (var packageInfo in packages) { ct.ThrowIfCancellationRequested(); progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.BuildingNodeTree, Alias = gameAlias }); var steps = completedSteps; var createNodesProgress = new Progress <PackageNodesCreationProgress> ( p => { progress?.Report ( new GameLoadingProgress { CompletionPercentage = steps / totalSteps, State = GameLoadingState.BuildingNodeTree, Alias = gameAlias, CurrentPackage = packageInfo.packageName, NodesCreationProgress = p } ); } ); await Task.Run(() => builder.AddPackage(packageInfo.packageName, packageInfo.package, createNodesProgress, ct), ct); packageGroup.AddPackage((PackageInteractionHandler)packageInfo.package); ++completedSteps; } // Build node tree var tree = builder.GetTree(); var optimizeTreeProgress = new Progress <TreeOptimizationProgress> ( p => { progress?.Report ( new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.BuildingNodeTree, Alias = gameAlias, OptimizationProgress = p } ); } ); var optimizer = new TreeOptimizer(_dictionary); var treeClosureCopy = tree; tree = await Task.Run(() => optimizer.OptimizeTree(treeClosureCopy, optimizeTreeProgress, ct), ct); using (var fs = File.OpenWrite(packageTreeFilePath)) { using (var serializer = new TreeSerializer(fs)) { await serializer.SerializeAsync(tree, ct); } } nodeTree = new SerializedTree(File.OpenRead(packageTreeFilePath)); } else { progress?.Report(new GameLoadingProgress { CompletionPercentage = 1, State = GameLoadingState.LoadingPackages, Alias = gameAlias }); // Load packages packageGroup = await PackageGroup.LoadAsync(gameAlias, packageSetHash, gamePath, ct, progress); } progress?.Report(new GameLoadingProgress { CompletionPercentage = 1, State = GameLoadingState.Loading, Alias = gameAlias }); return(packageGroup, nodeTree); }
/// <summary> /// Attempts to load a game in a specified path, returning a <see cref="PackageGroup"/> object with the /// packages in the path and an <see cref="OptimizedNodeTree"/> with a fully qualified node tree of the /// package group. /// If no packages are found, then this method will return null in both fields. /// </summary> /// <param name="gameAlias">The alias of the game at the path.</param> /// <param name="gamePath">The path to load as a game.</param> /// <param name="ct">A cancellation token.</param> /// <param name="progress">An <see cref="IProgress{GameLoadingProgress}"/> object for progress reporting.</param> /// <returns>A tuple with a package group and a node tree for the requested game.</returns> public async Task <(PackageGroup packageGroup, OptimizedNodeTree nodeTree)> LoadGameAsync( string gameAlias, string gamePath, CancellationToken ct, IProgress <GameLoadingProgress> progress = null) { progress?.Report(new GameLoadingProgress { CompletionPercentage = 0.0f, State = GameLoadingState.SettingUp, Alias = gameAlias }); List <string> packagePaths = Directory.EnumerateFiles ( gamePath, "*", SearchOption.AllDirectories ) .Where(p => p.EndsWith(".mpq", StringComparison.InvariantCultureIgnoreCase)) .OrderBy(p => p) .ToList(); if (packagePaths.Count == 0) { return(null, null); } string packageSetHash = GeneratePathSetHash(packagePaths); string packageTreeFilename = $".{packageSetHash}.tree"; string packageTreeFilePath = Path.Combine(gamePath, packageTreeFilename); PackageGroup packageGroup = new PackageGroup(packageSetHash); OptimizedNodeTree nodeTree = null; bool generateTree = true; if (File.Exists(packageTreeFilePath)) { progress?.Report(new GameLoadingProgress { CompletionPercentage = 0, State = GameLoadingState.LoadingNodeTree, Alias = gameAlias }); try { // Load tree nodeTree = new OptimizedNodeTree(packageTreeFilePath); generateTree = false; } catch (NodeTreeNotFoundException) { Log.Error("No file for the node tree found at the given location."); } catch (UnsupportedNodeTreeVersionException) { Log.Info("Unsupported node tree version present. Deleting and regenerating."); File.Delete(packageTreeFilePath); } } if (generateTree) { // Internal counters for progress reporting double completedSteps = 0; double totalSteps = packagePaths.Count * 2; // Load packages List <(string packageName, IPackage package)> packages = new List <(string packageName, IPackage package)>(); foreach (string packagePath in packagePaths) { ct.ThrowIfCancellationRequested(); progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.LoadingPackages, Alias = gameAlias }); try { PackageInteractionHandler package = await PackageInteractionHandler.LoadAsync(packagePath); packages.Add((Path.GetFileNameWithoutExtension(packagePath), package)); } catch (FileLoadException fex) { Log.Warn($"Failed to load archive {Path.GetFileNameWithoutExtension(packagePath)}: {fex.Message}"); } ++completedSteps; } // Load dictionary if neccesary if (this.Dictionary == null) { progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.LoadingDictionary, Alias = gameAlias }); this.Dictionary = await LoadDictionaryAsync(ct); } // Generate node tree MultiPackageNodeTreeBuilder multiBuilder = new MultiPackageNodeTreeBuilder(this.Dictionary); foreach (var packageInfo in packages) { ct.ThrowIfCancellationRequested(); progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.BuildingNodeTree, Alias = gameAlias }); await multiBuilder.ConsumePackageAsync(packageInfo.packageName, packageInfo.package, ct); packageGroup.AddPackage((PackageInteractionHandler)packageInfo.package); ++completedSteps; } // Build node tree multiBuilder.Build(); // Save it to disk File.WriteAllBytes(packageTreeFilePath, multiBuilder.CreateTree()); nodeTree = new OptimizedNodeTree(packageTreeFilePath); } else { progress?.Report(new GameLoadingProgress { CompletionPercentage = 1, State = GameLoadingState.LoadingPackages, Alias = gameAlias }); // Load packages packageGroup = await PackageGroup.LoadAsync(gameAlias, packageSetHash, gamePath, ct, progress); } progress?.Report(new GameLoadingProgress { CompletionPercentage = 1, State = GameLoadingState.Loading, Alias = gameAlias }); return(packageGroup, nodeTree); }