Ejemplo n.º 1
0
        private static Mod AttemptLoadVirtualMod(ArchiveFileInfo sfarEntry, SevenZipExtractor archive, Mod.MEGame game, string md5)
        {
            var sfarPath   = sfarEntry.FileName;
            var cookedPath = FilesystemInterposer.DirectoryGetParent(sfarPath, true);

            //Todo: Check if value is CookedPC/CookedPCConsole as further validation
            if (!string.IsNullOrEmpty(FilesystemInterposer.DirectoryGetParent(cookedPath, true)))
            {
                var dlcDir        = FilesystemInterposer.DirectoryGetParent(cookedPath, true);
                var dlcFolderName = Path.GetFileName(dlcDir);
                if (!string.IsNullOrEmpty(dlcFolderName))
                {
                    var thirdPartyInfo = ThirdPartyServices.GetThirdPartyModInfo(dlcFolderName, game);
                    if (thirdPartyInfo != null)
                    {
                        Log.Information($@"Third party mod found: {thirdPartyInfo.modname}, preparing virtual moddesc.ini");
                        //We will have to load a virtual moddesc. Since Mod constructor requires reading an ini, we will build and feed it a virtual one.
                        IniData virtualModDesc = new IniData();
                        virtualModDesc[@"ModManager"][@"cmmver"]     = App.HighestSupportedModDesc.ToString();
                        virtualModDesc[@"ModManager"][@"importedby"] = App.BuildNumber.ToString();
                        virtualModDesc[@"ModInfo"][@"game"]          = @"ME3";
                        virtualModDesc[@"ModInfo"][@"modname"]       = thirdPartyInfo.modname;
                        virtualModDesc[@"ModInfo"][@"moddev"]        = thirdPartyInfo.moddev;
                        virtualModDesc[@"ModInfo"][@"modsite"]       = thirdPartyInfo.modsite;
                        virtualModDesc[@"ModInfo"][@"moddesc"]       = thirdPartyInfo.moddesc;
                        virtualModDesc[@"ModInfo"][@"unofficial"]    = @"true";
                        if (int.TryParse(thirdPartyInfo.updatecode, out var updatecode) && updatecode > 0)
                        {
                            virtualModDesc[@"ModInfo"][@"updatecode"] = updatecode.ToString();
                            virtualModDesc[@"ModInfo"][@"modver"]     = 0.001.ToString(CultureInfo.InvariantCulture); //This will force mod to check for update after reload
                        }
                        else
                        {
                            virtualModDesc[@"ModInfo"][@"modver"] = 0.0.ToString(CultureInfo.InvariantCulture); //Will attempt to look up later after mods have parsed.
                        }

                        virtualModDesc[@"CUSTOMDLC"][@"sourcedirs"]        = dlcFolderName;
                        virtualModDesc[@"CUSTOMDLC"][@"destdirs"]          = dlcFolderName;
                        virtualModDesc[@"UPDATES"][@"originalarchivehash"] = md5;

                        var archiveSize    = new FileInfo(archive.FileName).Length;
                        var importingInfos = ThirdPartyServices.GetImportingInfosBySize(archiveSize);
                        if (importingInfos.Count == 1 && importingInfos[0].GetParsedRequiredDLC().Count > 0)
                        {
                            OnlineContent.QueryModRelay(importingInfos[0].md5, archiveSize); //Tell telemetry relay we are accessing the TPIS for an existing item so it can update latest for tracking
                            virtualModDesc[@"ModInfo"][@"requireddlc"] = importingInfos[0].requireddlc;
                        }

                        return(new Mod(virtualModDesc.ToString(), FilesystemInterposer.DirectoryGetParent(dlcDir, true), archive));
                    }
                }
                else
                {
                    Log.Information($@"No third party mod information for importing {dlcFolderName}. Should this be supported for import? Contact Mgamerz on the ME3Tweaks Discord if it should.");
                }
            }

            return(null);
        }
Ejemplo n.º 2
0
        private static void DownloadDB(string nexusDBPath)
        {
            var downloadResult = OnlineContent.DownloadME3TweaksStaticAsset(@"nexusfiledb.zip");

            if (downloadResult.errorMessage == null)
            {
                downloadResult.download.WriteToFile(nexusDBPath);
            }
        }
        public void ValidateArchiveModLoading()
        {
            GlobalTest.Init();

            Console.WriteLine("Fetching third party services");
            App.ThirdPartyImportingService      = OnlineContent.FetchThirdPartyImportingService();
            App.ThirdPartyIdentificationService = OnlineContent.FetchThirdPartyIdentificationManifest();

            var        compressedModsDirectory = Path.Combine(GlobalTest.FindDirectoryInParentDirectories(GlobalTest.TESTDATA_FOLDER_NAME), "compressedmods");
            List <Mod> modsFoundInArchive      = new List <Mod>();

            void addModCallback(Mod m)
            {
                Console.WriteLine($"Found mod in archive: {m.ModName}");
                modsFoundInArchive.Add(m);
            }

            void failedModCallback(Mod m)
            {
                Console.WriteLine($"A mod failed to load. This may be expected: {m.ModName}");
            }

            void logMessageCallback(string m)
            {
                Console.WriteLine(m);
            }

            foreach (var archive in Directory.GetFiles(compressedModsDirectory))
            {
                modsFoundInArchive.Clear();
                var realArchiveInfo = GlobalTest.ParseRealArchiveAttributes(archive);
                Console.WriteLine($"Inspecting archive: { archive}");
                ModArchiveImporter.InspectArchive(archive, addModCallback, failedModCallback, logMessageCallback, forcedMD5: realArchiveInfo.md5, forcedSize: realArchiveInfo.size);
                Assert.AreEqual(realArchiveInfo.nummodsexpected, modsFoundInArchive.Count(x => x.ValidMod), $"{archive} did not parse correct amount of mods.");

                foreach (var v in modsFoundInArchive)
                {
                    var cookedName = v.Game == MEGame.ME3 ? @"CookedPCConsole" : "CookedPC";
                    // Check nothing has FilesToInstall containing two 'CookedPCConsole' items in the string.
                    // This is fun edge case due to TESTPATCH having two names DLC_TestPatch and TESTPATCH

                    foreach (var mj in v.InstallationJobs)
                    {
                        foreach (var fti in mj.FilesToInstall)
                        {
                            var numAppearances = Regex.Matches(fti.Key, cookedName).Count;
                            if (numAppearances > 1)
                            {
                                Assert.Fail($@"Found more than 1 instance of {cookedName} in FilesToInstall targetpath item {fti.Key}! This indicates queue building was wrong. Mod: {v.ModName}, file {archive}");
                            }
                        }
                    }
                }
            }
        }
Ejemplo n.º 4
0
        public static string SetupNXMHandling(Action <long, long, string> notifyProgress, Action <string> notifyFinished)
        {
            bool installNewCopy = true;
            var  value          = Registry.GetValue(@"HKEY_CURRENT_USER\SOFTWARE\Classes\nxm\shell\open\command", "", null); // Get's 'Default'

            if (value is string path)
            {
                path = path.Replace(" \"%1\"", "").Trim('\"');
                if (!string.IsNullOrWhiteSpace(path))
                {
                    string nxmIniPath = Path.Combine(Directory.GetParent(path).FullName, "nxmhandler.ini");
                    // are we already using nxmhandler?
                    if (Path.GetFileName(path).Equals("nxmhandler.exe", StringComparison.InvariantCultureIgnoreCase) && File.Exists(nxmIniPath))
                    {
                        // Setup for nxmhandler already, we just need to adjust it to add M3
                        SetupM3InNXMHandler(nxmIniPath);
                        installNewCopy = false;
                    }
                }
            }

            if (installNewCopy)
            {
                // It's not setup. We will set up a copy of it
                var outpath = Utilities.GetCachedExecutablePath("nxmhandler");

                void onDownloadProgress(long done, long total)
                {
                    notifyProgress?.Invoke(done, total, "Downloading nxmhandler");
                }

                var archiveResult = OnlineContent.DownloadStaticAsset("nxmhandler.7z", onDownloadProgress);
                if (archiveResult.errorMessage == null)
                {
                    notifyProgress?.Invoke(0, -1, "Extracting nxmhandler");
                    var nxma = new SevenZipExtractor(archiveResult.download);
                    nxma.ExtractArchive(outpath);

                    // Register it
                    using var subkey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Classes\nxm\shell\open\command");
                    subkey.SetValue("", $"\"{ Path.Combine(outpath, "nxmhandler.exe")}\" \"%1\"");

                    var protocolKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Classes\nxm", true);
                    protocolKey.SetValue("URL Protocol", "");
                    protocolKey.SetValue("", "URL:NXM Protocol");

                    SetupM3InNXMHandler(Path.Combine(outpath, "nxmhandler.ini"));
                }
            }

            notifyFinished?.Invoke(null);
            return(null);
        }
Ejemplo n.º 5
0
        public void StartDownload(CancellationToken cancellationToken)
        {
            Task.Run(() =>
            {
                if (ProgressMaximum < 100 * FileSize.MebiByte)
                {
                    DownloadedStream = new MemoryStream();
                    MemoryAnalyzer.AddTrackedMemoryItem(@"NXM Download MemoryStream", new WeakReference(DownloadedStream));
                }
                else
                {
                    DownloadedStream = new FileStream(Path.Combine(Utilities.GetModDownloadCacheDirectory(), ModFile.FileName), FileMode.Create);
                    MemoryAnalyzer.AddTrackedMemoryItem(@"NXM Download FileStream", new WeakReference(DownloadedStream));
                }

                var downloadUri = DownloadLinks[0].Uri;

                var downloadResult = OnlineContent.DownloadToStream(downloadUri.ToString(), OnDownloadProgress, null, true, DownloadedStream, cancellationToken);
                if (downloadResult.errorMessage != null)
                {
                    DownloadedStream?.Dispose();
                    if (cancellationToken.IsCancellationRequested)
                    {
                        // Aborted download.
                    }
                    else
                    {
                        Log.Error($@"Download failed: {downloadResult.errorMessage}");
                        OnModDownloadError?.Invoke(this, downloadResult.errorMessage);
                    }
                    // Download didn't work!
                    Analytics.TrackEvent(@"NXM Download", new Dictionary <string, string>()
                    {
                        { @"Domain", domain },
                        { @"File", ModFile?.Name },
                        { @"Result", $@"Failed, {downloadResult.errorMessage}" },
                    });
                }
                else
                {
                    Analytics.TrackEvent(@"NXM Download", new Dictionary <string, string>()
                    {
                        { @"Domain", domain },
                        { @"File", ModFile?.Name },
                        { @"Result", @"Success" },
                    });
                }
                Downloaded = true;
                OnModDownloaded?.Invoke(this, new DataEventArgs(DownloadedStream));
            });
        }
Ejemplo n.º 6
0
        private void GetTopMods()
        {
            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ModMaker-TopModsFetch");

            nbw.DoWork += (a, b) =>
            {
                b.Result = OnlineContent.FetchTopModMakerMods();
            };
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error == null && b.Result is List <OnlineContent.ServerModMakerModInfo> topMods)
                {
                    TopMods.ReplaceAll(topMods);
                }
            };
            nbw.RunWorkerAsync();
        }
Ejemplo n.º 7
0
        private static void internalLoadManifest(bool forceLocal = false, bool overrideThrottling = false)
        {
            if (File.Exists(ManifestLocation) && (forceLocal || (!OnlineContent.CanFetchContentThrottleCheck() && !overrideThrottling))) //Force local, or we can't online check and cannot override throttle
            {
                LoadManifestFromDisk(ManifestLocation);
                return;
            }

            var shouldNotFetch = forceLocal || (!overrideThrottling && !OnlineContent.CanFetchContentThrottleCheck()) && File.Exists(ManifestLocation);

            if (!shouldNotFetch) //this cannot be triggered if forceLocal is true
            {
                Log.Information(@"Fetching ASI manifest from online source");
                var onlineManifest = OnlineContent.FetchRemoteString(@"https://me3tweaks.com/mods/asi/getmanifest?AllGames=1");
                onlineManifest = onlineManifest.Trim();
                try
                {
                    File.WriteAllText(StagedManifestLocation, onlineManifest);
                }
                catch (Exception e)
                {
                    Log.Error(@"Error writing cached ASI manifest to disk: " + e.Message);
                }

                try
                {
                    ParseManifest(onlineManifest, true);
                }
                catch (Exception e)
                {
                    Log.Error(@"Error parsing online manifest: " + e.Message);
                    internalLoadManifest(true); //force local load instead
                }
            }
            else if (File.Exists(ManifestLocation))
            {
                Log.Information(@"Loading ASI local manifest");
                LoadManifestFromDisk(ManifestLocation, false);
            }
            else
            {
                //can't get manifest or local manifest.
                //Todo: some sort of handling here as we are running in panel startup
                Log.Error(@"Cannot load ASI manifest: Could not fetch online manifest and no local manifest exists");
            }
        }
Ejemplo n.º 8
0
        public void TestNexusDB()
        {
            // Fetch the latest DB
            var downloadResult = OnlineContent.DownloadME3TweaksStaticAsset(@"nexusfiledb.zip");

            if (downloadResult.errorMessage != null)
            {
                Assert.Fail($@"Nexus DB could not be downloaded: {downloadResult.errorMessage}");
            }

            // Test the DB
            var searchGames = new List <string>();

            searchGames.Add(@"masseffect");
            searchGames.Add(@"masseffect2");
            searchGames.Add(@"masseffect3");

            foreach (var domain in searchGames)
            {
                var db = GameDatabase.LoadDatabase(domain, downloadResult.download);

                foreach (var instanceId in db.FileInstances)
                {
                    foreach (var Instance in instanceId.Value)
                    {
                        string resolvedName;
                        if (Instance.ParentPathID == 0)
                        {
                            resolvedName = db.NameTable[instanceId.Key];
                        }
                        else
                        {
                            resolvedName = db.Paths[Instance.ParentPathID].GetFullPath(db, db.NameTable[instanceId.Key]);
                        }

                        // DB does not ship to ME3Tweaks with the debug entries. So we cannot debug test the results.
                        // We will just make sure the lookup code does not throw an exception.
                        //string expectedName = Instance.DebugFullName;
                        //Assert.IsTrue(resolvedName.Equals(expectedName, StringComparison.InvariantCultureIgnoreCase), $@"Wrong name resolved in NexusDB! Expected: {expectedName} Got: {resolvedName}");
                    }
                }
            }
        }
