Пример #1
0
 void ConfigureVersionButton(Button versionButton, PackageGroup selection)
 {
     versionButton.clickable.clickedWithEventInfo -= PickVersion;
     versionButton.clickable.clickedWithEventInfo += PickVersion;
     versionButton.userData = selection;
     versionButton.text     = targetVersion;
     versionButton.SetEnabled(!selection.Installed);
 }
Пример #2
0
        public override ulong SolvePart1()
        {
            int targetWeight = packageWeights.Sum() / 3;

            GetMinMaxGroupSize(targetWeight, out int minGroupSize, out int maxGroupSize);

            // Start finding the groups
            int group1Size = minGroupSize;

            PackageGroup bestGroup   = null;
            ulong        bestGroupQE = ulong.MaxValue;

            var packageWeightsSet = new HashSet <int>(packageWeights);
            var groupDictionary   = new FlexibleDictionary <int, HashSet <int>[]>();

            while (true)
            {
                foreach (var group1 in GetGroups(group1Size))
                {
                    var remainingWeights = new HashSet <int>(packageWeightsSet);
                    remainingWeights.ExceptWith(group1);
                    int maxGroup2Size = maxGroupSize - (packageWeightsSet.Count - remainingWeights.Count);

                    for (int group2Size = minGroupSize; group2Size < maxGroup2Size; group2Size++)
                    {
                        if (GetGroups(group2Size).Any(group => !remainingWeights.Overlaps(group)))
                        {
                            EvaluateBestGroup(new(group1), ref bestGroup, ref bestGroupQE);
                            break;
                        }
                    }
                }

                if (bestGroup is not null)
                {
                    return(bestGroupQE);
                }

                group1Size++;
            }

            IEnumerable <HashSet <int> > GetGroups(int size)
            {
                if (!groupDictionary.ContainsKey(size))
                {
                    groupDictionary[size] = FindGroups(size, targetWeight, packageWeights);
                }
                return(groupDictionary[size]);
            }
        }
Пример #3
0
        private void AddGamePage(string alias, WarcraftVersion version, PackageGroup group, SerializedTree nodeTree)
        {
            var page = new GamePage(group, nodeTree, version, alias);

            page.FileLoadRequested          += OnFileLoadRequested;
            page.SaveRequested              += OnSaveRequested;
            page.ExportItemRequested        += OnExportItemRequested;
            page.EnqueueFileExportRequested += OnEnqueueItemRequested;

            _gamePages.Add(page);
            _gameTabNotebook.AppendPage(page.PageWidget, new Label(page.Alias));
            _gameTabNotebook.SetTabReorderable(page.PageWidget, true);

            _gameTabNotebook.ShowAll();
        }
Пример #4
0
        private void AddGamePage(string alias, WarcraftVersion version, PackageGroup group, OptimizedNodeTree nodeTree)
        {
            GamePage page = new GamePage(group, nodeTree, version);

            page.Alias = alias;

            page.FileLoadRequested          += OnFileLoadRequested;
            page.ExportItemRequested        += OnExportItemRequested;
            page.EnqueueFileExportRequested += OnEnqueueItemRequested;

            this.GamePages.Add(page);
            this.GameTabNotebook.AppendPage(page.PageWidget, new Label(page.Alias));
            this.GameTabNotebook.SetTabReorderable(page.PageWidget, true);

            this.GameTabNotebook.ShowAll();
        }
Пример #5
0
        /// <summary>
        /// Gets a <see cref="FileReference"/> from a given iter in the tree.
        /// </summary>
        /// <param name="packageGroup">The package group to create a reference for.</param>
        /// <param name="iter">The iter in the tree.</param>
        /// <returns>The FileReference pointed to by the given iter.</returns>
        /// <exception cref="InvalidDataException">Thrown if the iter doesn't belong to the model.</exception>
        public FileReference GetReferenceByIter(PackageGroup packageGroup, TreeIter iter)
        {
            if (iter.Stamp != this.Stamp)
            {
                throw new InvalidDataException("The given iter was not valid for this model.");
            }

            FileNode node = this.Tree.GetNode((ulong)iter.UserData);

            if (node == null)
            {
                return(null);
            }

            return(new FileReference(packageGroup, node, GetNodePackage(node), GetNodeFilePath(node)));
        }
