예제 #1
0
        private void DownloadArchives()
        {
            var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList();

            Info("Missing {0} archives", missing.Count);

            Info("Getting Nexus API Key, if a browser appears, please accept");
            NexusAPIKey = NexusAPI.GetNexusAPIKey();

            DownloadMissingArchives(missing);
        }
예제 #2
0
        public bool DownloadArchive(Archive archive, bool download)
        {
            try
            {
                switch (archive)
                {
                case NexusMod a:
                    string url;
                    try
                    {
                        url = NexusAPI.GetNexusDownloadLink(a as NexusMod, NexusAPIKey, !download);
                        if (!download)
                        {
                            return(true);
                        }
                    }
                    catch (Exception ex)
                    {
                        Info($"{a.Name} - Error Getting Nexus Download URL - {ex.Message}");
                        return(false);
                    }
                    Info($"Downloading Nexus Archive - {archive.Name} - {a.GameName} - {a.ModID} - {a.FileID}");
                    DownloadURLDirect(archive, url);
                    return(true);

                case MEGAArchive a:
                    return(DownloadMegaArchive(a, download));

                case GoogleDriveMod a:
                    return(DownloadGoogleDriveArchive(a, download));

                case MODDBArchive a:
                    return(DownloadModDBArchive(archive, (archive as MODDBArchive).URL, download));

                case MediaFireArchive a:
                    return(false);

                //return DownloadMediaFireArchive(archive, a.URL, download);
                case DirectURLArchive a:
                    return(DownloadURLDirect(archive, a.URL, headers: a.Headers, download: download));
                }
            }
            catch (Exception ex)
            {
                Utils.Log($"Download error for file {archive.Name}");
                Utils.Log(ex.ToString());
                return(false);
            }
            return(false);
        }
예제 #3
0
        private void DownloadMissingArchives(List <Archive> missing)
        {
            missing.PMap(archive =>
            {
                switch (archive)
                {
                case NexusMod a:
                    Info($"Downloading Nexus Archive - {archive.Name} - {a.GameName} - {a.ModID} - {a.FileID}");
                    string url;
                    try
                    {
                        url = NexusAPI.GetNexusDownloadLink(a as NexusMod, NexusAPIKey);
                    }
                    catch (Exception ex)
                    {
                        Info($"{a.Name} - Error Getting Nexus Download URL - {ex.Message}");
                        return;
                    }
                    DownloadURLDirect(archive, url);
                    break;

                case GoogleDriveMod a:
                    DownloadGoogleDriveArchive(a);
                    break;

                case MODDBArchive a:
                    DownloadModDBArchive(archive, (archive as MODDBArchive).URL);
                    break;

                case MediaFireArchive a:
                    DownloadMediaFireArchive(archive, a.URL);
                    break;

                case DirectURLArchive a:
                    DownloadURLDirect(archive, a.URL, headers: a.Headers);
                    break;

                default:
                    break;
                }
            });
        }
예제 #4
0
        private void DownloadArchives()
        {
            var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList();

            Info("Missing {0} archives", missing.Count);

            Info("Getting Nexus API Key, if a browser appears, please accept");
            NexusAPIKey = NexusAPI.GetNexusAPIKey();

            var user_status = NexusAPI.GetUserStatus(NexusAPIKey);

            if (!user_status.is_premium)
            {
                Info($"Automated installs with Wabbajack requires a premium nexus account. {user_status.name} is not a premium account");
                return;
            }

            DownloadMissingArchives(missing);
            return;
        }
예제 #5
0
        private void AskToEndorse()
        {
            var mods = ModList.Archives
                       .OfType <NexusMod>()
                       .GroupBy(f => (f.GameName, f.ModID))
                       .Select(mod => mod.First())
                       .ToArray();

            var result = MessageBox.Show(
                $"Installation has completed, but you have installed {mods.Length} from the Nexus, would you like to" +
                " endorse these mods to show support to the authors? It will only take a few moments.", "Endorse Mods?",
                MessageBoxButton.YesNo, MessageBoxImage.Question);

            if (result != MessageBoxResult.Yes)
            {
                return;
            }

            // Shuffle mods so that if we hit a API limit we don't always miss the same mods
            var r = new Random();

            for (var i = 0; i < mods.Length; i++)
            {
                var a   = r.Next(mods.Length);
                var b   = r.Next(mods.Length);
                var tmp = mods[a];
                mods[a] = mods[b];
                mods[b] = tmp;
            }

            mods.PMap(mod =>
            {
                var er = NexusAPI.EndorseMod(mod, NexusAPIKey);
                Utils.Log($"Endorsed {mod.GameName} - {mod.ModID} - Result: {er.message}");
            });
            Info("Done! You may now exit the application!");
        }