Ejemplo n.º 9
0
        private void DownloadAndApplyUpdate(object sender, DoWorkEventArgs e)
        {
            void pCallback(long done, long total)
            {
                ProgressValue = done;
                ProgressMax   = total;
                var hrDone  = ByteSize.FromBytes(done).ToString(@"0.00");
                var hrTotal = ByteSize.FromBytes(total).ToString(@"0.00");

                ProgressText = M3L.GetString(M3L.string_downloadingUpdate) + $@" {hrDone} / {hrTotal}";
            }

            var    downloadLinks = new string[] { PrimaryDownloadLink, BackupDownloadLink };
            string errorMessage  = null;

            foreach (var downloadLink in downloadLinks)
            {
                var updateFile = OnlineContent.DownloadToMemory(downloadLink, pCallback);
                ProgressText = M3L.GetString(M3L.string_preparingToApplyUpdate);
                if (updateFile.errorMessage == null)
                {
                    ProgressIndeterminate = true;
                    ApplyUpdateFromStream(updateFile.result);
                    return; //do not loop.
                }
                else
                {
                    Log.Error(@"Error downloading update: " + updateFile.errorMessage);
                    Analytics.TrackEvent(@"Error downloading update", new Dictionary <string, string>()
                    {
                        { @"Error message", updateFile.errorMessage }
                    });
                    errorMessage = updateFile.errorMessage;
                }
            }
            Application.Current.Dispatcher.Invoke(delegate
            {
                M3L.ShowDialog(Window.GetWindow(this), errorMessage, M3L.GetString(M3L.string_errorDownloadingUpdate), MessageBoxButton.OK, MessageBoxImage.Error);
                OnClosing(DataEventArgs.Empty);
            });
        }
Ejemplo n.º 10
0
        private static void internalLoadManifest(List <ASIGame> games, Action <object> selectionStateUpdateCallback = null)
        {
            using WebClient wc = new WebClient();
            var onlineManifest = OnlineContent.FetchRemoteString(@"https://me3tweaks.com/mods/asi/getmanifest?AllGames=1");

            if (onlineManifest != null)
            {
                File.WriteAllText(StagedManifestLocation, onlineManifest);
                ParseManifest(StagedManifestLocation, games, true, selectionStateUpdateCallback);
            }
            else if (File.Exists(ManifestLocation))
            {
                Log.Information(@"Loading ASI local manifest");
                ParseManifest(ManifestLocation, games, false, selectionStateUpdateCallback);
            }
            else
            {
                //can't get manifest or local manifest.
                //Todo: some sort of handling here as we are running in panel startup
            }
        }
        private void GetTopMods()
        {
            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ModMaker-TopModsFetch");

            nbw.DoWork += (a, b) =>
            {
                b.Result = OnlineContent.FetchTopModMakerMods();
            };
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occured in {nbw.Name} thread: {b.Error.Message}");
                }
                if (b.Error == null && b.Result is List <OnlineContent.ServerModMakerModInfo> topMods)
                {
                    TopMods.ReplaceAll(topMods);
                }
            };
            nbw.RunWorkerAsync();
        }