Пример #6
0
        public PackageVersionGenerator(
            AbsolutePath tracerDirectory,
            AbsolutePath testProjectDirectory)
        {
            var propsDirectory = tracerDirectory / "build";

            _definitionsFilePath = tracerDirectory / "build" / "PackageVersionsGeneratorDefinitions.json";
            _latestMinors        = new PackageGroup(propsDirectory, testProjectDirectory, "LatestMinors");
            _latestMajors        = new PackageGroup(propsDirectory, testProjectDirectory, "LatestMajors");
            _strategyGenerator   = new XunitStrategyFileGenerator(testProjectDirectory / "PackageVersions.g.cs");

            if (!File.Exists(_definitionsFilePath))
            {
                throw new Exception($"Definitions file {_definitionsFilePath} does not exist. Exiting.");
            }
        }
Пример #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="RenderableWorldModel"/> class.
        /// </summary>
        public RenderableWorldModel(WMO inModel, PackageGroup inPackageGroup)
        {
            this.Model             = inModel;
            this.ModelPackageGroup = inPackageGroup;

            this.ActorTransform = new Transform
                                  (
                new Vector3(0.0f, 0.0f, 0.0f),
                Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.Pi),
                new Vector3(1.0f, 1.0f, 1.0f)
                                  );

            this.IsInitialized = false;

            Initialize();
        }
Пример #8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="RenderableWorldModel"/> class.
        /// </summary>
        /// <param name="inModel">The model to render.</param>
        /// <param name="inPackageGroup">The package group the model belongs to.</param>
        /// <param name="inVersion">The game version of the package group.</param>
        public RenderableWorldModel(WMO inModel, PackageGroup inPackageGroup, WarcraftVersion inVersion)
        {
            this.Model             = inModel;
            this.ModelPackageGroup = inPackageGroup;

            this.DatabaseProvider = new ClientDatabaseProvider(inVersion, this.ModelPackageGroup);

            this.ActorTransform = new Transform
                                  (
                new Vector3(0.0f, 0.0f, 0.0f),
                Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.Pi),
                new Vector3(1.0f, 1.0f, 1.0f)
                                  );

            this.IsInitialized = false;

            Initialize();
        }
Пример #9
0
        private void BindPackageView(PackageGroup selection)
        {
            if (selection.Installed)
            {
                targetVersion = PackageHelper.GetPackageManagerManifest(selection.PackageDirectory).version;
            }
            else
            {
                targetVersion = selection["latest"].version;
            }

            ConfigureVersionButton(packageView.Q <Button>("tkpm-package-version-button"), selection);
            ConfigureInstallButton(packageView.Q <Button>("tkpm-package-install-button"), selection);

            RepopulateLabels(packageView.Q("tkpm-package-tags"), selection.Tags, "tag");

            var selectedVersion = selection[targetVersion];
            var pvDependencies  = selectedVersion.dependencies ?? Array.Empty <PackageVersion>();
            var dependencyIds   = new List <string>();

            foreach (var pvd in pvDependencies)
            {
                dependencyIds.Add(pvd.dependencyId);
            }
            var texts = dependencyIds ?? Enumerable.Empty <string>();

            RepopulateLabels(packageView.Q("tkpm-package-dependencies"), texts, "dependency");

            SetLabel(packageView, "tkpm-package-title", NicifyPackageName(selection.PackageName));
            SetLabel(packageView, "tkpm-package-name", selection.DependencyId);
            if (selection.Installed)
            {
                SetLabel(packageView, "tkpm-package-info-version-value", selection.InstalledVersion);
            }
            else
            {
                SetLabel(packageView, "tkpm-package-info-version-value", selection["latest"].version);
            }

            SetLabel(packageView, "tkpm-package-author-value", selection.Author);
            SetLabel(packageView, "tkpm-package-description", selection.Description);
        }
Пример #10
0
        /// <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);
        }
