public void ApplyPatches(List<PatchDefinition> patchDefinitions) { _appliedPatchInfos = new List<AppliedPatchInfo>(); //process this when saving the result foreach (var patchDefinition in patchDefinitions) { RemovePatches( new List<Tuple<PatchDefinition, AppliedPatchInfo>>() { new Tuple<PatchDefinition, AppliedPatchInfo>( patchDefinition, _appliedPatchInfos.FirstOrDefault(a => a.Name == patchDefinition.Name)) } ); //check if it has already been applied and remove beforehand var newlyAppliedPatch = new AppliedPatchInfo(patchDefinition.Name, patchDefinition.Version); _appliedPatchInfos.Add(newlyAppliedPatch); foreach (var variable in patchDefinition.Variables) //TODO: remove variable { var targetMethod = CodeHelper.ResolveMethodDefinition(_assembly.MainModule, variable.TargetMethodName); var targetType = TypeParser.ParseType(_assembly.MainModule, variable.Type); if (targetType == null) { throw new ApplicationException(string.Format("Type {0} couldn't be resolved for variable {1}", variable.TargetMethodName, variable.Name)); } if (targetMethod == null) { throw new ApplicationException( string.Format("Method {0} couldn't be resolved for variable {1}", variable.TargetMethodName, variable.Name)); } targetMethod.Body.Variables.Add( new VariableDefinition(variable.Name, targetType)); } foreach (var codeBlock in patchDefinition.CodeBlocks) { var codeBlockInfo = new AppliedCodeBlock() { InsertPos = codeBlock.InsertOffset, MethodName = codeBlock.TargetMethodName, LinesCount = codeBlock.Lines.Count(), OriginalLinesCount = codeBlock.TargetMethodInstructionCount }; newlyAppliedPatch.AppliedCodeBlocks.Add(codeBlockInfo); var typeName = codeBlock.TargetMethodName.Split(new[] { " ", "::" }, 3, StringSplitOptions.RemoveEmptyEntries)[1]; var type = _assembly.MainModule.Types.FirstOrDefault(t => t.FullName == typeName); if (type == null) { throw new ApplicationException(string.Format("type {0} not found in assembly {1}", typeName, _assemblyPath)); } var method = type.Methods.FirstOrDefault(m => m.FullName == codeBlock.TargetMethodName); if (method == null) { throw new ApplicationException(string.Format("method {0} not found in assembly: {1}", codeBlock.TargetMethodName, _assemblyPath)); } if (codeBlock.TargetMethodInstructionCount < 0) { codeBlockInfo.OriginalLinesCount = method.Body.Instructions.Count; } //var processor = method.Body.GetILProcessor(); var insertAt = method.Body.Instructions.Select((i, idx) => new Tuple<Instruction, int>(i, idx)).FirstOrDefault( it => it.Item1.Offset >= codeBlock.InsertOffset ); var insertIdx = insertAt != null ? insertAt.Item2 : 0; if (codeBlock.InsertOffset > 0 && insertIdx == 0) { Debug.WriteLine("something went wrong. ipos is:{0}", codeBlock.InsertOffset); } //Debug.Assert(codeBlock.InsertOffset != 163); if (codeBlock.TargetMethodInstructionCount > 0 && codeBlock.TargetMethodInstructionCount != method.Body.Instructions.Count) { throw new ApplicationException(string.Format("Instruction count({0}) in the target method {1} does not match the expected count {2}", method.Body.Instructions.Count, codeBlock.TargetMethodName, codeBlock.TargetMethodInstructionCount)); } codeBlockInfo.InsertPos = insertIdx; foreach (var code in codeBlock.Lines) { var i = code.Create(method.Body, _assembly.MainModule); method.Body.Instructions.Insert(insertIdx++, i); } } //UI patches foreach (var uiPatch in patchDefinition.UIPatches) //TODO: too phat, move out { var uiPatcher = new UIDataLoader(); var targetPath = Path.Combine(_gameRoot, uiPatch.FilePath); var doc = uiPatcher.FixAndLoadXml(targetPath); var uiPatchBackupNodes = doc.XPathSelectElements(string.Format("//SotsosBackup/UIPatchBackup[@PatchName='{0}']", patchDefinition.Name)); var installed = uiPatchBackupNodes.Select(xElement => xElement.Attributes("XPath").FirstOrDefault()).Any(xpathAtt => xpathAtt.Value == uiPatch.XPath); if (installed) //already installed, skip { continue; } var targetNode = doc.XPathSelectElement(uiPatch.XPath); var xmlNodeReader = new XmlNodeReader(uiPatch.Data); var newNode = XElement.Load(xmlNodeReader).Descendants().First(); var sotsosBackupNode = doc.XPathSelectElement("//SotsosBackup"); if (sotsosBackupNode == null) { sotsosBackupNode = new XElement("SotsosBackup"); doc.Root.DescendantNodes().Last().AddAfterSelf(sotsosBackupNode); } var uiPatchBackupNode = new XElement("UIPatchBackup"); uiPatchBackupNode.SetAttributeValue("XPath", uiPatch.XPath); uiPatchBackupNode.SetAttributeValue("PatchMode", uiPatch.PatchMode); uiPatchBackupNode.SetAttributeValue("PatchName", patchDefinition.Name); uiPatchBackupNode.SetAttributeValue("PatchVersion", patchDefinition.Version); sotsosBackupNode.Add(uiPatchBackupNode); if (uiPatch.PatchMode == UIPatchMode.Replace) { var dataNode = new XElement("Data"); uiPatchBackupNode.Add(dataNode); var parent = targetNode.Parent ?? doc.Root; var prevNode = targetNode.PreviousNode; targetNode.Remove(); dataNode.Add(targetNode); if (prevNode != null) { prevNode.AddAfterSelf(newNode); } else { parent.Add(newNode); //allow the nullref to flow up } } else if (uiPatch.PatchMode == UIPatchMode.InsertAfter) { uiPatchBackupNode.SetAttributeValue("RemoveXPath", uiPatch.RemoveXPath); targetNode.AddAfterSelf(newNode); } else if (uiPatch.PatchMode == UIPatchMode.InsertBefore) { uiPatchBackupNode.SetAttributeValue("RemoveXPath", uiPatch.RemoveXPath); targetNode.AddBeforeSelf(newNode); } using (var writer = XmlWriter.Create(targetPath, new XmlWriterSettings() { Indent = true })) { doc.WriteTo(writer); } } } }
public void RemovePatches(List<Tuple<PatchDefinition, AppliedPatchInfo>> toBeRemoved ) { if (_currentActivePatchInfos != null && _currentActivePatchInfos.Count > 0) { foreach (var patchToBeRemoved in toBeRemoved) { var patchInfo = patchToBeRemoved.Item2; //_currentActivePatchInfos.FirstOrDefault(pi => pi.Name == patchInfo); var patchDefinition = patchToBeRemoved.Item1; if (patchInfo != null) { var appliedBlocks = new List<AppliedCodeBlock>(patchInfo.AppliedCodeBlocks); appliedBlocks.Reverse(); foreach (var cb in appliedBlocks) { var typeName =cb.MethodName.Split(new[] { " ", "::" }, 3, StringSplitOptions.RemoveEmptyEntries)[1]; var type = _assembly.MainModule.Types.FirstOrDefault(t => t.FullName == typeName); var method = type.Methods.FirstOrDefault(m => m.FullName == cb.MethodName); // method.Body.Variables.Add(new VariableDefinition("ret","System.String")); if (method.Body.Instructions.Count() != cb.LinesCount + cb.OriginalLinesCount) { throw new ApplicationException(string.Format("Invalid patch state for code block {0}. Can't remove.", cb.Src)); } var insertIdx = cb.InsertPos; for (var p = 0; p < cb.LinesCount; p++) { method.Body.Instructions.RemoveAt(insertIdx); //check if it keeps the offsets intact } if (method.Body.Instructions.Count() != cb.OriginalLinesCount) { throw new ApplicationException(string.Format("Invalid patch state for code block {0}. Can't remove.", cb.Src)); } } _currentActivePatchInfos.Remove(patchInfo); _assembly.CustomAttributes.Remove(patchInfo.Attr); } //remove ui parts if (patchDefinition != null) { if (patchDefinition.Variables != null && patchDefinition.Variables.Count > 0) { foreach (var variable in patchDefinition.Variables) { var targetMethod = CodeHelper.ResolveMethodDefinition(_assembly.MainModule, variable.TargetMethodName); var reverseVariables = new List<VariableDefinition>(targetMethod.Body.Variables); reverseVariables.Reverse(); var findVar = reverseVariables.FirstOrDefault(v => v.VariableType.FullName == variable.Type); if (findVar != null) { targetMethod.Body.Variables.Remove(findVar); //remove last variable with given type } } } foreach (var uiPatch in patchDefinition.UIPatches) //TODO: load from assembly info instead of existing patches for better maint. { var uiPatcher = new UIDataLoader(); var targetPath = Path.Combine(_gameRoot, uiPatch.FilePath); var doc = uiPatcher.FixAndLoadXml(targetPath); var sotsosBackupNodes = doc.XPathSelectElements(string.Format("//SotsosBackup/UIPatchBackup[@PatchName='{0}']", patchDefinition.Name)); if (sotsosBackupNodes.Any()) { foreach (var backupNode in sotsosBackupNodes) { var patchMode = UIPatchMode.Replace; Enum.TryParse(backupNode.Attribute("PatchMode").Value, true, out patchMode); switch (patchMode) { case UIPatchMode.Replace: { var xpath = backupNode.Attribute("XPath").Value; var dataNode = backupNode.Descendants("Data").FirstOrDefault(); if (dataNode != null && dataNode.Elements().Count() == 1) { var restoreLocation = doc.XPathSelectElement(xpath); if (restoreLocation != null) { restoreLocation.AddAfterSelf(dataNode.Elements().First()); restoreLocation.Remove(); } } break; } case UIPatchMode.InsertBefore: case UIPatchMode.InsertAfter: { var xpath = backupNode.Attribute("RemoveXPath").Value; var nodeToRemove = doc.XPathSelectElement(xpath); if (nodeToRemove != null) { nodeToRemove.Remove(); } break; } } backupNode.Remove(); } } using (var writer = XmlWriter.Create(targetPath, new XmlWriterSettings(){Indent = true})) { doc.WriteTo(writer); } } } } } }