예제 #6
0
        public void Build(ModList lst)
        {
            Text($"### {lst.Name} - Installation Summary");
            Text(
                $"#### Download Summary ({lst.Archives.Count} archives - {lst.Archives.Sum(a => a.Size).ToFileSizeString()})");
            foreach (var archive in SortArchives(lst.Archives))
            {
                var hash = archive.Hash.FromBase64().ToHEX();
                switch (archive)
                {
                case NexusMod m:
                    var profile = m.UploaderProfile.Replace("/games/",
                                                            "/" + NexusAPI.ConvertGameName(m.GameName).ToLower() + "/");
                    NoWrapText(
                        $"* [{m.Name}](http://nexusmods.com/{NexusAPI.ConvertGameName(m.GameName)}/mods/{m.ModID})");
                    NoWrapText($"    * Author : [{m.UploadedBy}]({profile})");
                    NoWrapText($"    * Version : {m.Version}");
                    break;

                case MODDBArchive m:
                    NoWrapText($"* MODDB - [{m.Name}]({m.URL})");
                    break;

                case MEGAArchive m:
                    NoWrapText($"* MEGA - [{m.Name}]({m.URL})");
                    break;

                case GoogleDriveMod m:
                    NoWrapText(
                        $"* GoogleDrive - [{m.Name}](https://drive.google.com/uc?id={m.Id}&export=download)");
                    break;

                case DirectURLArchive m:
                    NoWrapText($"* URL - [{m.Name} - {m.URL}]({m.URL})");
                    break;
                }

                NoWrapText($"    * Size : {archive.Size.ToFileSizeString()}");
                NoWrapText($"    * SHA256 : [{hash}](https://www.virustotal.com/gui/file/{hash})");
            }

            Text("\n\n");
            var patched = lst.Directives.OfType <PatchedFromArchive>().OrderBy(p => p.To).ToList();

            Text($"#### Summary of ({patched.Count}) patches");
            foreach (var directive in patched)
            {
                NoWrapText(
                    $"* Applying {directive.Patch.Length} byte patch `{directive.FullPath}` to create `{directive.To}`");
            }


            var files = lst.Directives.OrderBy(d => d.To).ToList();

            Text($"\n\n### Install Plan of ({files.Count}) files");
            Text("(ignoring files that are directly copied from archives or listed in the patches section above)");
            foreach (var directive in files.OrderBy(f => f.GetType().Name).ThenByDescending(f => f.To))
            {
                switch (directive)
                {
                case FromArchive f:
                    //NoWrapText($"* `{f.To}` from `{f.FullPath}`");
                    break;

                case CleanedESM i:
                    NoWrapText($"* `{i.To}` by applying a patch to a game ESM ({i.SourceESMHash})");
                    break;

                case RemappedInlineFile i:
                    NoWrapText($"* `{i.To}` by remapping the contents of an inline file");
                    break;

                case InlineFile i:
                    NoWrapText($"* `{i.To}` from `{i.SourceData.Length.ToFileSizeString()}` file included in modlist");
                    break;

                case CreateBSA i:
                    NoWrapText(
                        $"* `{i.To}` by creating a BSA of files found in `{Consts.BSACreationDir}\\{i.TempID}`");
                    break;
                }
            }

            var inlined = lst.Directives.OfType <InlineFile>()
                          .Select(f => (f.To, "inlined", f.SourceData.Length))
                          .Concat(lst.Directives
                                  .OfType <PatchedFromArchive>()
                                  .Select(f => (f.To, "patched", f.Patch.Length)))
                          .ToHashSet()
                          .OrderByDescending(f => f.Length);

            NoWrapText("\n\n### Summary of inlined files in this installer");
            foreach (var inline in inlined)
            {
                NoWrapText($"* {inline.Length.ToFileSizeString()} for {inline.Item2} file {inline.To}");
            }
        }