Пример #11
0
        /// <summary>
        /// Loads all packages in the currently selected game directory. This function does not enumerate files
        /// and directories deeper than one to keep the UI responsive.
        /// </summary>
        private void Reload_Implementation()
        {
            if (!HasPackageDirectoryChanged())
            {
                return;
            }

            this.CachedPackageDirectories = GamePathStorage.Instance.GamePaths;
            this.PackageGroups.Clear();

            if (this.CachedPackageDirectories.Count > 0)
            {
                this.WorkQueue.Clear();
                this.NodeStorage.Clear();
            }

            foreach (string packageDirectory in this.CachedPackageDirectories)
            {
                if (!Directory.Exists(packageDirectory))
                {
                    continue;
                }

                // Create the package group and add it to the available ones
                string       folderName   = Path.GetFileName(packageDirectory);
                PackageGroup packageGroup = new PackageGroup(folderName, packageDirectory);
                // TODO: Creating a package group is real slow. Speed it up

                this.PackageGroups.Add(folderName, packageGroup);

                // Create a virtual item reference that points to the package group
                VirtualFileReference packageGroupReference = new VirtualFileReference(packageGroup,
                                                                                      new FileReference(packageGroup))
                {
                    State = ReferenceState.Enumerating
                };

                // Create a virtual package folder for the individual packages under the package group
                FileReference packageGroupPackagesFolderReference = new FileReference(packageGroup, packageGroupReference, "");

                // Add the package folder as a child to the package group node
                packageGroupReference.ChildReferences.Add(packageGroupPackagesFolderReference);

                // Send the package group node to the UI
                this.PackageGroupAddedArgs = new ReferenceEnumeratedEventArgs(packageGroupReference);
                RaisePackageGroupAdded();

                // Add the packages in the package group as nodes to the package folder
                foreach (KeyValuePair <string, List <string> > packageListFile in packageGroup.PackageListfiles)
                {
                    if (packageListFile.Value == null)
                    {
                        continue;
                    }

                    string        packageName      = Path.GetFileName(packageListFile.Key);
                    FileReference packageReference = new FileReference(packageGroup, packageGroupPackagesFolderReference,
                                                                       packageName, "");

                    // Send the package node to the UI
                    this.PackageEnumeratedArgs = new ReferenceEnumeratedEventArgs(packageReference);
                    RaisePackageEnumerated();

                    // Submit the package as a work order, enumerating the topmost directories
                    SubmitWork(packageReference);
                }
            }

            this.IsReloading            = false;
            this.ArePackageGroupsLoaded = true;
        }
Пример #12
0
 /// <summary>
 /// Initializes a new instance of the <see cref="FileReference"/> class.
 /// </summary>
 /// <param name="inPackageGroup">PackageGroup.</param>
 public FileReference(PackageGroup inPackageGroup)
 {
     this.PackageGroup = inPackageGroup;
 }
Пример #13
0
 /// <summary>
 /// Initializes a new instance of the <see cref="FileReference"/> class.
 /// </summary>
 /// <param name="packageGroup">PackageGroup.</param>
 public FileReference(PackageGroup packageGroup)
 {
     this.PackageGroup = packageGroup;
 }
Пример #14
0
 /// <summary>
 /// Initializes a new instance of the <see cref="Everlook.Explorer.VirtualItemReference"/> class,
 /// where the reference has a parent <see cref="Everlook.Explorer.VirtualItemReference"/>.
 /// </summary>
 /// <param name="parentVirtualReference">Parent virtual reference.</param>
 /// <param name="inPackageGroup">In group.</param>
 /// <param name="inHardReference">In hard reference.</param>
 public VirtualItemReference(VirtualItemReference parentVirtualReference, PackageGroup inPackageGroup, ItemReference inHardReference)
     : this(inPackageGroup, inHardReference)
 {
     this.ParentReference = parentVirtualReference;
 }
