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); }
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}"); } } } } } }
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); }
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)); }); }
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(); }
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"); } }
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}"); } } } }
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); }); }
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(); }
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); }); }
/// <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); }
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"); } }
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!"); }
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."); } }
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); } } } }
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 } }); } } } }
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)); // } //} } }
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(); }
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(); }
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(); }
/// <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(); }