예제 #7
0
        public void Build(ModList lst)
        {
            Text($"### {lst.Name} - Installation Summary");
            Text($"#### Download Summary ({lst.Archives.Count} archives)");
            foreach (var archive in SortArchives(lst.Archives))
            {
                switch (archive)
                {
                case NexusMod m:
                    var profile = m.UploaderProfile.Replace("/games/", "/" + NexusAPI.ConvertGameName(m.GameName).ToLower() + "/");
                    NoWrapText($"* [{m.UploadedBy}]({profile}) - [{m.Name}](http://nexusmods.com/{NexusAPI.ConvertGameName(m.GameName)}/mods/{m.ModID})");
                    break;

                case MODDBArchive m:
                    NoWrapText($"* MODDB - [{m.Name}]({m.URL})");
                    break;

                case MEGAArchive m:
                    NoWrapText($"* MEGA - [{m.Name}]({m.URL})");
                    break;

                case GoogleDriveMod m:
                    NoWrapText($"* GoogleDrive - [{m.Name}](https://drive.google.com/uc?id={m.Id}&export=download)");
                    break;

                case DirectURLArchive m:
                    NoWrapText($"* URL - [{m.Name} - {m.URL}]({m.URL})");
                    break;
                }
            }
            Text($"\n\n");
            var patched = lst.Directives.OfType <PatchedFromArchive>().OrderBy(p => p.To).ToList();

            Text($"#### Summary of ({patched.Count}) patches");
            foreach (var directive in patched)
            {
                NoWrapText($"* Applying {directive.Patch.Length} byte patch `{directive.FullPath}` to create `{directive.To}`");
            }


            var files = lst.Directives.OrderBy(d => d.To).ToList();

            Text($"\n\n### Install Plan of ({files.Count}) files");
            Text($"(ignoring files that are directly copied from archives or listed in the patches section above)");
            foreach (var directive in files)
            {
                switch (directive)
                {
                case FromArchive f:
                    //NoWrapText($"* `{f.To}` from `{f.FullPath}`");
                    break;

                case CleanedESM i:
                    NoWrapText($"* `{i.To}` by applying a patch to a game ESM ({i.SourceESMHash})");
                    break;

                case RemappedInlineFile i:
                    NoWrapText($"* `{i.To}` by remapping the contents of a inline file");
                    break;

                case InlineFile i:
                    NoWrapText($"* `{i.To}` from `{i.SourceData.Length}` byte file included in modlist");
                    break;

                case CreateBSA i:
                    NoWrapText($"* `{i.To}` by creating a BSA of files found in `{Consts.BSACreationDir}\\{i.TempID}`");
                    break;
                }
            }
        }