Пример #15
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GamePage"/> class. The given package group and node tree are
        /// wrapped by the page.
        /// </summary>
        /// <param name="packageGroup">The package group which the node tree maps to.</param>
        /// <param name="nodeTree">The prebuilt node tree to display.</param>
        /// <param name="version">The Warcraft version that the game page is contextually relevant for.</param>
        public GamePage(PackageGroup packageGroup, OptimizedNodeTree nodeTree, WarcraftVersion version)
        {
            this.Packages         = packageGroup;
            this.Version          = version;
            this.DatabaseProvider = new ClientDatabaseProvider(this.Version, this.Packages);

            this.TreeModel = new FileTreeModel(nodeTree);

            this.TreeAlignment = new Alignment(0.5f, 0.5f, 1.0f, 1.0f)
            {
                TopPadding    = 1,
                BottomPadding = 1
            };

            this.TreeFilter = new TreeModelFilter(new TreeModelAdapter(this.TreeModel), null)
            {
                VisibleFunc = TreeModelVisibilityFunc
            };

            this.TreeSorter = new TreeModelSort(this.TreeFilter);

            this.TreeSorter.SetSortFunc(0, SortGameTreeRow);
            this.TreeSorter.SetSortColumnId(0, SortType.Descending);

            this.Tree = new TreeView(this.TreeSorter)
            {
                HeadersVisible  = true,
                EnableTreeLines = true
            };

            CellRendererPixbuf nodeIconRenderer = new CellRendererPixbuf
            {
                Xalign = 0.0f
            };
            CellRendererText nodeNameRenderer = new CellRendererText
            {
                Xalign = 0.0f
            };

            TreeViewColumn column = new TreeViewColumn
            {
                Title   = "Data Files",
                Spacing = 4
            };

            column.PackStart(nodeIconRenderer, false);
            column.PackStart(nodeNameRenderer, false);

            column.SetCellDataFunc(nodeIconRenderer, RenderNodeIcon);
            column.SetCellDataFunc(nodeNameRenderer, RenderNodeName);

            this.Tree.AppendColumn(column);

            ScrolledWindow sw = new ScrolledWindow
            {
                this.Tree
            };

            this.TreeAlignment.Add(sw);

            this.Tree.RowActivated      += OnRowActivated;
            this.Tree.ButtonPressEvent  += OnButtonPressed;
            this.Tree.Selection.Changed += OnSelectionChanged;

            this.TreeContextMenu = new Menu();

            // Save item context button
            this.SaveItem = new ImageMenuItem
            {
                UseStock     = true,
                Label        = Stock.Save,
                CanFocus     = false,
                TooltipText  = "Save the currently selected item to disk.",
                UseUnderline = true
            };
            this.SaveItem.Activated += OnSaveItem;
            this.TreeContextMenu.Add(this.SaveItem);

            // Export item context button
            this.ExportItem = new ImageMenuItem("Export")
            {
                Image       = new Image(Stock.Convert, IconSize.Button),
                CanFocus    = false,
                TooltipText = "Exports the currently selected item to another format.",
            };
            this.ExportItem.Activated += OnExportItemRequested;
            this.TreeContextMenu.Add(this.ExportItem);

            // Open item context button
            this.OpenItem = new ImageMenuItem
            {
                UseStock     = true,
                Label        = Stock.Open,
                CanFocus     = false,
                TooltipText  = "Open the currently selected item.",
                UseUnderline = true
            };
            this.OpenItem.Activated += OnOpenItem;
            this.TreeContextMenu.Add(this.OpenItem);

            // Queue for export context button
            this.QueueForExportItem = new ImageMenuItem("Queue for export")
            {
                Image       = new Image(Stock.Convert, IconSize.Button),
                CanFocus    = false,
                TooltipText = "Queues the currently selected item for batch export.",
            };
            this.QueueForExportItem.Activated += OnQueueForExportRequested;
            this.TreeContextMenu.Add(this.QueueForExportItem);

            // Separator
            SeparatorMenuItem separator = new SeparatorMenuItem();

            this.TreeContextMenu.Add(separator);

            // Copy path context button
            this.CopyPathItem = new ImageMenuItem("Copy path")
            {
                Image       = new Image(Stock.Copy, IconSize.Button),
                CanFocus    = false,
                TooltipText = "Copy the path of the currently selected item.",
            };
            this.CopyPathItem.Activated += OnCopyPath;
            this.TreeContextMenu.Add(this.CopyPathItem);

            this.TreeAlignment.ShowAll();
        }
Пример #16
0
 /// <summary>
 /// Initializes a new instance of the <see cref="Everlook.Explorer.ItemReference"/> class.
 /// </summary>
 /// <param name="inPackageGroup">PackageGroup.</param>
 public ItemReference(PackageGroup inPackageGroup)
 {
     this.PackageGroup = inPackageGroup;
 }
Пример #17
0
 public void Add(PackageGroup aPackageGroup)
 {
     List.Add(aPackageGroup);
 }
 public SetPackageGroupingCommand(PackageGroup groupMode)
 {
     GroupMode = groupMode;
 }
Пример #19
0
        /// <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);
        }