Ejemplo n.º 12
0
        private void UpdateClassicMod(OnlineContent.ModUpdateInfo ui)
        {
            NamedBackgroundWorker bw = new NamedBackgroundWorker(@"ModUpdaterThread-" + ui.mod.ModName);

            bw.DoWork += (a, b) =>
            {
                OperationInProgress   = true;
                ui.UpdateInProgress   = true;
                ui.Indeterminate      = false;
                ui.DownloadButtonText = M3L.GetString(M3L.string_downloading);
                //void updateProgressCallback(long bytesReceived, long totalBytes)
                //{
                //    ui.By
                //}
                bool errorShown = false;
                void errorCallback(string message)
                {
                    if (!errorShown)
                    {
                        errorShown = true;
                        Application.Current.Dispatcher.Invoke(delegate { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_errorOccuredWhileUpdatingXErrorMessage, ui.mod.ModName, message), M3L.GetString(M3L.string_interp_errorUpdatingX, ui.mod.ModName), MessageBoxButton.OK, MessageBoxImage.Error); }
                                                              );
                    }
                }

                var stagingDirectory = Directory.CreateDirectory(Path.Combine(Utilities.GetTempPath(), Path.GetFileName(ui.mod.ModPath))).FullName;
                var modUpdated       = OnlineContent.UpdateMod(ui, stagingDirectory, errorCallback);
                ui.UpdateInProgress   = false;
                ui.CanUpdate          = !modUpdated;
                AnyModUpdated        |= modUpdated;
                ui.DownloadButtonText = ui.CanUpdate ? M3L.GetString(M3L.string_downloadUpdate) : M3L.GetString(M3L.string_updated);
                Utilities.DeleteFilesAndFoldersRecursively(stagingDirectory);
            };
            bw.RunWorkerCompleted += (a, b) =>
            {
                OperationInProgress = false;
                CommandManager.InvalidateRequerySuggested();
            };
            bw.RunWorkerAsync();
        }
        private void DownloadAndApplyUpdate(object sender, DoWorkEventArgs e)
        {
            void pCallback(long done, long total)
            {
                ProgressValue = done;
                ProgressMax   = total;
                ProgressText  = $"Downloading update {ByteSize.FromBytes(done)} / {ByteSize.FromBytes(total)}";
            }

            //debug
            var downloadLinks = new string[] { PrimaryDownloadLink, BackupDownloadLink };
            // string l = "https://github.com/ME3Tweaks/ME3TweaksModManager/releases/download/0.0.0.1/UpdateTest2.7z";
            string errorMessage = null;

            foreach (var downloadLink in downloadLinks)
            {
                var updateFile = OnlineContent.DownloadToMemory(downloadLink, pCallback);
                ProgressText          = "Preparing to apply update";
                ProgressIndeterminate = true;
                if (updateFile.errorMessage == null)
                {
                    ApplyUpdateFromStream(updateFile.result);
                    return; //do not loop.
                }
                else
                {
                    Log.Error("Error downloading update: " + updateFile.errorMessage);
                    Analytics.TrackEvent("Error downloading update", new Dictionary <string, string>()
                    {
                        { "Error message", updateFile.errorMessage }
                    });
                    errorMessage = updateFile.errorMessage;
                }
            }
            Application.Current.Dispatcher.Invoke(delegate
            {
                Xceed.Wpf.Toolkit.MessageBox.Show(Window.GetWindow(this), errorMessage, "Error downloading update", MessageBoxButton.OK, MessageBoxImage.Error);
                OnClosing(DataEventArgs.Empty);
            });
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Gets the path to the GUI library specified by the DLC name. Returns null if the library is not found and could not be downloaded.
        /// If Download is specified this call should run on a background thread only.
        /// </summary>
        /// <param name="dlcname">DLC mod to lookup library for</param>
        /// <param name="download">Download library if missing. If this is false and library is missing the value returned will be null</param>
        /// <returns>Path to library, null if it does not exist.</returns>
        public static string GetUILibraryPath(string dlcname, bool download, Action <long, long> progressCallback = null)
        {
            string libraryFolder = Path.Combine(Utilities.GetAppDataFolder(), @"UIModLibrary");
            string libraryPath   = Path.Combine(libraryFolder, dlcname + @".zip");

            if (File.Exists(libraryPath))
            {
                return(libraryPath);
            }

            if (!Directory.Exists(libraryFolder) && !download)
            {
                return(null);
            }
            if (!File.Exists(libraryPath) && !download)
            {
                return(null);
            }

            if (download)
            {
                Directory.CreateDirectory(libraryFolder);
                Log.Information(@"Downloading UI library for " + dlcname);
                var downloaded = OnlineContent.DownloadToMemory(M3_UILIBRARY_ROOT + dlcname + @".zip", progressCallback);
                if (downloaded.errorMessage == null)
                {
                    File.WriteAllBytes(libraryPath, downloaded.result.ToArray());
                    Log.Information(@"Downloaded UI library for " + dlcname);
                    return(libraryPath);
                }
                else
                {
                    Log.Error(@"Error downloading UI library: " + downloaded.errorMessage);
                    return(null);
                }
            }

            return(null);
        }
Ejemplo n.º 15
0
        private static void internalLoadManifest(List <ASIGame> games, Action <object> selectionStateUpdateCallback = null, bool forceLocal = false)
        {
            using WebClient wc = new WebClient();
            var onlineManifest = forceLocal ? null : OnlineContent.FetchRemoteString(@"https://me3tweaks.com/mods/asi/getmanifest?AllGames=1");

            if (onlineManifest != null) //this cannot be triggered if forceLocal is true
            {
                try
                {
                    File.WriteAllText(StagedManifestLocation, onlineManifest);
                }
                catch (Exception e)
                {
                    Log.Error(@"Error writing cached ASI manifest to disk: " + e.Message);
                }

                try
                {
                    ParseManifest(onlineManifest, games, true, selectionStateUpdateCallback);
                }
                catch (Exception e)
                {
                    Log.Error(@"Error parsing online manifest: " + e.Message);
                    internalLoadManifest(games, selectionStateUpdateCallback, true); //force local load instead
                }
            }
            else if (File.Exists(ManifestLocation))
            {
                Log.Information(@"Loading ASI local manifest");
                LoadManifestFromDisk(ManifestLocation, games, false, selectionStateUpdateCallback);
            }
            else
            {
                //can't get manifest or local manifest.
                //Todo: some sort of handling here as we are running in panel startup
                Log.Error(@"Cannot load ASI manifest: Could not fetch online manifest and no local manifest exists");
            }
        }
Ejemplo n.º 16
0
        private void ApplyUpdateToMod(object obj)
        {
            if (obj is OnlineContent.ModUpdateInfo ui)
            {
                NamedBackgroundWorker bw = new NamedBackgroundWorker("ModUpdaterThread-" + ui.mod.ModName);
                bw.DoWork += (a, b) =>
                {
                    ui.UpdateInProgress   = true;
                    ui.Indeterminate      = false;
                    ui.DownloadButtonText = "Downloading";
                    //void updateProgressCallback(long bytesReceived, long totalBytes)
                    //{
                    //    ui.By
                    //}
                    bool errorShown = false;
                    void errorCallback(string message)
                    {
                        if (!errorShown)
                        {
                            errorShown = true;
                            Application.Current.Dispatcher.Invoke(delegate { Xceed.Wpf.Toolkit.MessageBox.Show($"Error occured while updating {ui.mod.ModName}:\n{message}", $"Error updating {ui.mod.ModName}", MessageBoxButton.OK, MessageBoxImage.Error); }
                                                                  );
                        }
                    }

                    var stagingDirectory = Directory.CreateDirectory(Path.Combine(Utilities.GetTempPath(), Path.GetFileName(ui.mod.ModPath))).FullName;
                    var modUpdated       = OnlineContent.UpdateMod(ui, stagingDirectory, errorCallback);
                    ui.UpdateInProgress   = false;
                    ui.CanUpdate          = !modUpdated;
                    AnyModUpdated        |= modUpdated;
                    ui.DownloadButtonText = ui.CanUpdate ? "Download update" : "Updated";
                    Utilities.DeleteFilesAndFoldersRecursively(stagingDirectory);
                };
                bw.RunWorkerCompleted += (a, b) => { CommandManager.InvalidateRequerySuggested(); };
                bw.RunWorkerAsync();
            }
        }
        public void ValidateOnlineFetches()
        {
            GlobalTest.Init();
            var helpItems = OnlineContent.FetchLatestHelp("int", false, true);

            Assert.AreNotEqual(0, helpItems.Count, "FetchLatestHelp failed: No items were parsed, the list is empty!");
            helpItems.Sort();

            var tips = OnlineContent.FetchTipsService(true);

            Assert.AreNotEqual(0, tips.Count, "FetchTipsService failed: No items were parsed, the list is empty!");

            var tpmiService = OnlineContent.FetchThirdPartyImportingService(true);

            Assert.AreNotEqual(0, tpmiService.Count, "FetchThirdPartyImportingService failed: No items were parsed, the list is empty!");

            var bgfis = OnlineContent.FetchBasegameFileIdentificationServiceManifest(true);

            Assert.AreNotEqual(0, bgfis.Count, "FetchBasegameIdentificationServiceManifest failed: No items were parsed, the list is empty!");

            var startupManifest = OnlineContent.FetchOnlineStartupManifest(true);

            Assert.AreNotEqual(0, startupManifest.Count, "FetchOnlineStartupManifest failed: No items were parsed, the list is empty!");
        }
Ejemplo n.º 18
0
        public void ValidateArchiveModLoading()
        {
            GlobalTest.Init();

            Console.WriteLine("Fetching third party services");
            App.ThirdPartyImportingService      = OnlineContent.FetchThirdPartyImportingService();
            App.ThirdPartyIdentificationService = OnlineContent.FetchThirdPartyIdentificationManifest();

            var        compressedModsDirectory = Path.Combine(GlobalTest.GetTestDataDirectory(), "compressedmods");
            List <Mod> modsFoundInArchive      = new List <Mod>();

            void addModCallback(Mod m)
            {
                Console.WriteLine($"Found mod in archive: {m.ModName}");
                modsFoundInArchive.Add(m);
            }

            void failedModCallback(Mod m)
            {
                Console.WriteLine($"A mod failed to load. This may be expected: {m.ModName}");
            }

            void logMessageCallback(string m)
            {
                Console.WriteLine(m);
            }

            foreach (var archive in Directory.GetFiles(compressedModsDirectory))
            {
                modsFoundInArchive.Clear();
                var realArchiveInfo = GlobalTest.ParseRealArchiveAttributes(archive);
                Console.WriteLine($"Inspecting archive: { archive}");
                ModArchiveImporter.InspectArchive(archive, addModCallback, failedModCallback, logMessageCallback, forcedMD5: realArchiveInfo.md5, forcedSize: realArchiveInfo.size);
                Assert.AreEqual(realArchiveInfo.nummodsexpected, modsFoundInArchive.Count, $"{archive} did not parse correct amount of mods.");
            }
        }
Ejemplo n.º 19
0
        private void UpdateModMakerMod(OnlineContent.ModMakerModUpdateInfo mui)
        {
            //throw new NotImplementedException();
            NamedBackgroundWorker bw = new NamedBackgroundWorker(@"ModmakerModUpdaterThread-" + mui.mod.ModName);

            bw.DoWork += (a, b) =>
            {
                mui.DownloadButtonText = M3L.GetString(M3L.string_compiling);

                OperationInProgress  = true;
                mui.UpdateInProgress = true;
                mui.Indeterminate    = false;

                mui.UIStatusString = M3L.GetString(M3L.string_downloadingDelta);
                var normalEndpoint = OnlineContent.ModmakerModsEndpoint + mui.ModMakerId;
                var lzmaEndpoint   = normalEndpoint + @"&method=lzma";

                string modDelta = null;

                //Try LZMA first
                try
                {
                    var download = OnlineContent.DownloadToMemory(lzmaEndpoint);
                    if (download.errorMessage == null)
                    {
                        mui.UIStatusString = M3L.GetString(M3L.string_decompressingDelta);
                        // OK
                        var decompressed = SevenZipHelper.LZMA.DecompressLZMAFile(download.result.ToArray());
                        modDelta = Encoding.UTF8.GetString(decompressed);
                        // File.WriteAllText(@"C:\users\mgamerz\desktop\decomp.txt", modDelta);
                    }
                    else
                    {
                        Log.Error(@"Error downloading lzma mod delta to memory: " + download.errorMessage);
                    }
                }
                catch (Exception e)
                {
                    Log.Error(@"Error downloading LZMA mod delta to memory: " + e.Message);
                }

                if (modDelta == null)
                {
                    //failed to download LZMA.
                    var download = OnlineContent.DownloadToMemory(normalEndpoint);
                    if (download.errorMessage == null)
                    {
                        //OK
                        modDelta = Encoding.UTF8.GetString(download.result.ToArray());
                    }
                    else
                    {
                        Log.Error(@"Error downloading decompressed mod delta to memory: " + download.errorMessage);
                    }
                }

                void setOverallMax(int max)
                {
                    mui.OverallProgressMax = max;
                }

                void setOverallValue(int current)
                {
                    mui.OverallProgressValue = current;
                    if (current > mui.OverallProgressMax)
                    {
                        Debugger.Break();
                    }
                }

                void setCurrentTaskString(string str)
                {
                    mui.UIStatusString = str;
                }

                if (modDelta != null)
                {
                    var compiler = new ModMakerCompiler(mui.ModMakerId);
                    //compiler.SetCurrentMaxCallback = SetCurrentMax;
                    //compiler.SetCurrentValueCallback = SetCurrentProgressValue;
                    compiler.SetOverallMaxCallback   = setOverallMax;
                    compiler.SetOverallValueCallback = setOverallValue;
                    //compiler.SetCurrentTaskIndeterminateCallback = SetCurrentTaskIndeterminate;
                    compiler.SetCurrentTaskStringCallback = setCurrentTaskString;
                    //compiler.SetModNameCallback = SetModNameOrDownloadText;
                    //compiler.SetCompileStarted = CompilationInProgress;
                    //compiler.SetModNotFoundCallback = ModNotFound;
                    Mod m = compiler.DownloadAndCompileMod(modDelta);
                    File.WriteAllText(System.IO.Path.Combine(Utilities.GetModmakerDefinitionsCache(), mui.ModMakerId + @".xml"), modDelta);
                    mui.DownloadButtonText = M3L.GetString(M3L.string_updated);
                    mui.UIStatusString     = M3L.GetString(M3L.string_interp_modMakerCodeX, mui.ModMakerId);
                    mui.UpdateInProgress   = false;
                    mui.CanUpdate          = false;
                    AnyModUpdated          = true;
                    //b.Result = m;
                }
            };
            bw.RunWorkerCompleted += (a, b) =>
            {
                OperationInProgress = false;
                CommandManager.InvalidateRequerySuggested();
            };
            bw.RunWorkerAsync();
        }
        public void TestBuildingExtractionQueues()
        {
            GlobalTest.Init();

            Console.WriteLine("Fetching third party services");
            App.ThirdPartyImportingService      = OnlineContent.FetchThirdPartyImportingService();
            App.ThirdPartyIdentificationService = OnlineContent.FetchThirdPartyIdentificationManifest();

            var        compressedModsDirectory = Path.Combine(GlobalTest.GetTestDataDirectory(), "compressedmods");
            List <Mod> modsFoundInArchive      = new List <Mod>();

            void addModCallback(Mod m)
            {
                Console.WriteLine($"Found mod in archive: {m.ModName}");
                modsFoundInArchive.Add(m);
            }

            void failedModCallback(Mod m)
            {
                //Console.WriteLine($"A mod failed to load. This may be expected: {m.ModName}");
            }

            void logMessageCallback(string m)
            {
                Console.WriteLine(m);
            }

            #region Get Targets
            List <GameTarget> targets = new List <GameTarget>();
            var root = GlobalTest.GetTestGameFoldersDirectory(Mod.MEGame.ME1);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(Mod.MEGame.ME1, d, false, false);
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            root = GlobalTest.GetTestGameFoldersDirectory(Mod.MEGame.ME2);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(Mod.MEGame.ME2, d, false, false);
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            root = GlobalTest.GetTestGameFoldersDirectory(Mod.MEGame.ME3);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(Mod.MEGame.ME3, d, false, false);
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            #endregion


            foreach (var archive in Directory.GetFiles(compressedModsDirectory))
            {
                modsFoundInArchive.Clear();
                var realArchiveInfo = GlobalTest.ParseRealArchiveAttributes(archive);
                Console.WriteLine($"Inspecting archive: { archive}");
                ModArchiveImporter.InspectArchive(archive, addModCallback, failedModCallback, logMessageCallback, forcedMD5: realArchiveInfo.md5, forcedSize: realArchiveInfo.size);
                var archiveZ = new SevenZipExtractor(archive);
                foreach (var mod in modsFoundInArchive)
                {
                    mod.GetAllRelativeReferences(archiveZ);
                    var targetsForMod = targets.Where(x => x.Game == mod.Game).ToList();
                    foreach (var target in targetsForMod)
                    {
                        mod.GetInstallationQueues(target);
                    }
                }
            }
        }
Ejemplo n.º 21
0
        public void TestBuildingExtractionQueues()
        {
            GlobalTest.Init();

            Console.WriteLine("Fetching third party services");
            App.ThirdPartyImportingService      = OnlineContent.FetchThirdPartyImportingService();
            App.ThirdPartyIdentificationService = OnlineContent.FetchThirdPartyIdentificationManifest();

            var        compressedModsDirectory = Path.Combine(GlobalTest.GetTestDataDirectory(), "compressedmods");
            List <Mod> modsFoundInArchive      = new List <Mod>();

            void addModCallback(Mod m)
            {
                Console.WriteLine($"Found mod in archive: {m.ModName}");
                modsFoundInArchive.Add(m);
            }

            void failedModCallback(Mod m)
            {
                //Console.WriteLine($"A mod failed to load. This may be expected: {m.ModName}");
            }

            void logMessageCallback(string m)
            {
                Console.WriteLine(m);
            }

            #region Get Targets
            List <GameTarget> targets = new List <GameTarget>();
            var root = GlobalTest.GetTestGameFoldersDirectory(Mod.MEGame.ME1);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(Mod.MEGame.ME1, d, false, false);
                gt.ValidateTarget();
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            root = GlobalTest.GetTestGameFoldersDirectory(Mod.MEGame.ME2);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(Mod.MEGame.ME2, d, false, false);
                gt.ValidateTarget();
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            root = GlobalTest.GetTestGameFoldersDirectory(Mod.MEGame.ME3);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(Mod.MEGame.ME3, d, false, false);
                gt.ValidateTarget();
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            #endregion

            //Compressed Mods

            foreach (var archive in Directory.GetFiles(compressedModsDirectory))
            {
                modsFoundInArchive.Clear();
                var realArchiveInfo = GlobalTest.ParseRealArchiveAttributes(archive);
                Console.WriteLine($@"Inspecting archive: { archive}");
                ModArchiveImporter.InspectArchive(archive, addModCallback, failedModCallback, logMessageCallback, forcedMD5: realArchiveInfo.md5, forcedSize: realArchiveInfo.size);
                var archiveZ = new SevenZipExtractor(archive);
                foreach (var mod in modsFoundInArchive)
                {
                    bool altsOn = false;
                    while (true)
                    {
                        if (altsOn)
                        {
                            foreach (var job in mod.InstallationJobs)
                            {
                                List <string> selectedGroups = new List <string>();
                                foreach (var altfile in job.AlternateFiles)
                                {
                                    if (altfile.GroupName != null)
                                    {
                                        if (selectedGroups.Contains(altfile.GroupName))
                                        {
                                            continue; //we already did first time of this. I know that's a weak test case...
                                        }
                                        selectedGroups.Add(altfile.GroupName);
                                    }

                                    altfile.IsSelected = true;
                                }
                            }
                        }

                        var refs = mod.GetAllRelativeReferences(!mod.IsVirtualized, archiveZ); //test
                        //validate references are actually in this archive
                        foreach (var fileREf in refs)
                        {
                            var expectedPath = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, fileREf);
                            //var expectedPath = fileREf;
                            var inArchiveFile = archiveZ.ArchiveFileData.FirstOrDefault(x => x.FileName == expectedPath);
                            Assert.IsNotNull(inArchiveFile.FileName, "Relative referenced file was not found in archive: " + fileREf);
                        }

                        //size test
                        Assert.AreNotEqual(0, mod.SizeRequiredtoExtract, "Archive extraction size is zero! For file " + archive);

                        var targetsForMod = targets.Where(x => x.Game == mod.Game).ToList();
                        foreach (var target in targetsForMod)
                        {
                            var queue = mod.GetInstallationQueues(target);
                            foreach (var jobMapping in queue.Item1)
                            {
                                foreach (var unpackedItem in jobMapping.Value.unpackedJobMapping)
                                {
                                    string sourceFile;
                                    if (jobMapping.Key.JobDirectory == null || unpackedItem.Value.IsFullRelativeFilePath)
                                    {
                                        sourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, unpackedItem.Value.FilePath);
                                    }
                                    else
                                    {
                                        sourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, jobMapping.Key.JobDirectory, unpackedItem.Value.FilePath);
                                    }
                                    Assert.IsTrue(archiveZ.ArchiveFileNames.Contains(sourceFile), "Archive should contain a file specified by mod (mod is valid) but does not appear to. File: " + sourceFile);
                                }
                            }
                        }

                        if (!altsOn)
                        {
                            altsOn = true;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            //EXE mods
            var exeModsDirectory = Path.Combine(GlobalTest.GetTestDataDirectory(), "exemods");

            if (Directory.Exists(exeModsDirectory))
            {
                foreach (var exe in Directory.GetFiles(exeModsDirectory))
                {
                    modsFoundInArchive.Clear();
                    //var realArchiveInfo = GlobalTest.ParseRealArchiveAttributes(exe);
                    Console.WriteLine($@"Inspecting exe: { exe}");
                    ModArchiveImporter.InspectArchive(exe, addModCallback, failedModCallback, logMessageCallback);
                    var archiveZ = new SevenZipExtractor(exe, InArchiveFormat.Nsis);
                    foreach (var mod in modsFoundInArchive)
                    {
                        foreach (var job in mod.InstallationJobs)
                        {
                            List <string> selectedGroups = new List <string>();
                            foreach (var altfile in job.AlternateFiles)
                            {
                                if (altfile.GroupName != null)
                                {
                                    if (selectedGroups.Contains(altfile.GroupName))
                                    {
                                        continue; //we already did first time of this. I know that's a weak test case...
                                    }
                                    selectedGroups.Add(altfile.GroupName);
                                }

                                altfile.IsSelected = true;
                            }
                        }


                        var refs = mod.GetAllRelativeReferences(false, archiveZ); //test and get refs. exe mods will always be virtualized as they won't have a moddesc.ini file.
                                                                                  //exe mods remap to subconetns
                                                                                  //same code as Mod-Extraction.cs
                        foreach (var fileREf in refs)
                        {
                            var expectedPath = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, fileREf);
                            //var expectedPath = fileREf;
                            var inArchiveFile = archiveZ.ArchiveFileData.FirstOrDefault(x => x.FileName == expectedPath);
                            Assert.IsNotNull(inArchiveFile.FileName, "Relative referenced file was not found in archive: " + fileREf);
                        }

                        mod.ExtractFromArchive(exe, "", false, testRun: true);
                    }
                }
            }
            else
            {
                Console.WriteLine("No exemods directory found. This section of testing will be skipped");
            }
        }
        //this should be private but no way to test it private for now...

        /// <summary>
        /// Inspects and loads compressed mods from an archive.
        /// </summary>
        /// <param name="filepath">Path of the archive</param>
        /// <param name="addCompressedModCallback">Callback indicating that the mod should be added to the collection of found mods</param>
        /// <param name="currentOperationTextCallback">Callback to tell caller what's going on'</param>
        /// <param name="forcedOverrideData">Override data about archive. Used for testing only</param>
        public static void InspectArchive(string filepath, Action <Mod> addCompressedModCallback = null, Action <Mod> failedToLoadModeCallback = null, Action <string> currentOperationTextCallback = null, string forcedMD5 = null, int forcedSize = -1)
        {
            string     relayVersionResponse = @"-1";
            List <Mod> internalModList      = new List <Mod>(); //internal mod list is for this function only so we don't need a callback to get our list since results are returned immediately
            var        isExe       = filepath.EndsWith(@".exe");
            var        archiveFile = isExe ? new SevenZipExtractor(filepath, InArchiveFormat.Nsis) : new SevenZipExtractor(filepath);

            using (archiveFile)
            {
#if DEBUG
                foreach (var v in archiveFile.ArchiveFileData)
                {
                    Debug.WriteLine($@"{v.FileName} | Index {v.Index} | Size {v.Size}");
                }
#endif
                var moddesciniEntries = new List <ArchiveFileInfo>();
                var sfarEntries       = new List <ArchiveFileInfo>(); //ME3 DLC
                var bioengineEntries  = new List <ArchiveFileInfo>(); //ME2 DLC
                var me2mods           = new List <ArchiveFileInfo>(); //ME2 RCW Mods
                foreach (var entry in archiveFile.ArchiveFileData)
                {
                    string fname = Path.GetFileName(entry.FileName);
                    if (!entry.IsDirectory && fname.Equals(@"moddesc.ini", StringComparison.InvariantCultureIgnoreCase))
                    {
                        moddesciniEntries.Add(entry);
                    }
                    else if (!entry.IsDirectory && fname.Equals(@"Default.sfar", StringComparison.InvariantCultureIgnoreCase))
                    {
                        //for unofficial lookups
                        sfarEntries.Add(entry);
                    }
                    else if (!entry.IsDirectory && fname.Equals(@"BIOEngine.ini", StringComparison.InvariantCultureIgnoreCase))
                    {
                        //for unofficial lookups
                        bioengineEntries.Add(entry);
                    }
                    else if (!entry.IsDirectory && Path.GetExtension(fname) == @".me2mod")
                    {
                        //for unofficial lookups
                        me2mods.Add(entry);
                    }
                }

                if (moddesciniEntries.Count > 0)
                {
                    foreach (var entry in moddesciniEntries)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        Mod m = new Mod(entry, archiveFile);
                        if (m.ValidMod)
                        {
                            addCompressedModCallback?.Invoke(m);
                            internalModList.Add(m);
                        }
                        else
                        {
                            failedToLoadModeCallback?.Invoke(m);
                        }
                    }
                }
                else if (me2mods.Count > 0)
                {
                    //found some .me2mod files.
                    foreach (var entry in me2mods)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        MemoryStream ms = new MemoryStream();
                        archiveFile.ExtractFile(entry.Index, ms);
                        ms.Position = 0;
                        StreamReader reader         = new StreamReader(ms);
                        string       text           = reader.ReadToEnd();
                        var          rcwModsForFile = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(entry.FileName), text);
                        foreach (var rcw in rcwModsForFile)
                        {
                            Mod m = new Mod(rcw);
                            addCompressedModCallback?.Invoke(m);
                            internalModList.Add(m);
                        }
                    }
                }
                else
                {
                    Log.Information(@"Querying third party importing service for information about this file");
                    currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_queryingThirdPartyImportingService));
                    var  md5  = forcedMD5 ?? Utilities.CalculateMD5(filepath);
                    long size = forcedSize > 0 ? forcedSize : new FileInfo(filepath).Length;
                    var  potentialImportinInfos = ThirdPartyServices.GetImportingInfosBySize(size);
                    var  importingInfo          = potentialImportinInfos.FirstOrDefault(x => x.md5 == md5);

                    if (importingInfo == null && isExe)
                    {
                        Log.Error(@"EXE-based mods must be validated by ME3Tweaks before they can be imported into M3. This is to prevent breaking third party mods.");
                        return;
                    }

                    if (importingInfo?.servermoddescname != null)
                    {
                        //Partially supported unofficial third party mod
                        //Mod has a custom written moddesc.ini stored on ME3Tweaks
                        Log.Information(@"Fetching premade moddesc.ini from ME3Tweaks for this mod archive");
                        string custommoddesc    = OnlineContent.FetchThirdPartyModdesc(importingInfo.servermoddescname);
                        Mod    virutalCustomMod = new Mod(custommoddesc, "", archiveFile); //Load virutal mod
                        if (virutalCustomMod.ValidMod)
                        {
                            addCompressedModCallback?.Invoke(virutalCustomMod);
                            internalModList.Add(virutalCustomMod);
                            return; //Don't do further parsing as this is custom written
                        }
                        else
                        {
                            Log.Error(@"Server moddesc was not valid for this mod. This shouldn't occur. Please report to Mgamerz.");
                            return;
                        }
                    }

                    ExeTransform transform = null;
                    if (importingInfo?.exetransform != null)
                    {
                        Log.Information(@"TPIS lists exe transform for this mod: " + importingInfo.exetransform);
                        transform = new ExeTransform(OnlineContent.FetchExeTransform(importingInfo.exetransform));
                    }

                    //Fully unofficial third party mod.

                    //ME3
                    foreach (var sfarEntry in sfarEntries)
                    {
                        var vMod = AttemptLoadVirtualMod(sfarEntry, archiveFile, Mod.MEGame.ME3, md5);
                        if (vMod.ValidMod)
                        {
                            addCompressedModCallback?.Invoke(vMod);
                            internalModList.Add(vMod);
                            vMod.ExeExtractionTransform = transform;
                        }
                    }

                    //TODO: ME2
                    //foreach (var entry in bioengineEntries)
                    //{
                    //    var vMod = AttemptLoadVirtualMod(entry, archiveFile, Mod.MEGame.ME2, md5);
                    //    if (vMod.ValidMod)
                    //    {
                    //        addCompressedModCallback?.Invoke(vMod);
                    //        internalModList.Add(vMod);
                    //    }
                    //}

                    //TODO: ME1

                    if (importingInfo?.version != null)
                    {
                        foreach (Mod compressedMod in internalModList)
                        {
                            compressedMod.ModVersionString = importingInfo.version;
                            Version.TryParse(importingInfo.version, out var parsedValue);
                            compressedMod.ParsedModVersion = parsedValue;
                        }
                    }
                    else if (relayVersionResponse == @"-1")
                    {
                        //If no version information, check ME3Tweaks to see if it's been added recently
                        //see if server has information on version number
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_gettingAdditionalInformationAboutFileFromME3Tweaks));
                        Log.Information(@"Querying ME3Tweaks for additional information");
                        var modInfo = OnlineContent.QueryModRelay(md5, size);
                        //todo: make this work offline.
                        if (modInfo != null && modInfo.TryGetValue(@"version", out string value))
                        {
                            Log.Information(@"ME3Tweaks reports version number for this file is: " + value);
                            foreach (Mod compressedMod in internalModList)
                            {
                                compressedMod.ModVersionString = value;
                                Version.TryParse(value, out var parsedValue);
                                compressedMod.ParsedModVersion = parsedValue;
                            }
                            relayVersionResponse = value;
                        }
                        else
                        {
                            Log.Information(@"ME3Tweaks does not have additional version information for this file");
                            Analytics.TrackEvent("Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                            {
                                { "Filename", Path.GetFileName(filepath) },
                                { "MD5", md5 }
                            });
                        }
                    }

                    else
                    {
                        //Try straight up TPMI import?
                        Log.Warning($@"No importing information is available for file with hash {md5}. No mods could be found.");
                        Analytics.TrackEvent("Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                        {
                            { "Filename", Path.GetFileName(filepath) },
                            { "MD5", md5 }
                        });
                    }
                }
            }
        }
