public async Task <XmlHistory> PatchAsync(LaunchType launchType) { var history = new XmlHistory(); history.Success = false; var totalProgress = new ProgressObject(); var logForm = new LogForm(totalProgress); logForm.InvokeIfRequired(() => logForm.Show()); try { List <PatchGroup> patches = PatchManager.SaveInstructions(ref history); await Task.Run(() => PatchManager.ApplyInstructions(launchType, patches, totalProgress)).ConfigureAwait(false); history.Success = true; } catch (Exception exception) { Logger.Error(exception, "Patching failed because instructions cannot be applied."); } if (!history.Success) { PatchingHelper.RestorePatchedFiles(AppContextManager.Context.Value, history.Files); } logForm.InvokeIfRequired(() => CloseForm(logForm)); return(history); }
public async Task LaunchTestRunAsync(LaunchType launchType = LaunchType.Test) { StartUnlockDialog(); SetState(LaunchManagerState.IsPatching); XmlHistory history = await this.PatchAsync(launchType).ConfigureAwait(false); SetState(LaunchManagerState.Idle); PatchingHelper.RestorePatchedFiles(AppContextManager.Context.Value, history.Files); if (!PreferencesManager.OpenLogAfterPatch) { return; } try { string logPath = LogManager.Logs.First(kvp => kvp.Key == "PatchManager").Value; TryOpenTextFile(logPath); } catch (Exception exception) { Logger.Error(exception, "Cannot open log file after test run"); } }
public async Task LaunchTestRunAsync(LaunchType launchType = LaunchType.Test) { MainWindow.Enabled = false; new Thread(o => AskUnlockGUI()).Start(); State.Value = LaunchManagerState.IsPatching; XmlHistory history = await this.PatchAsync(launchType).ConfigureAwait(false); State.Value = LaunchManagerState.Idle; PatchingHelper.RestorePatchedFiles(AppContextManager.Context.Value, history.Files); if (PreferencesManager.OpenLogAfterPatch) { try { string logPath = LogManager.Logs.First(kvp => kvp.Key == "PatchManager").Value; TryOpenTextFile(logPath); } catch (Exception exception) { Logger.Error(exception, "Cannot open log file after test run"); } } }
public static void RestorePatchedFiles() { if (History != null) { Logger.Debug("Trying to restore patched files because we have History..."); PatchingHelper.RestorePatchedFiles(AppContextManager.Context.Value, History.Files); } }
public async Task <XmlHistory> Command_Patch() { State.Value = LaunchManagerState.IsPatching; var history = new XmlHistory { Success = false }; try { var progObj = new ProgressObject(); using (var logForm = new LogForm(progObj) { Icon = _home.Icon }) { logForm.Show(); try { var patches = GroupPatches(Instructions).ToList(); history.Files = patches.Select(XmlFileHistory.FromInstrGroup).ToList(); _historySerializer.Serialize(history, _pathHistoryXml); await Task.Run(() => ApplyInstructions(patches, progObj)); history.Success = true; } catch (PatchingProcessException ex) { Command_Display_Patching_Error(ex); } if (!history.Success) { PatchingHelper.RestorePatchedFiles(AppInfo, history.Files); } logForm.Close(); } } catch (Exception ex) { Command_Display_Error("Patch the game", ex: ex); } finally { State.Value = LaunchManagerState.Idle; if (Preferences.OpenLogAfterPatch) { Process.Start(_pathLogFile); } } return(history); }
public async void Command_Launch_Modded() { Action <IBindable <LaunchManagerState> > p = null; var history = await Command_Patch(); p = v => { if (v.Value == LaunchManagerState.Idle) { PatchingHelper.RestorePatchedFiles(AppInfo, history.Files); State.HasChanged -= p; } }; State.HasChanged += p; Command_Launch(); }
/// <exception cref="T:PatchworkLauncher.PatchingProcessException">Cannot switch files safely during the patching process</exception> public static void TrySwitchFilesSafely(string sourcePath, string destinationPath, string backupPath, PatchGroup patchGroup) { try { PatchingHelper.SwitchFilesSafely(sourcePath, destinationPath, backupPath); } catch (Exception exception) { throw new PatchingProcessException(exception) { AssociatedInstruction = null, AssociatedPatchGroup = patchGroup, Step = PatchProcessingStep.PerformingSwitch }; } }
private void ApplyInstructions(IEnumerable <PatchGroup> patchGroups, ProgressObject po) { //TODO: Use a different progress tracking system and make the entire patching operation more recoverable and fault-tolerant. //TODO: Refactor this method. patchGroups = patchGroups.ToList(); var appInfo = AppInfo; var logger = Logger; var fileProgress = new ProgressObject(); po.Child.Value = fileProgress; var patchProgress = new ProgressObject(); fileProgress.Child.Value = patchProgress; var myAttributesAssembly = typeof(AppInfo).Assembly; var attributesAssemblyName = Path.GetFileName(myAttributesAssembly.Location); var history = new List <XmlFileHistory>(); po.TaskTitle.Value = "Patching Game"; po.TaskText.Value = appInfo.AppName; po.Total.Value = patchGroups.Count(); foreach (var patchGroup in patchGroups) { var patchCount = patchGroup.Instructions.Count; po.TaskTitle.Value = $"Patching {appInfo.AppName}"; var targetFile = patchGroup.TargetPath; po.TaskText.Value = Path.GetFileName(targetFile); //Note that Path.Combine(FILENAME, "..", OTHER_FILENAME) doesn't work on Mono but does work on .NET. var dir = Path.GetDirectoryName(targetFile); var localAssemblyName = Path.Combine(dir, attributesAssemblyName); var copy = true; fileProgress.TaskTitle.Value = "Patching File"; fileProgress.Total.Value = 2 + patchCount; fileProgress.Current.Value++; var backupModified = PatchingHelper.GetBackupForModified(targetFile); var backupOrig = PatchingHelper.GetBackupForOriginal(targetFile); fileProgress.TaskText.Value = "Applying Patch"; if (!PatchingHelper.DoesFileMatchPatchList(backupModified, targetFile, patchGroup.Instructions) || Preferences.AlwaysPatch) { if (File.Exists(localAssemblyName)) { try { var localAssembly = AssemblyCache.Default.ReadAssembly(localAssemblyName); if (localAssembly.GetAssemblyMetadataString() == myAttributesAssembly.GetAssemblyMetadataString()) { copy = false; } } catch (Exception ex) { Logger.Warning(ex, $"Failed to read local attributes assembly so it will be overwritten."); //if reading the assembly failed for any reason, just ignore... } } if (copy) { File.Copy(myAttributesAssembly.Location, localAssemblyName, true); } var patcher = new AssemblyPatcher(targetFile, logger) { EmbedHistory = true }; foreach (var patch in patchGroup.Instructions) { try { patcher.PatchManifest(patch.Patch, patchProgress.ToMonitor()); } catch (PatchException ex) { throw new PatchingProcessException(ex) { AssociatedInstruction = patch, AssociatedPatchGroup = patchGroup, Step = PatchProcessingStep.ApplyingSpecificPatch }; } fileProgress.Current.Value++; } patchProgress.TaskText.Value = ""; patchProgress.TaskTitle.Value = ""; fileProgress.Current.Value++; fileProgress.TaskText.Value = "Writing Assembly"; if (Environment.OSVersion.Platform == PlatformID.Win32NT) { fileProgress.TaskText.Value = "Running PEVerify..."; var targetFolder = Path.GetDirectoryName(targetFile); try { var peOutput = patcher.RunPeVerify(new PEVerifyInput { AssemblyResolutionFolder = targetFolder, IgnoreErrors = AppInfo.IgnorePEVerifyErrors.ToList() }); logger.Information(peOutput.Output); } catch (Exception ex) { logger.Error(ex, "Failed to run PEVerify on the assembly."); } } try { patcher.WriteTo(backupModified); } catch (Exception ex) { throw new PatchingProcessException(ex) { AssociatedInstruction = null, AssociatedPatchGroup = patchGroup, Step = PatchProcessingStep.WritingToFile }; } } else { fileProgress.Current.Value += patchCount; } try { PatchingHelper.SwitchFilesSafely(backupModified, targetFile, backupOrig); } catch (Exception ex) { throw new PatchingProcessException(ex) { AssociatedInstruction = null, AssociatedPatchGroup = patchGroup, Step = PatchProcessingStep.PerformingSwitch }; } AssemblyCache.Default.ClearCache(); po.Current.Value++; } }
public async void Command_TestRun() { var history = await Command_Patch(); PatchingHelper.RestorePatchedFiles(AppInfo, history.Files); }
public LaunchManager() { //the following is needed on linux... the current directory must be the Mono executable, which is bad. Environment.CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); try { FormIcon = Icon.FromHandle(Resources.IconSmall.GetHicon()); if (File.Exists(_pathLogFile)) { File.Delete(_pathLogFile); } XmlPreferences prefs = new XmlPreferences(); try { prefs = _prefsSerializer.Deserialize(_pathPrefsFile, new XmlPreferences()); } catch (Exception ex) { Command_Display_Error("Read preferences file", _pathPrefsFile, ex, "Special preferences will be reset"); } Preferences = prefs; Logger = new LoggerConfiguration().WriteTo.File(_pathLogFile, LogEventLevel.Verbose).MinimumLevel.Is(Preferences.MinimumEventLevel).CreateLogger(); AppInfoFactory gameInfoFactory; gameInfoFactory = !File.Exists(_pathGameInfoAssembly) ? null : PatchingHelper.LoadAppInfoFactory(_pathGameInfoAssembly); var settings = new XmlSettings(); var history = new XmlHistory(); ; try { history = _historySerializer.Deserialize(_pathHistoryXml, new XmlHistory()); } catch (Exception ex) { Command_Display_Error("Load patching history", _pathHistoryXml, ex, "If the launcher was terminated unexpectedly last time, it may not be able to recover."); } try { settings = _settingsSerializer.Deserialize(_pathSettings, new XmlSettings()); } catch (Exception ex) { Command_Display_Error("Read settings file", _pathSettings, ex, "Patch list and other settings will be reset."); } string folderDialogReason = null; if (settings.BaseFolder == null) { folderDialogReason = "(no game folder has been specified)"; } else if (!Directory.Exists(settings.BaseFolder)) { folderDialogReason = "(the previous game folder does not exist)"; } if (folderDialogReason != null) { if (!Command_SetGameFolder_Dialog(folderDialogReason)) { Command_ExitApplication(); } } else { BaseFolder = settings.BaseFolder; } _home = new guiHome(this) { Icon = FormIcon }; var defaultAppInfo = new AppInfo() { AppName = "No AppInfo.dll", }; AppInfo = gameInfoFactory?.CreateInfo(new DirectoryInfo(BaseFolder)) ?? defaultAppInfo; AppInfo.AppVersion = AppInfo.AppVersion ?? "version??"; var icon = TryOpenIcon(AppInfo.IconLocation) ?? _home.Icon.ToBitmap(); ProgramIcon = icon; Instructions = new DisposingBindingList <PatchInstruction>(); var instructions = new List <XmlInstruction>(); foreach (var xmlPatch in settings.Instructions) { try { Command_Direct_AddPatch(xmlPatch.PatchPath, xmlPatch.IsEnabled); } catch { instructions.Add(xmlPatch); } } var patchList = instructions.Select(x => x.PatchPath).Join(Environment.NewLine); if (patchList.Length > 0) { Command_Display_Error("Load patches on startup.", patchList); } try { PatchingHelper.RestorePatchedFiles(AppInfo, history.Files); } catch (Exception ex) { Command_Display_Error("Restore files", ex: ex); } //File.Delete(_pathHistoryXml); _home.Closed += (sender, args) => Command_ExitApplication(); _icon = new NotifyIcon { Icon = FormIcon, Visible = false, Text = "Patchwork Launcher", ContextMenu = new ContextMenu { MenuItems = { new MenuItem("Quit", (o, e) => Command_ExitApplication()) } } }; File.Delete(_pathHistoryXml); } catch (Exception ex) { Command_Display_Error("Launch the application", ex: ex, message: "The application will now exit."); Command_ExitApplication(); } }
private static AppInfoFactory GetFactory(string filePath) { return(File.Exists(filePath) ? PatchingHelper.LoadAppInfoFactory(filePath) : null); }
public static void ApplyInstructions(LaunchType launchType, List <PatchGroup> patchGroups, ProgressObject totalProgress) { //TODO: Use a different progress tracking system and make the entire patching operation more recoverable and fault-tolerant. //TODO: Refactor this method. AppInfo appInfo = AppContextManager.Context.Value; Assembly myAttributesAssembly = typeof(AppInfo).Assembly; string myAttributesAssemblyName = Path.GetFileName(myAttributesAssembly.Location); var fileProgress = new ProgressObject(); var patchProgress = new ProgressObject(); totalProgress.Child.Value = fileProgress; fileProgress.Child.Value = patchProgress; totalProgress.SetTaskData("Patching Game", appInfo.AppName, patchGroups.Count); foreach (PatchGroup patchGroup in patchGroups) { int patchCount = patchGroup.Instructions.Count; string destinationPath = patchGroup.TargetPath; var patcher = new AssemblyPatcher(destinationPath, Logger); patcher.EmbedHistory = true; string sourcePath = PatchingHelper.GetBackupForModified(destinationPath); string backupPath = PatchingHelper.GetBackupForOriginal(destinationPath); // note that Path.Combine(FILENAME, "..", OTHER_FILENAME) doesn't work on Mono but does work on .NET. string targetDirectory = Path.GetDirectoryName(destinationPath); string localAssemblyName = Path.Combine(targetDirectory, myAttributesAssemblyName); totalProgress.SetTaskData(string.Format("Patching {0}", appInfo.AppName), Path.GetFileName(destinationPath)); fileProgress.SetTaskData("Patching File", total: 2 + patchCount, increment: true); fileProgress.SetTaskData(taskText: "Applying Patch"); if (!PatchingHelper.DoesFileMatchPatchList(sourcePath, destinationPath, patchGroup.Instructions) || PreferencesManager.Preferences.AlwaysPatch) { try { myAttributesAssembly.TryCopyAttributesAssembly(localAssemblyName); } catch (Exception exception) { Logger.Warning(exception, "Failed to read local attributes assembly so it will be overwritten."); } foreach (PatchInstruction patch in patchGroup.Instructions) { try { patcher.TryPatchManifest(patch, patchGroup, patchProgress); } catch (PatchingProcessException exception) { Logger.Show(exception); } fileProgress.SetTaskData(increment: true); } patchProgress.SetTaskData(string.Empty, string.Empty); fileProgress.SetTaskData(taskText: "Writing Assembly", increment: true); if (launchType == LaunchType.Test && Environment.OSVersion.Platform == PlatformID.Win32NT) { fileProgress.SetTaskData(taskText: "Running PEVerify"); string peOutput = patcher.TryRunPeVerify(appInfo, destinationPath); Logger.Information(peOutput); } try { patcher.TryBackup(sourcePath, patchGroup); } catch (PatchingProcessException exception) { Logger.Show(exception); } } else { fileProgress.Current.Value += patchCount; } try { LaunchManager.TrySwitchFilesSafely(sourcePath, destinationPath, backupPath, patchGroup); } catch (PatchingProcessException exception) { Logger.Show(exception); } AssemblyCache.Default.ClearCache(); totalProgress.SetTaskData(increment: true); } }