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++;
}
}
}