Ejemplo n.º 23
0
        public void ExtractFromArchive(string archivePath, string outputFolderPath, bool compressPackages,
                                       Action <string> updateTextCallback = null, Action <DetailedProgressEventArgs> extractingCallback = null, Action <string, int, int> compressedPackageCallback = null)
        {
            if (!IsInArchive)
            {
                throw new Exception(@"Cannot extract a mod that is not part of an archive.");
            }
            compressPackages &= Game == MEGame.ME3; //ME3 ONLY FOR NOW
            var archiveFile = archivePath.EndsWith(@".exe") ? new SevenZipExtractor(archivePath, InArchiveFormat.Nsis) : new SevenZipExtractor(archivePath);

            using (archiveFile)
            {
                var fileIndicesToExtract = new List <int>();
                var referencedFiles      = GetAllRelativeReferences(archiveFile);

                if (!IsVirtualized)
                {
                    referencedFiles.Add(@"moddesc.ini");
                }
                //unsure if this is required?? doesn't work for MEHEM EXE
                //referencedFiles = referencedFiles.Select(x => FilesystemInterposer.PathCombine(IsInArchive, ModPath, x)).ToList(); //remap to in-archive paths so they match entry paths
                foreach (var info in archiveFile.ArchiveFileData)
                {
                    if (referencedFiles.Contains(info.FileName))
                    {
                        Log.Information(@"Adding file to extraction list: " + info.FileName);
                        fileIndicesToExtract.Add(info.Index);
                    }
                }
                #region old

                /*
                 * bool fileAdded = false;
                 * //moddesc.ini
                 * if (info.FileName == ModDescPath)
                 * {
                 * //Debug.WriteLine("Add file to extraction list: " + info.FileName);
                 * fileIndicesToExtract.Add(info.Index);
                 * continue;
                 * }
                 *
                 * //Check each job
                 * foreach (ModJob job in InstallationJobs)
                 * {
                 * if (job.Header == ModJob.JobHeader.CUSTOMDLC)
                 * {
                 #region Extract Custom DLC
                 *  foreach (var localCustomDLCFolder in job.CustomDLCFolderMapping.Keys)
                 *  {
                 *      if (info.FileName.StartsWith(FilesystemInterposer.PathCombine(IsInArchive, ModPath, localCustomDLCFolder)))
                 *      {
                 *          //Debug.WriteLine("Add file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 *  //Alternate files
                 *  foreach (var alt in job.AlternateFiles)
                 *  {
                 *      if (alt.AltFile != null && info.FileName.Equals(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AltFile), StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add alternate file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 *  //Alternate DLC
                 *  foreach (var alt in job.AlternateDLCs)
                 *  {
                 *      if (info.FileName.StartsWith(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AlternateDLCFolder), StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add alternate dlc file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 #endregion
                 * }
                 * else
                 * {
                 #region Official headers
                 *
                 *  foreach (var inSubDirFile in job.FilesToInstall.Values)
                 *  {
                 *      var inArchivePath = FilesystemInterposer.PathCombine(IsInArchive, ModPath, job.JobDirectory, inSubDirFile); //keep relative if unpacked mod, otherwise use full in-archive path for extraction
                 *      if (info.FileName.Equals(inArchivePath, StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *  //Alternate files
                 *  foreach (var alt in job.AlternateFiles)
                 *  {
                 *      if (alt.AltFile != null && info.FileName.Equals(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AltFile), StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add alternate file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 #endregion
                 * }
                 * }
                 * }*/
                #endregion
                archiveFile.Progressing += (sender, args) => { extractingCallback?.Invoke(args); };
                string outputFilePathMapping(ArchiveFileInfo entryInfo)
                {
                    string entryPath = entryInfo.FileName;

                    if (ExeExtractionTransform != null && ExeExtractionTransform.PatchRedirects.Any(x => x.index == entryInfo.Index))
                    {
                        Log.Information(@"Extracting vpatch file at index " + entryInfo.Index);
                        return(Path.Combine(Utilities.GetVPatchRedirectsFolder(), ExeExtractionTransform.PatchRedirects.First(x => x.index == entryInfo.Index).outfile));
                    }

                    if (ExeExtractionTransform != null && ExeExtractionTransform.NoExtractIndexes.Any(x => x == entryInfo.Index))
                    {
                        Log.Information(@"Extracting file to trash (not used): " + entryPath);
                        return(Path.Combine(Utilities.GetTempPath(), @"Trash", @"trashfile"));
                    }

                    if (ExeExtractionTransform != null && ExeExtractionTransform.AlternateRedirects.Any(x => x.index == entryInfo.Index))
                    {
                        var outfile = ExeExtractionTransform.AlternateRedirects.First(x => x.index == entryInfo.Index).outfile;
                        Log.Information($@"Extracting file with redirection: {entryPath} {outfile}");
                        return(Path.Combine(outputFolderPath, outfile));
                    }

                    //Archive path might start with a \. Substring may return value that start with a \
                    var subModPath = entryPath /*.TrimStart('\\')*/.Substring(ModPath.Length).TrimStart('\\');
                    var path       = Path.Combine(outputFolderPath, subModPath);


                    //Debug.WriteLine("remapping output: " + entryPath + " -> " + path);
                    return(path);
                }

                if (compressPackages)
                {
                    compressionQueue = new BlockingCollection <string>();
                }

                int numberOfPackagesToCompress = referencedFiles.Count(x => x.RepresentsPackageFilePath());
                int compressedPackageCount     = 0;
                NamedBackgroundWorker compressionThread;
                if (compressPackages)
                {
                    compressionThread         = new NamedBackgroundWorker(@"ImportingCompressionThread");
                    compressionThread.DoWork += (a, b) =>
                    {
                        try
                        {
                            while (true)
                            {
                                var package = compressionQueue.Take();
                                //updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)));
                                var p = MEPackageHandler.OpenMEPackage(package);
                                //Check if any compressed textures.
                                bool shouldNotCompress = false;
                                foreach (var texture in p.Exports.Where(x => x.IsTexture()))
                                {
                                    var storageType = Texture2D.GetTopMipStorageType(texture);
                                    shouldNotCompress |= storageType == ME3Explorer.Unreal.StorageTypes.pccLZO || storageType == ME3Explorer.Unreal.StorageTypes.pccZlib;
                                    if (!shouldNotCompress)
                                    {
                                        break;
                                    }
                                }

                                if (!shouldNotCompress)
                                {
                                    compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress);
                                    Log.Information(@"Compressing package: " + package);
                                    p.save(true);
                                }
                                else
                                {
                                    Log.Information(@"Not compressing package due to file containing compressed textures: " + package);
                                }


                                Interlocked.Increment(ref compressedPackageCount);
                                compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressedX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress);
                            }
                        }
                        catch (InvalidOperationException)
                        {
                            //Done.
                            lock (compressionCompletedSignaler)
                            {
                                Monitor.Pulse(compressionCompletedSignaler);
                            }
                        }
                    };
                    compressionThread.RunWorkerAsync();
                }
                archiveFile.FileExtractionFinished += (sender, args) =>
                {
                    if (compressPackages)
                    {
                        var fToCompress = outputFilePathMapping(args.FileInfo);
                        if (fToCompress.RepresentsPackageFilePath())
                        {
                            //Debug.WriteLine("Adding to blocking queue");
                            compressionQueue.TryAdd(fToCompress);
                        }
                    }
                };



                archiveFile.ExtractFiles(outputFolderPath, outputFilePathMapping, fileIndicesToExtract.ToArray());
                Log.Information(@"File extraction completed.");


                compressionQueue?.CompleteAdding();
                if (compressPackages && numberOfPackagesToCompress > 0 && numberOfPackagesToCompress > compressedPackageCount)
                {
                    Log.Information(@"Waiting for compression of packages to complete.");
                    while (!compressionQueue.IsCompleted)
                    {
                        lock (compressionCompletedSignaler)
                        {
                            Monitor.Wait(compressionCompletedSignaler);
                        }
                    }

                    Log.Information(@"Package compression has completed.");
                }
                ModPath = outputFolderPath;
                if (IsVirtualized)
                {
                    var parser = new IniDataParser().Parse(VirtualizedIniText);
                    parser[@"ModInfo"][@"modver"] = ModVersionString; //In event relay service resolved this
                    File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), parser.ToString());
                    IsVirtualized = false;                            //no longer virtualized
                }

                if (ExeExtractionTransform != null)
                {
                    var vpat = Utilities.GetCachedExecutablePath(@"vpat.exe");
                    Utilities.ExtractInternalFile(@"MassEffectModManagerCore.modmanager.executables.vpat.exe", vpat, true);

                    //Handle VPatching
                    foreach (var transform in ExeExtractionTransform.VPatches)
                    {
                        var patchfile  = Path.Combine(Utilities.GetVPatchRedirectsFolder(), transform.patchfile);
                        var inputfile  = Path.Combine(ModPath, transform.inputfile);
                        var outputfile = Path.Combine(ModPath, transform.outputfile);

                        var args = $"\"{patchfile}\" \"{inputfile}\" \"{outputfile}\"";      //do not localize
                        Directory.CreateDirectory(Directory.GetParent(outputfile).FullName); //ensure output directory exists as vpatch will not make one.
                        Log.Information($@"VPatching file into alternate: {inputfile} to {outputfile}");
                        updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_vPatchingIntoAlternate, Path.GetFileName(inputfile)));
                        Utilities.RunProcess(vpat, args, true, false, false, true);
                    }

                    //Handle copyfile
                    foreach (var copyfile in ExeExtractionTransform.CopyFiles)
                    {
                        string srcfile  = Path.Combine(ModPath, copyfile.inputfile);
                        string destfile = Path.Combine(ModPath, copyfile.outputfile);
                        Log.Information($@"Applying transform copyfile: {srcfile} -> {destfile}");
                        File.Copy(srcfile, destfile, true);
                    }

                    if (ExeExtractionTransform.PostTransformModdesc != null)
                    {
                        //fetch online moddesc for this mod.
                        Log.Information(@"Fetching post-transform third party moddesc.");
                        var moddesc = OnlineContent.FetchThirdPartyModdesc(ExeExtractionTransform.PostTransformModdesc);
                        File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), moddesc);
                    }
                }

                //int packagesCompressed = 0;
                //if (compressPackages)
                //{
                //    var packages = Utilities.GetPackagesInDirectory(ModPath, true);
                //    extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0));
                //    foreach (var package in packages)
                //    {
                //        updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)));
                //        Log.Information("Compressing package: " + package);
                //        var p = MEPackageHandler.OpenMEPackage(package);
                //        p.save(true);

                //        packagesCompressed++;
                //        extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0));
                //    }
                //}
            }
        }