예제 #8
0
        private Archive ResolveArchive(string sha, IDictionary <string, IndexedArchive> archives)
        {
            if (archives.TryGetValue(sha, out var found))
            {
                if (found.IniData == null)
                {
                    Error("No download metadata found for {0}, please use MO2 to query info or add a .meta file and try again.", found.Name);
                }
                var general = found.IniData.General;
                if (general == null)
                {
                    Error("No General section in mod metadata found for {0}, please use MO2 to query info or add the info and try again.", found.Name);
                }

                Archive result;

                if (general.directURL != null && general.directURL.StartsWith("https://drive.google.com"))
                {
                    var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*");
                    var match = regex.Match(general.directURL);
                    result = new GoogleDriveMod()
                    {
                        Id = match.ToString()
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith(Consts.MegaPrefix))
                {
                    result = new MEGAArchive()
                    {
                        URL = general.directURL
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith("https://www.dropbox.com/"))
                {
                    var uri   = new UriBuilder((string)general.directURL);
                    var query = HttpUtility.ParseQueryString(uri.Query);

                    if (query.GetValues("dl").Count() > 0)
                    {
                        query.Remove("dl");
                    }

                    query.Set("dl", "1");

                    uri.Query = query.ToString();

                    result = new DirectURLArchive()
                    {
                        URL = uri.ToString()
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith("https://www.moddb.com/downloads/start"))
                {
                    result = new MODDBArchive()
                    {
                        URL = general.directURL
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith("http://www.mediafire.com/file/"))
                {
                    Error("Mediafire links are not currently supported");
                    return(null);

                    /*result = new MediaFireArchive()
                     * {
                     *  URL = general.directURL
                     * };*/
                }
                else if (general.directURL != null)
                {
                    var tmp = new DirectURLArchive()
                    {
                        URL = general.directURL
                    };
                    if (general.directURLHeaders != null)
                    {
                        tmp.Headers = new List <string>();
                        tmp.Headers.AddRange(general.directURLHeaders.Split('|'));
                    }
                    result = tmp;
                }
                else if (general.manualURL != null)
                {
                    result = new ManualURLArchive()
                    {
                        URL = general.manualURL.ToString()
                    };
                }
                else if (general.modID != null && general.fileID != null && general.gameName != null)
                {
                    var nm = new NexusMod()
                    {
                        GameName = general.gameName,
                        FileID   = general.fileID,
                        ModID    = general.modID,
                        Version  = general.version ?? "0.0.0.0"
                    };
                    var info = NexusAPI.GetModInfo(nm, NexusKey);
                    nm.Author          = info.author;
                    nm.UploadedBy      = info.uploaded_by;
                    nm.UploaderProfile = info.uploaded_users_profile_url;
                    result             = nm;
                }
                else
                {
                    Error("No way to handle archive {0} but it's required by the modpack", found.Name);
                    return(null);
                }

                result.Name = found.Name;
                result.Hash = found.File.Hash;
                result.Meta = found.Meta;

                Info($"Checking link for {found.Name}");

                var installer = new Installer(null, "", s => Utils.Log(s));
                installer.NexusAPIKey = NexusKey;
                if (!installer.DownloadArchive(result, false))
                {
                    Error($"Unable to resolve link for {found.Name}. If this is hosted on the nexus the file may have been removed.");
                }

                return(result);
            }
            Error("No match found for Archive sha: {0} this shouldn't happen", sha);
            return(null);
        }
예제 #9
0
        public void Compile()
        {
            Info($"Indexing {MO2Folder}");
            VFS.AddRoot(MO2Folder);
            Info($"Indexing {GamePath}");
            VFS.AddRoot(GamePath);

            var mo2_files = Directory.EnumerateFiles(MO2Folder, "*", SearchOption.AllDirectories)
                            .Where(p => p.FileExists())
                            .Select(p => new RawSourceFile(VFS.Lookup(p))
            {
                Path = p.RelativeTo(MO2Folder)
            });

            var game_files = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
                             .Where(p => p.FileExists())
                             .Select(p => new RawSourceFile(VFS.Lookup(p))
            {
                Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath))
            });

            var loot_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LOOT");

            Info($"Indexing {loot_path}");
            VFS.AddRoot(loot_path);

            var loot_files = Directory.EnumerateFiles(loot_path, "userlist.yaml", SearchOption.AllDirectories)
                             .Where(p => p.FileExists())
                             .Select(p => new RawSourceFile(VFS.Lookup(p))
            {
                Path = Path.Combine(Consts.LOOTFolderFilesDir, p.RelativeTo(loot_path))
            });


            Info($"Indexing Archives");
            IndexedArchives = Directory.EnumerateFiles(MO2DownloadsFolder)
                              .Where(f => Consts.SupportedArchives.Contains(Path.GetExtension(f)))
                              .Where(f => File.Exists(f + ".meta"))
                              .Select(f => new IndexedArchive()
            {
                File    = VFS.Lookup(f),
                Name    = Path.GetFileName(f),
                IniData = (f + ".meta").LoadIniFile(),
                Meta    = File.ReadAllText(f + ".meta")
            })
                              .ToList();

            Info($"Indexing Files");
            IndexedFiles = IndexedArchives.PMap(f => { Status($"Finding files in {Path.GetFileName(f.File.FullPath)}");
                                                       return(VFS.FilesInArchive(f.File)); })
                           .SelectMany(fs => fs)
                           .OrderByDescending(f => f.TopLevelArchive.LastModified)
                           .GroupBy(f => f.Hash)
                           .ToDictionary(f => f.Key, f => f.AsEnumerable());

            Info("Searching for mod files");

            AllFiles = mo2_files.Concat(game_files)
                       .Concat(loot_files)
                       .ToList();

            Info("Found {0} files to build into mod list", AllFiles.Count);

            ExtraFiles = new ConcurrentBag <Directive>();

            ModInis = Directory.EnumerateDirectories(Path.Combine(MO2Folder, "mods"))
                      .Select(f =>
            {
                var mod_name  = Path.GetFileName(f);
                var meta_path = Path.Combine(f, "meta.ini");
                if (File.Exists(meta_path))
                {
                    return(mod_name, meta_path.LoadIniFile());
                }
                return(null, null);
            })
                      .Where(f => f.Item2 != null)
                      .ToDictionary(f => f.Item1, f => f.Item2);

            var stack = MakeStack();


            Info("Running Compilation Stack");
            var results = AllFiles.PMap(f => RunStack(stack, f)).ToList();

            // Add the extra files that were generated by the stack
            Info($"Adding {ExtraFiles.Count} that were generated by the stack");
            results = results.Concat(ExtraFiles).ToList();

            var nomatch = results.OfType <NoMatch>();

            Info("No match for {0} files", nomatch.Count());
            foreach (var file in nomatch)
            {
                Info("     {0}", file.To);
            }
            if (nomatch.Count() > 0)
            {
                if (IgnoreMissingFiles)
                {
                    Info("Continuing even though files were missing at the request of the user.");
                }
                else
                {
                    Info("Exiting due to no way to compile these files");
                    return;
                }
            }

            InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList();

            Info("Getting nexus api_key please click authorize if a browser window appears");

            NexusKey = NexusAPI.GetNexusAPIKey();
            User     = NexusAPI.GetUserStatus(NexusKey);

            if (!User.is_premium)
            {
                Info($"User {User.name} is not a premium Nexus user, cannot continue");
            }


            GatherArchives();
            BuildPatches();

            ModList = new ModList()
            {
                Archives   = SelectedArchives,
                Directives = InstallDirectives,
                Name       = MO2Profile
            };

            GenerateReport();
            PatchExecutable();

            ResetMembers();

            ShowReport();

            Info("Done Building Modpack");
        }