Ejemplo n.º 24
0
        private void UpdateModMakerMod(OnlineContent.ModMakerModUpdateInfo mui)
        {
            //throw new NotImplementedException();
            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ModmakerModUpdaterThread-" + mui.mod.ModName);

            nbw.WorkerReportsProgress = true;
            nbw.ProgressChanged      += (a, b) =>
            {
                if (b.UserState is double d)
                {
                    mainwindow.TaskBarItemInfoHandler.ProgressValue = d;
                }
            };
            nbw.DoWork += (a, b) =>
            {
                mui.DownloadButtonText = M3L.GetString(M3L.string_compiling);

                OperationInProgress  = true;
                mui.UpdateInProgress = true;
                mui.Indeterminate    = false;

                mui.UIStatusString = M3L.GetString(M3L.string_downloadingDelta);
                var normalEndpoint = OnlineContent.ModmakerModsEndpoint + mui.ModMakerId;
                var lzmaEndpoint   = normalEndpoint + @"&method=lzma";

                string modDelta = null;

                //Try LZMA first
                try
                {
                    var download = OnlineContent.DownloadToMemory(lzmaEndpoint);
                    if (download.errorMessage == null)
                    {
                        mui.UIStatusString = M3L.GetString(M3L.string_decompressingDelta);
                        // OK
                        var decompressed = SevenZipHelper.LZMA.DecompressLZMAFile(download.result.ToArray());
                        modDelta = Encoding.UTF8.GetString(decompressed);
                    }
                    else
                    {
                        Log.Error(@"Error downloading lzma mod delta to memory: " + download.errorMessage);
                    }
                }
                catch (Exception e)
                {
                    Log.Error(@"Error downloading LZMA mod delta to memory: " + e.Message);
                }

                if (modDelta == null)
                {
                    //failed to download LZMA.
                    var download = OnlineContent.DownloadToMemory(normalEndpoint);
                    if (download.errorMessage == null)
                    {
                        //OK
                        modDelta = Encoding.UTF8.GetString(download.result.ToArray());
                    }
                    else
                    {
                        Log.Error(@"Error downloading decompressed mod delta to memory: " + download.errorMessage);
                    }
                }

                void setOverallMax(int max)
                {
                    mui.OverallProgressMax = max;
                }

                void setOverallValue(int current)
                {
                    mui.OverallProgressValue = current;
                    nbw.ReportProgress(0, current * 1.0 / mui.OverallProgressMax);
                    if (current > mui.OverallProgressMax)
                    {
                        Debugger.Break();
                    }
                }

                void setCurrentTaskString(string str)
                {
                    mui.UIStatusString = str;
                }

                if (modDelta != null)
                {
                    var compiler = new ModMakerCompiler(mui.ModMakerId);
                    //compiler.SetCurrentMaxCallback = SetCurrentMax;
                    //compiler.SetCurrentValueCallback = SetCurrentProgressValue;
                    compiler.SetOverallMaxCallback   = setOverallMax;
                    compiler.SetOverallValueCallback = setOverallValue;
                    //compiler.SetCurrentTaskIndeterminateCallback = SetCurrentTaskIndeterminate;
                    compiler.SetCurrentTaskStringCallback = setCurrentTaskString;
                    //compiler.SetModNameCallback = SetModNameOrDownloadText;
                    //compiler.SetCompileStarted = CompilationInProgress;
                    //compiler.SetModNotFoundCallback = ModNotFound;
                    Mod m = compiler.DownloadAndCompileMod(modDelta);
                    if (m != null)
                    {
                        try
                        {
                            File.WriteAllText(System.IO.Path.Combine(Utilities.GetModmakerDefinitionsCache(), mui.ModMakerId + @".xml"), modDelta);
                        }
                        catch (Exception e)
                        {
                            Log.Error(@"Couldn't cache modmaker xml file: " + e.Message);
                        }

                        mui.DownloadButtonText = M3L.GetString(M3L.string_updated);
                        mui.UIStatusString     = M3L.GetString(M3L.string_interp_modMakerCodeX, mui.ModMakerId);
                        mui.UpdateInProgress   = false;
                        mui.CanUpdate          = false;
                        AnyModUpdated          = true;
                    }
                    else
                    {
                        mui.UpdateInProgress   = false;
                        mui.DownloadButtonText = M3L.GetString(M3L.string_compilingFailed);
                        mui.UpdateInProgress   = false;
                    }
                }
            };
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occured in {nbw.Name} thread: {b.Error.Message}");
                }
                Analytics.TrackEvent(@"Updated mod", new Dictionary <string, string>()
                {
                    { @"Type", @"ModMaker" },
                    { @"ModName", mui.mod.ModName },
                    { @"Result", mui.CanUpdate ? @"Success" : @"Failed" }
                });

                mainwindow.TaskBarItemInfoHandler.ProgressState = TaskbarItemProgressState.None;
                OperationInProgress = false;
                CommandManager.InvalidateRequerySuggested();
            };
            mainwindow.TaskBarItemInfoHandler.ProgressValue = 0;
            mainwindow.TaskBarItemInfoHandler.ProgressState = TaskbarItemProgressState.Normal;
            nbw.RunWorkerAsync();
        }
Ejemplo n.º 25
0
        private void UpdateClassicMod(OnlineContent.ModUpdateInfo ui)
        {
            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ModUpdaterThread-" + ui.mod.ModName);

            nbw.WorkerReportsProgress = true;
            nbw.ProgressChanged      += (a, b) =>
            {
                if (b.UserState is double d)
                {
                    mainwindow.TaskBarItemInfoHandler.ProgressValue = d;
                }
            };
            nbw.DoWork += (a, b) =>
            {
                OperationInProgress   = true;
                ui.UpdateInProgress   = true;
                ui.Indeterminate      = false;
                ui.DownloadButtonText = M3L.GetString(M3L.string_downloading);
                ui.ProgressChanged   += (a, b) =>
                {
                    if (b.totalToDl != 0 && nbw.IsBusy) //? IsBusy needs to be here for some reason or it crashes, like progress comes in late or something.
                    {
                        nbw.ReportProgress(0, b.currentDl * 1.0 / b.totalToDl);
                    }
                };
                bool errorShown = false;
                void errorCallback(string message)
                {
                    if (!errorShown)
                    {
                        errorShown = true;
                        Application.Current.Dispatcher.Invoke(delegate { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_errorOccuredWhileUpdatingXErrorMessage, ui.mod.ModName, message), M3L.GetString(M3L.string_interp_errorUpdatingX, ui.mod.ModName), MessageBoxButton.OK, MessageBoxImage.Error); }
                                                              );
                    }
                }

                var stagingDirectory = Directory.CreateDirectory(Path.Combine(Utilities.GetTempPath(), Path.GetFileName(ui.mod.ModPath))).FullName;
                var modUpdated       = OnlineContent.UpdateMod(ui, stagingDirectory, errorCallback);
                ui.UpdateInProgress   = false;
                ui.CanUpdate          = !modUpdated;
                AnyModUpdated        |= modUpdated;
                ui.DownloadButtonText = ui.CanUpdate ? M3L.GetString(M3L.string_downloadUpdate) : M3L.GetString(M3L.string_updated);
                Utilities.DeleteFilesAndFoldersRecursively(stagingDirectory);
            };
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occured in {nbw.Name} thread: {b.Error.Message}");
                }
                Analytics.TrackEvent(@"Updated mod", new Dictionary <string, string>()
                {
                    { @"Type", @"Classic" },
                    { @"ModName", ui.mod.ModName },
                    { @"Result", ui.CanUpdate ? @"Success" : @"Failed" }
                });
                mainwindow.TaskBarItemInfoHandler.ProgressState = TaskbarItemProgressState.None;
                OperationInProgress = false;
                CommandManager.InvalidateRequerySuggested();
            };
            mainwindow.TaskBarItemInfoHandler.ProgressValue = 0;
            mainwindow.TaskBarItemInfoHandler.ProgressState = TaskbarItemProgressState.Normal;
            nbw.RunWorkerAsync();
        }
Ejemplo n.º 26
0
            private void ToggleDisabler()
            {
                NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"OIGDisablerThread");

                nbw.DoWork += async(a, b) =>
                {
                    if (!Utilities.IsGameRunning(Game))
                    {
                        var d3d9Path = Path.Combine(MEDirectories.ExecutableDirectory(SelectedTarget), @"d3d9.dll");
                        if (!File.Exists(d3d9Path))
                        {
                            if (File.Exists(Utilities.GetOriginOverlayDisableFile()))
                            {
                                Log.Information(@"Installing origin overlay disabler from cache to " + d3d9Path);
                                try
                                {
                                    File.Copy(Utilities.GetOriginOverlayDisableFile(), d3d9Path);
                                }
                                catch (Exception e)
                                {
                                    Log.Error($@"Error installing d3d9.dll: {e.Message}");
                                }
                            }
                            else
                            {
                                var client = new GitHubClient(new ProductHeaderValue(@"ME3TweaksModManager"));
                                try
                                {
                                    var releases = await client.Repository.Release.GetAll(@"ME3Tweaks", @"d3d9-blank-proxy");

                                    if (releases.Count > 0)
                                    {
                                        Log.Information(@"Parsing release information from github");

                                        //The release we want to check is always the latest with assets that is not a pre-release
                                        var latestRel = releases.FirstOrDefault(x => !x.Prerelease && x.Assets.Count > 0);
                                        if (latestRel != null)
                                        {
                                            var downloadUrl        = latestRel.Assets[0].BrowserDownloadUrl;
                                            var downloadedZipAsset = OnlineContent.DownloadToMemory(downloadUrl);
                                            using var zf = new ZipArchive(downloadedZipAsset.result);
                                            var d3d9 = zf.Entries.First(x => x.FullName == @"d3d9.dll");
                                            if (d3d9 != null)
                                            {
                                                await using var data = d3d9.Open();
                                                var memStream = new MemoryStream();
                                                data.CopyTo(memStream);
                                                try
                                                {
                                                    Log.Information(@"Installing origin overlay disabler from memory to " + d3d9Path);
                                                    memStream.WriteToFile(d3d9Path); //install
                                                    Log.Information(@"Caching d3d9 disabler");
                                                    memStream.WriteToFile(Utilities.GetOriginOverlayDisableFile());
                                                }
                                                catch (Exception e)
                                                {
                                                    Log.Error(@"Cannot install/cache disabler: " + e.Message);
                                                }
                                            }
                                        }
                                    }
                                }
                                catch (Exception e)
                                {
                                    Log.Error(@"Error checking for tool update: " + e);
                                }
                            }
                        }
                        else
                        {
                            Log.Information(@"Deleting " + d3d9Path);
                            try
                            {
                                File.Delete(d3d9Path);
                            }
                            catch (Exception e)
                            {
                                Log.Error($@"Error deleting d3d9.dll: {e.Message}");
                            }
                        }
                    }
                };
                nbw.RunWorkerCompleted += (await, b) =>
                {
                    SetupDisablerButtonText();
                };
                nbw.RunWorkerAsync();
            }
Ejemplo n.º 27
0
        /// <summary>
        /// Extracts the mod from the archive. The caller should handle exception that may be thrown.
        /// </summary>
        /// <param name="archivePath"></param>
        /// <param name="outputFolderPath"></param>
        /// <param name="compressPackages"></param>
        /// <param name="updateTextCallback"></param>
        /// <param name="extractingCallback"></param>
        /// <param name="compressedPackageCallback"></param>
        /// <param name="testRun"></param>
        public void ExtractFromArchive(string archivePath, string outputFolderPath, bool compressPackages,
                                       Action <string> updateTextCallback = null, Action <DetailedProgressEventArgs> extractingCallback = null, Action <string, int, int> compressedPackageCallback = null,
                                       bool testRun = false, Stream archiveStream = null)
        {
            if (!IsInArchive)
            {
                throw new Exception(@"Cannot extract a mod that is not part of an archive.");
            }
            if (archiveStream == null && !File.Exists(archivePath))
            {
                throw new Exception(M3L.GetString(M3L.string_interp_theArchiveFileArchivePathIsNoLongerAvailable, archivePath));
            }

            compressPackages &= Game >= MEGame.ME2;

            SevenZipExtractor archive;
            var isExe = archivePath.EndsWith(@".exe", StringComparison.InvariantCultureIgnoreCase);

            bool closeStreamOnFinish = true;

            if (archiveStream != null)
            {
                archive             = isExe ? new SevenZipExtractor(archiveStream, InArchiveFormat.Nsis) : new SevenZipExtractor(archiveStream);
                closeStreamOnFinish = false;
            }
            else
            {
                archive = isExe ? new SevenZipExtractor(archivePath, InArchiveFormat.Nsis) : new SevenZipExtractor(archivePath);
            }

            var fileIndicesToExtract       = new List <int>();
            var filePathsToExtractTESTONLY = new List <string>();
            var referencedFiles            = GetAllRelativeReferences(!IsVirtualized, archive);

            if (isExe)
            {
                //remap to mod root. Not entirely sure if this needs to be done for sub mods?
                referencedFiles = referencedFiles.Select(x => FilesystemInterposer.PathCombine(IsInArchive, ModPath, x)).ToList(); //remap to in-archive paths so they match entry paths
            }
            foreach (var info in archive.ArchiveFileData)
            {
                if (!info.IsDirectory && (ModPath == "" || info.FileName.Contains((string)ModPath)))
                {
                    var relativedName = isExe ? info.FileName : info.FileName.Substring(ModPath.Length).TrimStart('\\');
                    if (referencedFiles.Contains(relativedName))
                    {
                        Log.Information(@"Adding file to extraction list: " + info.FileName);
                        fileIndicesToExtract.Add(info.Index);
                        filePathsToExtractTESTONLY.Add(relativedName);
                    }
                }
            }

            void archiveExtractionProgress(object?sender, DetailedProgressEventArgs args)
            {
                extractingCallback?.Invoke(args);
            }

            archive.Progressing += archiveExtractionProgress;
            string outputFilePathMapping(ArchiveFileInfo entryInfo)
            {
                Log.Information(@"Mapping extraction target for " + entryInfo.FileName);

                string entryPath = entryInfo.FileName;

                if (ExeExtractionTransform != null && ExeExtractionTransform.PatchRedirects.Any(x => x.index == entryInfo.Index))
                {
                    Log.Information(@"Extracting vpatch file at index " + entryInfo.Index);
                    return(Path.Combine(Utilities.GetVPatchRedirectsFolder(), ExeExtractionTransform.PatchRedirects.First(x => x.index == entryInfo.Index).outfile));
                }

                if (ExeExtractionTransform != null && ExeExtractionTransform.NoExtractIndexes.Any(x => x == entryInfo.Index))
                {
                    Log.Information(@"Extracting file to trash (not used): " + entryPath);
                    return(Path.Combine(Utilities.GetTempPath(), @"Trash", @"trashfile"));
                }

                if (ExeExtractionTransform != null && ExeExtractionTransform.AlternateRedirects.Any(x => x.index == entryInfo.Index))
                {
                    var outfile = ExeExtractionTransform.AlternateRedirects.First(x => x.index == entryInfo.Index).outfile;
                    Log.Information($@"Extracting file with redirection: {entryPath} -> {outfile}");
                    return(Path.Combine(outputFolderPath, outfile));
                }

                //Archive path might start with a \. Substring may return value that start with a \
                var subModPath = entryPath /*.TrimStart('\\')*/.Substring(ModPath.Length).TrimStart('\\');
                var path       = Path.Combine(outputFolderPath, subModPath);


                //Debug.WriteLine("remapping output: " + entryPath + " -> " + path);
                return(path);
            }

            if (compressPackages)
            {
                compressionQueue = new BlockingCollection <string>();
            }

            int numberOfPackagesToCompress = referencedFiles.Count(x => x.RepresentsPackageFilePath());
            int compressedPackageCount     = 0;
            NamedBackgroundWorker compressionThread;

            if (compressPackages)
            {
                compressionThread         = new NamedBackgroundWorker(@"ImportingCompressionThread");
                compressionThread.DoWork += (a, b) =>
                {
                    try
                    {
                        while (true)
                        {
                            var  package           = compressionQueue.Take();
                            var  p                 = MEPackageHandler.OpenMEPackage(package);
                            bool shouldNotCompress = Game == MEGame.ME1;
                            if (!shouldNotCompress)
                            {
                                //updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)));
                                FileInfo fileInfo     = new FileInfo(package);
                                var      created      = fileInfo.CreationTime;  //File Creation
                                var      lastmodified = fileInfo.LastWriteTime; //File Modification

                                compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress);
                                Log.Information(@"Compressing package: " + package);
                                p.Save(compress: true);
                                File.SetCreationTime(package, created);
                                File.SetLastWriteTime(package, lastmodified);
                            }
                            else
                            {
                                Log.Information(@"Skipping compression for ME1 package file: " + package);
                            }


                            Interlocked.Increment(ref compressedPackageCount);
                            compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressedX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress);
                        }
                    }
                    catch (InvalidOperationException)
                    {
                        //Done.
                        lock (compressionCompletedSignaler)
                        {
                            Monitor.Pulse(compressionCompletedSignaler);
                        }
                    }
                };
                compressionThread.RunWorkerAsync();
            }

            void compressPackage(object?sender, FileInfoEventArgs args)
            {
                if (compressPackages)
                {
                    var fToCompress = outputFilePathMapping(args.FileInfo);
                    if (fToCompress.RepresentsPackageFilePath())
                    {
                        //Debug.WriteLine("Adding to blocking queue");
                        compressionQueue.TryAdd(fToCompress);
                    }
                }
            }

            archive.FileExtractionFinished += compressPackage;

            if (!testRun)
            {
                Log.Information(@"Extracting files...");
                archive.ExtractFiles(outputFolderPath, outputFilePathMapping, fileIndicesToExtract.ToArray());
            }
            else
            {
                // test run mode
                // exes can have duplicate filenames but different indexes so we must check for those here.
                if (fileIndicesToExtract.Count != referencedFiles.Count && filePathsToExtractTESTONLY.Distinct().ToList().Count != referencedFiles.Count)
                {
                    throw new Exception(@"The amount of referenced files does not match the amount of files that are going to be extracted!");
                }
            }
            Log.Information(@"File extraction completed.");
            archive.Progressing -= archiveExtractionProgress;

            compressionQueue?.CompleteAdding();
            if (compressPackages && numberOfPackagesToCompress > 0 && numberOfPackagesToCompress > compressedPackageCount)
            {
                Log.Information(@"Waiting for compression of packages to complete.");
                while (!compressionQueue.IsCompleted)
                {
                    lock (compressionCompletedSignaler)
                    {
                        Monitor.Wait(compressionCompletedSignaler);
                    }
                }

                Log.Information(@"Package compression has completed.");
            }

            archive.FileExtractionFinished -= compressPackage;

            ModPath = outputFolderPath;
            if (IsVirtualized)
            {
                var parser = new IniDataParser().Parse(VirtualizedIniText);
                parser[@"ModInfo"][@"modver"] = ModVersionString; //In event relay service resolved this
                if (!testRun)
                {
                    File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), parser.ToString());
                }
                IsVirtualized = false; //no longer virtualized
            }

            if (ExeExtractionTransform != null)
            {
                if (ExeExtractionTransform.VPatches.Any())
                {
                    // MEHEM uses Vpatching for its alternates.
                    var vpat = Utilities.GetCachedExecutablePath(@"vpat.exe");
                    if (!testRun)
                    {
                        Utilities.ExtractInternalFile(@"MassEffectModManagerCore.modmanager.executables.vpat.exe", vpat, true);
                    }
                    //Handle VPatching
                    foreach (var transform in ExeExtractionTransform.VPatches)
                    {
                        var patchfile  = Path.Combine(Utilities.GetVPatchRedirectsFolder(), transform.patchfile);
                        var inputfile  = Path.Combine(ModPath, transform.inputfile);
                        var outputfile = Path.Combine(ModPath, transform.outputfile);

                        var args = $"\"{patchfile}\" \"{inputfile}\" \"{outputfile}\""; //do not localize
                        if (!testRun)
                        {
                            Directory.CreateDirectory(Directory.GetParent(outputfile).FullName); //ensure output directory exists as vpatch will not make one.
                        }
                        Log.Information($@"VPatching file into alternate: {inputfile} to {outputfile}");
                        updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_vPatchingIntoAlternate, Path.GetFileName(inputfile)));
                        if (!testRun)
                        {
                            Utilities.RunProcess(vpat, args, true, false, false, true);
                        }
                    }
                }

                //Handle copyfile
                foreach (var copyfile in ExeExtractionTransform.CopyFiles)
                {
                    string srcfile  = Path.Combine(ModPath, copyfile.inputfile);
                    string destfile = Path.Combine(ModPath, copyfile.outputfile);
                    Log.Information($@"Applying transform copyfile: {srcfile} -> {destfile}");
                    if (!testRun)
                    {
                        File.Copy(srcfile, destfile, true);
                    }
                }

                if (ExeExtractionTransform.PostTransformModdesc != null)
                {
                    //fetch online moddesc for this mod.
                    Log.Information(@"Fetching post-transform third party moddesc.");
                    var moddesc = OnlineContent.FetchThirdPartyModdesc(ExeExtractionTransform.PostTransformModdesc);
                    if (!testRun)
                    {
                        File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), moddesc);
                    }
                }
            }

            //int packagesCompressed = 0;
            //if (compressPackages)
            //{
            //    var packages = Utilities.GetPackagesInDirectory(ModPath, true);
            //    extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0));
            //    foreach (var package in packages)
            //    {
            //        updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)));
            //        Log.Information("Compressing package: " + package);
            //        var p = MEPackageHandler.OpenMEPackage(package);
            //        p.save(true);

            //        packagesCompressed++;
            //        extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0));
            //    }
            //}
            if (closeStreamOnFinish)
            {
                archive?.Dispose();
            }
            else
            {
                archive?.DisposeObjectOnly();
            }
        }
        //this should be private but no way to test it private for now...

        /// <summary>
        /// Inspects and loads compressed mods from an archive.
        /// </summary>
        /// <param name="filepath">Path of the archive</param>
        /// <param name="addCompressedModCallback">Callback indicating that the mod should be added to the collection of found mods</param>
        /// <param name="currentOperationTextCallback">Callback to tell caller what's going on'</param>
        /// <param name="forcedOverrideData">Override data about archive. Used for testing only</param>
        public static void InspectArchive(string filepath, Action <Mod> addCompressedModCallback = null, Action <Mod> failedToLoadModeCallback = null, Action <string> currentOperationTextCallback = null,
                                          Action showALOTLauncher = null, string forcedMD5 = null, int forcedSize = -1)
        {
            string     relayVersionResponse = @"-1";
            List <Mod> internalModList      = new List <Mod>(); //internal mod list is for this function only so we don't need a callback to get our list since results are returned immediately
            var        isExe       = filepath.EndsWith(@".exe");
            var        archiveFile = isExe ? new SevenZipExtractor(filepath, InArchiveFormat.Nsis) : new SevenZipExtractor(filepath);

            using (archiveFile)
            {
#if DEBUG
                foreach (var v in archiveFile.ArchiveFileData)
                {
                    Debug.WriteLine($@"{v.FileName} | Index {v.Index} | Size {v.Size} | Last Modified {v.LastWriteTime}");
                }
#endif
                var  moddesciniEntries = new List <ArchiveFileInfo>();
                var  sfarEntries       = new List <ArchiveFileInfo>(); //ME3 DLC
                var  bioengineEntries  = new List <ArchiveFileInfo>(); //ME2 DLC
                var  me2mods           = new List <ArchiveFileInfo>(); //ME2 RCW Mods
                var  textureModEntries = new List <ArchiveFileInfo>(); //TPF MEM MOD files
                bool isAlotFile        = false;
                try
                {
                    foreach (var entry in archiveFile.ArchiveFileData)
                    {
                        if (!entry.IsDirectory)
                        {
                            string fname = Path.GetFileName(entry.FileName);
                            if (fname.Equals(@"ALOTInstaller.exe", StringComparison.InvariantCultureIgnoreCase))
                            {
                                isAlotFile = true;
                            }
                            else if (fname.Equals(@"moddesc.ini", StringComparison.InvariantCultureIgnoreCase))
                            {
                                moddesciniEntries.Add(entry);
                            }
                            else if (fname.Equals(@"Default.sfar", StringComparison.InvariantCultureIgnoreCase))
                            {
                                //for unofficial lookups
                                sfarEntries.Add(entry);
                            }
                            else if (fname.Equals(@"BIOEngine.ini", StringComparison.InvariantCultureIgnoreCase))
                            {
                                //for unofficial lookups
                                bioengineEntries.Add(entry);
                            }
                            else if (Path.GetExtension(fname) == @".me2mod")
                            {
                                me2mods.Add(entry);
                            }
                            else if (Path.GetExtension(fname) == @".mem" || Path.GetExtension(fname) == @".tpf" || Path.GetExtension(fname) == @".mod")
                            {
                                //for forwarding to ALOT Installer
                                textureModEntries.Add(entry);
                            }
                        }
                    }
                }
                catch (SevenZipArchiveException svae)
                {
                    //error reading archive!
                    Mod failed = new Mod(false);
                    failed.ModName          = M3L.GetString(M3L.string_archiveError);
                    failed.LoadFailedReason = M3L.GetString(M3L.string_couldNotInspectArchive7zException);
                    Log.Error($@"Unable to inspect archive {filepath}: SevenZipException occurred! It may be corrupt. The specific error was: {svae.Message}");
                    failedToLoadModeCallback?.Invoke(failed);
                    addCompressedModCallback?.Invoke(failed);
                    return;
                }

                // Used for TPIS information lookup
                long archiveSize = forcedSize > 0 ? forcedSize : new FileInfo(filepath).Length;

                if (moddesciniEntries.Count > 0)
                {
                    foreach (var entry in moddesciniEntries)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        Mod m = new Mod(entry, archiveFile);
                        if (!m.ValidMod)
                        {
                            failedToLoadModeCallback?.Invoke(m);
                            m.SelectedForImport = false;
                        }

                        addCompressedModCallback?.Invoke(m);
                        internalModList.Add(m);
                    }
                }
                else if (me2mods.Count > 0)
                {
                    //found some .me2mod files.
                    foreach (var entry in me2mods)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        MemoryStream ms = new MemoryStream();
                        archiveFile.ExtractFile(entry.Index, ms);
                        ms.Position = 0;
                        StreamReader reader         = new StreamReader(ms);
                        string       text           = reader.ReadToEnd();
                        var          rcwModsForFile = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(entry.FileName), text);
                        foreach (var rcw in rcwModsForFile)
                        {
                            Mod m = new Mod(rcw);
                            addCompressedModCallback?.Invoke(m);
                            internalModList.Add(m);
                        }
                    }
                }
                else if (textureModEntries.Any() && isAlotFile)
                {
                    if (isAlotFile)
                    {
                        //is alot installer
                        Log.Information(@"This file contains texture files and ALOTInstaller.exe - this is an ALOT main file");
                        var textureLibraryPath = Utilities.GetALOTInstallerTextureLibraryDirectory();
                        if (textureLibraryPath != null)
                        {
                            //we have destination
                            var destPath = Path.Combine(textureLibraryPath, Path.GetFileName(filepath));
                            if (!File.Exists(destPath))
                            {
                                Log.Information(M3L.GetString(M3L.string_thisFileIsNotInTheTextureLibraryMovingItToTheTextureLibrary));
                                currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_movingALOTFileToTextureLibraryPleaseWait));
                                archiveFile.Dispose();
                                File.Move(filepath, destPath, true);
                                showALOTLauncher?.Invoke();
                            }
                        }
                    }
                    //todo: Parse
                    //else
                    //{
                    //    //found some texture-mod only files
                    //    foreach (var entry in textureModEntries)
                    //    {
                    //        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                    //        MemoryStream ms = new MemoryStream();
                    //        archiveFile.ExtractFile(entry.Index, ms);
                    //        ms.Position = 0;
                    //        StreamReader reader = new StreamReader(ms);
                    //        string text = reader.ReadToEnd();
                    //        var rcwModsForFile = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(entry.FileName), text);
                    //        foreach (var rcw in rcwModsForFile)
                    //        {
                    //            Mod m = new Mod(rcw);
                    //            addCompressedModCallback?.Invoke(m);
                    //            internalModList.Add(m);
                    //        }
                    //    }
                    //}
                }
                else
                {
                    Log.Information(@"Querying third party importing service for information about this file: " + filepath);
                    currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_queryingThirdPartyImportingService));
                    var md5 = forcedMD5 ?? Utilities.CalculateMD5(filepath);
                    var potentialImportinInfos = ThirdPartyServices.GetImportingInfosBySize(archiveSize);
                    var importingInfo          = potentialImportinInfos.FirstOrDefault(x => x.md5 == md5);

                    if (importingInfo == null && isExe)
                    {
                        Log.Error(@"EXE-based mods must be validated by ME3Tweaks before they can be imported into M3. This is to prevent breaking third party mods.");
                        return;
                    }

                    if (importingInfo?.servermoddescname != null)
                    {
                        //Partially supported unofficial third party mod
                        //Mod has a custom written moddesc.ini stored on ME3Tweaks
                        Log.Information(@"Fetching premade moddesc.ini from ME3Tweaks for this mod archive");
                        string custommoddesc    = null;
                        string loadFailedReason = null;
                        try
                        {
                            custommoddesc = OnlineContent.FetchThirdPartyModdesc(importingInfo.servermoddescname);
                        }
                        catch (Exception e)
                        {
                            loadFailedReason = e.Message;
                            Log.Error(@"Error fetching moddesc from server: " + e.Message);
                        }

                        Mod virutalCustomMod = new Mod(custommoddesc, "", archiveFile); //Load virutal mod
                        if (virutalCustomMod.ValidMod)
                        {
                            Log.Information(@"Mod loaded from server moddesc.");
                            addCompressedModCallback?.Invoke(virutalCustomMod);
                            internalModList.Add(virutalCustomMod);
                            return; //Don't do further parsing as this is custom written
                        }
                        else
                        {
                            if (loadFailedReason != null)
                            {
                                virutalCustomMod.LoadFailedReason = M3L.GetString(M3L.string_interp_failedToFetchModdesciniFileFromServerReasonLoadFailedReason, loadFailedReason);
                            }
                            else
                            {
                                Log.Error(@"Server moddesc was not valid for this mod. This shouldn't occur. Please report to Mgamerz.");
                            }
                            return;
                        }
                    }

                    ExeTransform transform = null;
                    if (importingInfo?.exetransform != null)
                    {
                        Log.Information(@"TPIS lists exe transform for this mod: " + importingInfo.exetransform);
                        transform = new ExeTransform(OnlineContent.FetchExeTransform(importingInfo.exetransform));
                    }

                    //Fully unofficial third party mod.

                    //ME3
                    foreach (var sfarEntry in sfarEntries)
                    {
                        var vMod = AttemptLoadVirtualMod(sfarEntry, archiveFile, Mod.MEGame.ME3, md5);
                        if (vMod != null)
                        {
                            addCompressedModCallback?.Invoke(vMod);
                            internalModList.Add(vMod);
                            vMod.ExeExtractionTransform = transform;
                        }
                    }

                    //TODO: ME2 ?
                    //foreach (var entry in bioengineEntries)
                    //{
                    //    var vMod = AttemptLoadVirtualMod(entry, archiveFile, Mod.MEGame.ME2, md5);
                    //    if (vMod.ValidMod)
                    //    {
                    //        addCompressedModCallback?.Invoke(vMod);
                    //        internalModList.Add(vMod);
                    //    }
                    //}

                    //TODO: ME1 ?

                    if (importingInfo?.version != null)
                    {
                        foreach (Mod compressedMod in internalModList)
                        {
                            compressedMod.ModVersionString = importingInfo.version;
                            Version.TryParse(importingInfo.version, out var parsedValue);
                            compressedMod.ParsedModVersion = parsedValue;
                        }
                    }
                    else if (relayVersionResponse == @"-1")
                    {
                        //If no version information, check ME3Tweaks to see if it's been added recently
                        //see if server has information on version number
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_gettingAdditionalInformationAboutFileFromME3Tweaks));
                        Log.Information(@"Querying ME3Tweaks for additional information for this file...");
                        var modInfo = OnlineContent.QueryModRelay(md5, archiveSize);
                        //todo: make this work offline.
                        if (modInfo != null && modInfo.TryGetValue(@"version", out string value))
                        {
                            Log.Information(@"ME3Tweaks reports version number for this file is: " + value);
                            foreach (Mod compressedMod in internalModList)
                            {
                                compressedMod.ModVersionString = value;
                                Version.TryParse(value, out var parsedValue);
                                compressedMod.ParsedModVersion = parsedValue;
                            }
                            relayVersionResponse = value;
                        }
                        else
                        {
                            Log.Information(@"ME3Tweaks does not have additional version information for this file.");
                            Analytics.TrackEvent(@"Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                            {
                                { @"Filename", Path.GetFileName(filepath) },
                                { @"MD5", md5 }
                            });
                            foreach (Mod compressedMod in internalModList)
                            {
                                compressedMod.ModVersionString = M3L.GetString(M3L.string_unknown);
                            }
                        }
                    }

                    else
                    {
                        //Try straight up TPMI import?
                        Log.Warning($@"No importing information is available for file with hash {md5}. No mods could be found.");
                        Analytics.TrackEvent(@"Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                        {
                            { @"Filename", Path.GetFileName(filepath) },
                            { @"MD5", md5 }
                        });
                    }
                }
            }
        }
        private UploadModResult UploadMod(Action <double> progressCallback = null, Action <TaskbarItemProgressState> setTaskbarProgressState = null)
        {
            #region online fetch

            //Fetch current production manifest for mod (it may not exist)
            setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);
            using var wc = new System.Net.WebClient();
            try
            {
                CurrentActionText = M3L.GetString(M3L.string_checkingIfUpdaterServiceIsConfiguredForMod);
                string validationUrl = $@"{UpdaterServiceCodeValidationEndpoint}?updatecode={mod.ModClassicUpdateCode}&updatexmlname={mod.UpdaterServiceServerFolderShortname}.xml";
                string isBeingServed = wc.DownloadStringAwareOfEncoding(validationUrl);
                if (string.IsNullOrWhiteSpace(isBeingServed) || isBeingServed != @"true") //we don't parse for bool because it might have a different text that is not specifically true or false. It might
                                                                                          // have an error for example
                {
                    //Not being served
                    Log.Error(@"This mod is not configured for serving on the Updater Service. Please contact Mgamerz.");
                    CurrentActionText = M3L.GetString(M3L.string_serverNotConfiguredForModContactMgamerz);
                    HideChangelogArea();
                    return(UploadModResult.NOT_BEING_SERVED);
                }
            }
            catch (Exception ex)
            {
                Log.Error(@"Error validating mod is configured on updater service: " + ex.Message);
                CurrentActionText = M3L.GetString(M3L.string_interp_errorCheckingUpdaterServiceConfiguration, ex.Message);
                HideChangelogArea();
                return(UploadModResult.ERROR_VALIDATING_MOD_IS_CONFIGURED);
            }

            #endregion

            #region get current production version to see if we should prompt user

            var latestVersionOnServer = OnlineContent.GetLatestVersionOfModOnUpdaterService(mod.ModClassicUpdateCode);
            if (latestVersionOnServer != null)
            {
                if (latestVersionOnServer >= mod.ParsedModVersion)
                {
                    bool cancel = false;
                    setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Paused);
                    Application.Current.Dispatcher.Invoke(delegate
                    {
                        // server is newer or same as version we are pushing
                        var response = M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialog_serverVersionSameOrNewerThanLocal, mod.ParsedModVersion, latestVersionOnServer), M3L.GetString(M3L.string_serverVersionSameOrNewerThanLocal), MessageBoxButton.YesNo, MessageBoxImage.Warning);
                        if (response == MessageBoxResult.No)
                        {
                            CurrentActionText = M3L.GetString(M3L.string_uploadAbortedModOnServerIsSameOrNewerThanLocalOneBeingUploaded);
                            HideChangelogArea();
                            cancel = true;
                            return;
                        }
                    });
                    setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);
                    if (cancel)
                    {
                        return(UploadModResult.ABORTED_BY_USER_SAME_VERSION_UPLOADED);
                    }
                }
            }

            #endregion

            #region mod variables

            //get refs
            var files = mod.GetAllRelativeReferences(true);
            files = files.OrderByDescending(x => new FileInfo(Path.Combine(mod.ModPath, x)).Length).ToList();
            long totalModSizeUncompressed = files.Sum(x => new FileInfo(Path.Combine(mod.ModPath, x)).Length);

            #endregion

            #region compress and stage mod

            void updateCurrentTextCallback(string newText)
            {
                CurrentActionText = newText;
            }

            bool?canceledCheckCallback() => CancelOperations;

            CurrentActionText = M3L.GetString(M3L.string_compressingModForUpdaterService);
            progressCallback?.Invoke(0);
            setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Normal);
            var lzmaStagingPath = OnlineContent.StageModForUploadToUpdaterService(mod, files, totalModSizeUncompressed, canceledCheckCallback, updateCurrentTextCallback, progressCallback);

            #endregion

            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);

            #region hash mod and build server manifest

            CurrentActionText = M3L.GetString(M3L.string_buildingServerManifest);

            long amountHashed = 0;
            ConcurrentDictionary <string, SourceFile> manifestFiles = new ConcurrentDictionary <string, SourceFile>();
            Parallel.ForEach(files, new ParallelOptions()
            {
                MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 1)
            }, x =>
            {
                if (CancelOperations)
                {
                    return;
                }
                SourceFile sf       = new SourceFile();
                var sFile           = Path.Combine(mod.ModPath, x);
                var lFile           = Path.Combine(lzmaStagingPath, x + @".lzma");
                sf.hash             = Utilities.CalculateMD5(sFile);
                sf.lzmahash         = Utilities.CalculateMD5(lFile);
                var fileInfo        = new FileInfo(sFile);
                sf.size             = fileInfo.Length;
                sf.timestamp        = fileInfo.LastWriteTimeUtc.Ticks;
                sf.relativefilepath = x;
                sf.lzmasize         = new FileInfo(lFile).Length;
                manifestFiles.TryAdd(x, sf);
                var done          = Interlocked.Add(ref amountHashed, sf.size);
                CurrentActionText = M3L.GetString(M3L.string_buildingServerManifest) + $@" {Math.Round(done * 100.0 / totalModSizeUncompressed)}%";
            });
            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            //Build document
            XmlDocument xmlDoc   = new XmlDocument();
            XmlNode     rootNode = xmlDoc.CreateElement(@"mod");
            xmlDoc.AppendChild(rootNode);

            foreach (var mf in manifestFiles)
            {
                if (CancelOperations)
                {
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                XmlNode sourceNode = xmlDoc.CreateElement(@"sourcefile");

                var size = xmlDoc.CreateAttribute(@"size");
                size.InnerText = mf.Value.size.ToString();

                var hash = xmlDoc.CreateAttribute(@"hash");
                hash.InnerText = mf.Value.hash;

                var lzmasize = xmlDoc.CreateAttribute(@"lzmasize");
                lzmasize.InnerText = mf.Value.lzmasize.ToString();

                var lzmahash = xmlDoc.CreateAttribute(@"lzmahash");
                lzmahash.InnerText = mf.Value.lzmahash;

                var timestamp = xmlDoc.CreateAttribute(@"timestamp");
                timestamp.InnerText = mf.Value.timestamp.ToString();

                sourceNode.InnerText = mf.Key;
                sourceNode.Attributes.Append(size);
                sourceNode.Attributes.Append(hash);
                sourceNode.Attributes.Append(lzmasize);
                sourceNode.Attributes.Append(lzmahash);
                sourceNode.Attributes.Append(timestamp);

                rootNode.AppendChild(sourceNode);
            }

            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            foreach (var bf in mod.UpdaterServiceBlacklistedFiles)
            {
                if (CancelOperations)
                {
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                var bfn = xmlDoc.CreateElement(@"blacklistedfile");
                bfn.InnerText = bf;
                rootNode.AppendChild(bfn);
            }

            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            var updatecode = xmlDoc.CreateAttribute(@"updatecode");
            updatecode.InnerText = mod.ModClassicUpdateCode.ToString();
            rootNode.Attributes.Append(updatecode);

            var version = xmlDoc.CreateAttribute(@"version");
            version.InnerText = mod.ParsedModVersion.ToString();
            rootNode.Attributes.Append(version);

            var serverfolder = xmlDoc.CreateAttribute(@"folder");
            serverfolder.InnerText = mod.UpdaterServiceServerFolder;
            rootNode.Attributes.Append(serverfolder);


            setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);
            #endregion

            //wait to ensure changelog is set.

            while (ChangelogNotYetSet)
            {
                setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Paused);
                if (CancelOperations)
                {
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                CurrentActionText = M3L.GetString(M3L.string_waitingForChangelogToBeSet);
                Thread.Sleep(250); //wait for changelog to be set.
            }

            setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);

            #region Finish building manifest

            var changelog = xmlDoc.CreateAttribute(@"changelog");
            changelog.InnerText = ChangelogText;
            rootNode.Attributes.Append(changelog);

            using var stringWriter = new StringWriterWithEncoding(Encoding.UTF8);
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent         = true;
            settings.IndentChars    = @" ";
            settings.Encoding       = Encoding.UTF8;
            using var xmlTextWriter = XmlWriter.Create(stringWriter, settings);
            xmlDoc.WriteTo(xmlTextWriter);
            xmlTextWriter.Flush();


            #endregion

            var finalManifestText = stringWriter.GetStringBuilder().ToString();

            #region Connect to ME3Tweaks

            CurrentActionText = M3L.GetString(M3L.string_connectingToME3TweaksUpdaterService);
            Log.Information(@"Connecting to ME3Tweaks as " + Username);
            string host     = @"ftp.me3tweaks.com";
            string username = Username;
            string password = Settings.DecryptUpdaterServicePassword();

            using SftpClient sftp = new SftpClient(host, username, password);
            sftp.Connect();

            Log.Information(@"Connected to ME3Tweaks over SSH (SFTP)");

            CurrentActionText = M3L.GetString(M3L.string_connectedToME3TweaksUpdaterService);
            var serverFolderName = mod.UpdaterServiceServerFolderShortname;

            //sftp.ChangeDirectory(LZMAStoragePath);

            //Log.Information(@"Listing files/folders for " + LZMAStoragePath);
            //var lzmaStorageDirectoryItems = sftp.ListDirectory(LZMAStoragePath);
            var  serverModPath  = LZMAStoragePath + @"/" + serverFolderName;
            bool justMadeFolder = false;
            if (!sftp.Exists(serverModPath))
            {
                CurrentActionText = M3L.GetString(M3L.string_creatingServerFolderForMod);
                Log.Information(@"Creating server folder for mod: " + serverModPath);
                sftp.CreateDirectory(serverModPath);
                justMadeFolder = true;
            }

            var dirContents = sftp.ListDirectory(serverModPath).ToList();
            Dictionary <string, string> serverHashes = new Dictionary <string, string>();

            //Open SSH connection as we will need to hash files out afterwards.
            Log.Information(@"Connecting to ME3Tweaks Updater Service over SSH (SSH Shell)");
            using SshClient sshClient = new SshClient(host, username, password);
            sshClient.Connect();
            Log.Information(@"Connected to ME3Tweaks Updater Service over SSH (SSH Shell)");

            if (!justMadeFolder && dirContents.Any(x => x.Name != @"." && x.Name != @".."))
            {
                CurrentActionText = M3L.GetString(M3L.string_hashingFilesOnServerForDelta);
                Log.Information(@"Hashing existing files on server to compare for delta");
                serverHashes = getServerHashes(sshClient, serverFolderName, serverModPath);
            }

            //Calculate what needs to be updated or removed from server
            List <string> filesToUploadToServer  = new List <string>();
            List <string> filesToDeleteOffServer = new List <string>();

            //Files to upload
            foreach (var sourceFile in manifestFiles)
            {
                //find matching server file
                if (serverHashes.TryGetValue(sourceFile.Key.Replace('\\', '/') + @".lzma", out var matchingHash))
                {
                    //exists on server, compare hash
                    if (matchingHash != sourceFile.Value.lzmahash)
                    {
                        //server hash is different! Upload new file.
                        Log.Information(@"Server version of file is different from local: " + sourceFile.Key);
                        filesToUploadToServer.Add(sourceFile.Key);
                    }
                    else
                    {
                        Log.Information(@"Server version of file is same as local: " + sourceFile.Key);
                    }
                }
                else
                {
                    Log.Information(@"Server does not have file: " + sourceFile.Key);
                    filesToUploadToServer.Add(sourceFile.Key);
                }
            }

            //Files to remove from server
            foreach (var serverfile in serverHashes.Keys)
            {
                if (!manifestFiles.Any(x => (x.Key + @".lzma") == serverfile.Replace('/', '\\')))
                {
                    Log.Information(@"File exists on server but not locally: " + serverfile);
                    filesToDeleteOffServer.Add(serverfile);
                }
            }

            #endregion


            long amountUploaded = 0, amountToUpload = 1;
            //Confirm changes
            if (filesToDeleteOffServer.Any() || filesToUploadToServer.Any())
            {
                var text = M3L.GetString(M3L.string_interp_updaterServiceDeltaConfirmationHeader, mod.ModName);
                if (filesToUploadToServer.Any())
                {
                    text += M3L.GetString(M3L.string_nnFilesToUploadToServern) + @" " + string.Join('\n' + @" - ", filesToUploadToServer);                              //weird stuff to deal with localizer
                }
                if (filesToDeleteOffServer.Any())
                {
                    text += M3L.GetString(M3L.string_nnFilesToDeleteOffServern) + @" " + string.Join('\n' + @" - ", filesToDeleteOffServer);                               //weird stuff to deal with localizer
                }
                text += M3L.GetString(M3L.string_interp_updaterServiceDeltaConfirmationFooter);
                bool performUpload = false;
                Log.Information(@"Prompting user to accept server delta");
                setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Paused);
                Application.Current.Dispatcher.Invoke(delegate { performUpload = M3L.ShowDialog(mainwindow, text, M3L.GetString(M3L.string_confirmChanges), MessageBoxButton.OKCancel, MessageBoxImage.Exclamation) == MessageBoxResult.OK; });
                setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);

                if (performUpload)
                {
                    Log.Information(@"User has accepted the delta, applying delta to server");

                    #region upload files

                    //Create directories
                    SortedSet <string> directoriesToCreate = new SortedSet <string>();
                    foreach (var f in filesToUploadToServer)
                    {
                        string foldername = f;
                        var    lastIndex  = foldername.LastIndexOf(@"\");

                        while (lastIndex > 0)
                        {
                            foldername = foldername.Substring(0, lastIndex);
                            directoriesToCreate.Add(foldername.Replace('\\', '/'));
                            lastIndex = foldername.LastIndexOf(@"\");
                        }
                    }

                    #endregion

                    //UploadDirectory(sftp, lzmaStagingPath, serverModPath, (ucb) => Debug.WriteLine("UCB: " + ucb));
                    var dirsToCreateOnServerSorted = directoriesToCreate.ToList();
                    dirsToCreateOnServerSorted.Sort((a, b) => a.Length.CompareTo(b.Length)); //short to longest so we create top levels first!
                    int numFoldersToCreate = dirsToCreateOnServerSorted.Count();
                    int numDone            = 0;
                    if (dirsToCreateOnServerSorted.Count > 0)
                    {
                        CurrentActionText = M3L.GetString(M3L.string_creatingModDirectoriesOnServer);
                        foreach (var f in dirsToCreateOnServerSorted)
                        {
                            var serverFolderStr = serverModPath + @"/" + f;
                            if (!sftp.Exists(serverFolderStr))
                            {
                                Log.Information(@"Creating directory on server: " + serverFolderStr);
                                sftp.CreateDirectory(serverFolderStr);
                            }
                            else
                            {
                                Log.Information(@"Server folder already exists, skipping: " + serverFolderStr);
                            }

                            numDone++;
                            CurrentActionText = M3L.GetString(M3L.string_creatingModDirectoriesOnServer) + @" " + Math.Round(numDone * 100.0 / numFoldersToCreate) + @"%";
                        }
                    }

                    //Upload files
                    progressCallback?.Invoke(0);
                    setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Normal);

                    amountToUpload = filesToUploadToServer.Sum(x => new FileInfo(Path.Combine(lzmaStagingPath, x + @".lzma")).Length);
                    foreach (var file in filesToUploadToServer)
                    {
                        if (CancelOperations)
                        {
                            AbortUpload();
                            return(UploadModResult.ABORTED_BY_USER);
                        }

                        var fullPath       = Path.Combine(lzmaStagingPath, file + @".lzma");
                        var serverFilePath = serverModPath + @"/" + file.Replace(@"\", @"/") + @".lzma";
                        Log.Information(@"Uploading file " + fullPath + @" to " + serverFilePath);
                        long amountUploadedBeforeChunk = amountUploaded;
                        using Stream fileStream = new FileStream(fullPath, FileMode.Open);
                        sftp.UploadFile(fileStream, serverFilePath, true, (x) =>
                        {
                            if (CancelOperations)
                            {
                                CurrentActionText = M3L.GetString(M3L.string_abortingUpload);
                                return;
                            }

                            amountUploaded    = amountUploadedBeforeChunk + (long)x;
                            var uploadedHR    = ByteSize.FromBytes(amountUploaded).ToString(@"0.00");
                            var totalUploadHR = ByteSize.FromBytes(amountToUpload).ToString(@"0.00");
                            if (amountToUpload > 0)
                            {
                                progressCallback?.Invoke(amountUploaded * 1.0 / amountToUpload);
                            }
                            CurrentActionText = M3L.GetString(M3L.string_interp_uploadingFilesToServerXY, uploadedHR, totalUploadHR);
                        });
                    }
                    setTaskbarProgressState?.Invoke(TaskbarItemProgressState.Indeterminate);

                    if (CancelOperations)
                    {
                        AbortUpload();
                        return(UploadModResult.ABORTED_BY_USER);
                    }

                    //delete extra files
                    int numdone = 0;
                    foreach (var file in filesToDeleteOffServer)
                    {
                        CurrentActionText = M3L.GetString(M3L.string_interp_deletingObsoleteFiles, numdone, filesToDeleteOffServer.Count);
                        var fullPath = $@"{LZMAStoragePath}/{serverFolderName}/{file}";
                        Log.Information(@"Deleting unused file off server: " + fullPath);
                        sftp.DeleteFile(fullPath);
                        numdone++;
                    }



                    //Upload manifest
                    using var manifestStream = finalManifestText.ToStream();
                    var serverManifestPath = $@"{ManifestStoragePath}/{serverFolderName}.xml";
                    Log.Information(@"Uploading manifest to server: " + serverManifestPath);
                    sftp.UploadFile(manifestStream, serverManifestPath, true, (x) =>
                    {
                        var uploadedAmountHR    = ByteSize.FromBytes(amountUploaded).ToString(@"0.00");
                        var uploadAmountTotalHR = ByteSize.FromBytes(amountToUpload).ToString(@"0.00");
                        CurrentActionText       = M3L.GetString(M3L.string_uploadingUpdateManifestToServer) + $@"{uploadedAmountHR}/{uploadAmountTotalHR}";
                    });
                }
                else
                {
                    Log.Warning(@"User has declined uploading the delta. We will not change anything on the server.");
                    CancelOperations = true;
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                CurrentActionText = M3L.GetString(M3L.string_validatingModOnServer);
                Log.Information(@"Verifying hashes on server for new files");
                var newServerhashes = getServerHashes(sshClient, serverFolderName, serverModPath);
                var badHashes       = verifyHashes(manifestFiles, newServerhashes);
                if (badHashes.Any())
                {
                    CurrentActionText = M3L.GetString(M3L.string_someHashesOnServerAreIncorrectContactMgamerz);
                    return(UploadModResult.BAD_SERVER_HASHES_AFTER_VALIDATION);
                }
                else
                {
                    CurrentActionText = M3L.GetString(M3L.string_modUploadedToUpdaterService);
                    return(UploadModResult.UPLOAD_OK);
                }
            }

            return(UploadModResult.ABORTED_BY_USER);
        }
        private void StartCompiler()
        {
            CompileInProgress = true;
            Settings.Save(); //Persist controller mixin option, keybinds injection
            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ModmakerCompiler");

            nbw.DoWork += (a, b) =>
            {
                string modDelta = null;

                if (int.TryParse(ModMakerCode, out var code))
                {
                    DownloadAndModNameText = @"Downloading mod delta from ME3Tweaks";
                    var normalEndpoint = OnlineContent.ModmakerModsEndpoint + code;
                    var lzmaEndpoint   = normalEndpoint + @"&method=lzma";


                    //Try LZMA first
                    try
                    {
                        var download = OnlineContent.DownloadToMemory(lzmaEndpoint, (done, total) =>
                        {
                            if (total != -1)
                            {
                                var suffix             = $@" {(done * 100.0 / total).ToString(@"0")}%"; //do not localize
                                DownloadAndModNameText = M3L.GetString(M3L.string_downloadingModDeltaFromME3Tweaks) + suffix;
                            }
                            else
                            {
                                DownloadAndModNameText = M3L.GetString(M3L.string_downloadingModDeltaFromME3Tweaks);
                            }
                        });
                        if (download.errorMessage == null)
                        {
                            DownloadAndModNameText = M3L.GetString(M3L.string_decompressingDelta);
                            // OK
                            var decompressed = SevenZipHelper.LZMA.DecompressLZMAFile(download.result.ToArray());
                            modDelta = Encoding.UTF8.GetString(decompressed);
                            // File.WriteAllText(@"C:\users\mgamerz\desktop\decomp.txt", modDelta);
                        }
                        else
                        {
                            Log.Error(@"Error downloading lzma mod delta to memory: " + download.errorMessage);
                        }
                    }
                    catch (Exception e)
                    {
                        Log.Error(@"Error downloading LZMA mod delta to memory: " + e.Message);
                    }

                    if (modDelta == null)
                    {
                        //failed to download LZMA.
                        var download = OnlineContent.DownloadToMemory(normalEndpoint, (done, total) =>
                        {
                            var suffix             = $" {(done * 100.0 / total).ToString(@"0")}%"; //do not localize
                            DownloadAndModNameText = M3L.GetString(M3L.string_downloadingModDeltaFromME3Tweaks) + suffix;
                        });
                        if (download.errorMessage == null)
                        {
                            //OK
                            modDelta = Encoding.UTF8.GetString(download.result.ToArray());
                        }
                        else
                        {
                            Log.Error(@"Error downloading decompressed mod delta to memory: " + download.errorMessage);
                        }
                    }
                }
                else if (File.Exists(LocalFilePath))
                {
                    modDelta = File.ReadAllText(LocalFilePath);
                }


                if (modDelta != null)
                {
                    KeepOpenWhenThreadFinishes = false;
                    var compiler = new ModMakerCompiler(code);
                    compiler.SetCurrentMaxCallback               = SetCurrentMax;
                    compiler.SetCurrentValueCallback             = SetCurrentProgressValue;
                    compiler.SetOverallMaxCallback               = SetOverallMax;
                    compiler.SetOverallValueCallback             = SetOverallValue;
                    compiler.SetCurrentTaskIndeterminateCallback = SetCurrentTaskIndeterminate;
                    compiler.SetCurrentTaskStringCallback        = SetCurrentTaskString;
                    compiler.SetModNameCallback     = SetModNameOrDownloadText;
                    compiler.SetCompileStarted      = CompilationInProgress;
                    compiler.SetModNotFoundCallback = ModNotFound;
                    compiler.NotifySomeDLCIsMissing = NotifySomeDLCIsMissing;
                    Mod m = compiler.DownloadAndCompileMod(modDelta);
                    if (m != null && !LocalFileOption)
                    {
                        var sanitizedname = Utilities.SanitizePath(m.ModName);
                        File.WriteAllText(Path.Combine(Utilities.GetModmakerDefinitionsCache(), $@"{code}-{sanitizedname}.xml"), modDelta);
                    }
                    b.Result = m;
                }
            };
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occured in {nbw.Name} thread: {b.Error.Message}");
                }
                CompileInProgress = false;
                if (!KeepOpenWhenThreadFinishes && b.Result is Mod m)
                {
                    OnClosing(new DataEventArgs(m));
                }
                else
                {
                    CloseProgressPanel();
                    ShowCloseButton = true;
                }
                CommandManager.InvalidateRequerySuggested();
            };
            nbw.RunWorkerAsync();
        }