using dnlib.DotNet; using Obfuz.Utils; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Xml; using System.Xml.Linq; using UnityEngine; namespace Obfuz.ObfusPasses.SymbolObfus { public class RenameRecordMap { private enum RenameStatus { NotRenamed, Renamed, } private class RenameRecord { public RenameStatus status; public string signature; public string oldName; public string newName; public string oldStackTraceSignature; // only for MethodDef public object renameMappingData; } private class RenameMappingField { public RenameStatus status; public string signature; public string newName; } private class RenameMappingMethod { public RenameStatus status; public string signature; public string newName; } private class RenameMappingMethodParam { public RenameStatus status; public int index; public string newName; } private class RenameMappingProperty { public RenameStatus status; public string signature; public string newName; } private class RenameMappingEvent { public RenameStatus status; public string signature; public string newName; } private class RenameMappingType { public RenameStatus status; public string oldFullName; public string newFullName; public Dictionary fields = new Dictionary(); public Dictionary methods = new Dictionary(); public Dictionary properties = new Dictionary(); public Dictionary events = new Dictionary(); } private class RenameMappingAssembly { public string assName; public Dictionary types = new Dictionary(); } private readonly string _mappingFile; private readonly Dictionary _assemblies = new Dictionary(); private readonly Dictionary _modRenames = new Dictionary(); private readonly Dictionary _typeRenames = new Dictionary(); private readonly Dictionary _methodRenames = new Dictionary(); private readonly Dictionary _paramRenames = new Dictionary(); private readonly Dictionary _fieldRenames = new Dictionary(); private readonly Dictionary _propertyRenames = new Dictionary(); private readonly Dictionary _eventRenames = new Dictionary(); private readonly Dictionary _virtualMethodGroups = new Dictionary(); public RenameRecordMap(string mappingFile) { _mappingFile = mappingFile; } public void Init(List assemblies, INameMaker nameMaker) { LoadXmlMappingFile(_mappingFile); foreach (ModuleDef mod in assemblies) { string name = mod.Assembly.Name; RenameMappingAssembly rma = _assemblies.GetValueOrDefault(name); _modRenames.Add(mod, new RenameRecord { status = RenameStatus.NotRenamed, signature = name, oldName = name, newName = null, renameMappingData = rma, }); foreach (TypeDef type in mod.GetTypes()) { nameMaker.AddPreservedName(type, name); nameMaker.AddPreservedNamespace(type, type.Namespace); string fullTypeName = type.FullName; RenameMappingType rmt = rma?.types.GetValueOrDefault(fullTypeName); if (rmt != null) { var (newNamespace, newName) = MetaUtil.SplitNamespaceAndName(rmt.newFullName); nameMaker.AddPreservedNamespace(type, newNamespace); nameMaker.AddPreservedName(type, newName); } _typeRenames.Add(type, new RenameRecord { status = RenameStatus.NotRenamed, signature = fullTypeName, oldName = fullTypeName, newName = null, renameMappingData = rmt, }); foreach (MethodDef method in type.Methods) { nameMaker.AddPreservedName(method, method.Name); string methodSig = TypeSigUtil.ComputeMethodDefSignature(method); nameMaker.AddPreservedName(method, method.Name); RenameMappingMethod rmm = rmt?.methods.GetValueOrDefault(methodSig); if (rmm != null) { nameMaker.AddPreservedName(method, rmm.newName); } _methodRenames.Add(method, new RenameRecord { status = RenameStatus.NotRenamed, signature = methodSig, oldName = method.Name, newName = null, renameMappingData = rmm, oldStackTraceSignature = MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method), }); foreach (Parameter param in method.Parameters) { if (param.ParamDef != null) { _paramRenames.Add(param.ParamDef, new RenameRecord { status = RenameStatus.NotRenamed, signature = param.Name, oldName = param.Name, newName = null, }); } } } foreach (FieldDef field in type.Fields) { nameMaker.AddPreservedName(field, field.Name); string fieldSig = TypeSigUtil.ComputeFieldDefSignature(field); RenameMappingField rmf = rmt?.fields.GetValueOrDefault(fieldSig); if (rmf != null) { nameMaker.AddPreservedName(field, rmf.newName); } _fieldRenames.Add(field, new RenameRecord { status = RenameStatus.NotRenamed, signature = fieldSig, oldName = field.Name, newName = null, renameMappingData = rmf, }); } foreach (PropertyDef property in type.Properties) { nameMaker.AddPreservedName(property, property.Name); string propertySig = TypeSigUtil.ComputePropertyDefSignature(property); RenameMappingProperty rmp = rmt?.properties.GetValueOrDefault(propertySig); if (rmp != null) { nameMaker.AddPreservedName(property, rmp.newName); } _propertyRenames.Add(property, new RenameRecord { status = RenameStatus.NotRenamed, signature = propertySig, oldName = property.Name, newName = null, renameMappingData = rmp, }); } foreach (EventDef eventDef in type.Events) { nameMaker.AddPreservedName(eventDef, eventDef.Name); string eventSig = TypeSigUtil.ComputeEventDefSignature(eventDef); RenameMappingEvent rme = rmt?.events.GetValueOrDefault(eventSig); if (rme != null) { nameMaker.AddPreservedName(eventDef, rme.newName); } _eventRenames.Add(eventDef, new RenameRecord { status = RenameStatus.NotRenamed, signature = eventSig, oldName = eventDef.Name, newName = null, renameMappingData = rme, }); } } } } private void LoadXmlMappingFile(string mappingFile) { if (string.IsNullOrEmpty(mappingFile) || !File.Exists(mappingFile)) { return; } var doc = new XmlDocument(); doc.Load(mappingFile); var root = doc.DocumentElement; foreach (XmlNode node in root.ChildNodes) { if (!(node is XmlElement element)) { continue; } LoadAssemblyMapping(element); } } private void LoadAssemblyMapping(XmlElement ele) { if (ele.Name != "assembly") { throw new System.Exception($"Invalid node name: {ele.Name}. Expected 'assembly'."); } var assemblyName = ele.Attributes["name"].Value; var rma = new RenameMappingAssembly { assName = assemblyName, }; foreach (XmlNode node in ele.ChildNodes) { if (!(node is XmlElement element)) { continue; } if (element.Name != "type") { throw new System.Exception($"Invalid node name: {element.Name}. Expected 'type'."); } LoadTypeMapping(element, rma); } _assemblies.Add(assemblyName, rma); } private void LoadTypeMapping(XmlElement ele, RenameMappingAssembly ass) { var typeName = ele.Attributes["fullName"].Value; var newTypeName = ele.Attributes["newFullName"].Value; var rmt = new RenameMappingType { oldFullName = typeName, newFullName = newTypeName, status = (RenameStatus)System.Enum.Parse(typeof(RenameStatus), ele.Attributes["status"].Value), }; foreach (XmlNode node in ele.ChildNodes) { if (!(node is XmlElement c)) { continue; } switch (node.Name) { case "field": LoadFieldMapping(c, rmt); break; case "event": LoadEventMapping(c, rmt); break; case "property": LoadPropertyMapping(c, rmt); break; case "method": LoadMethodMapping(c, rmt); break; default: throw new System.Exception($"Invalid node name:{node.Name}"); } } ass.types.Add(typeName, rmt); } private void LoadMethodMapping(XmlElement ele, RenameMappingType type) { string signature = ele.Attributes["signature"].Value; string newName = ele.Attributes["newName"].Value; var rmm = new RenameMappingMethod { signature = signature, newName = newName, status = RenameStatus.Renamed, }; type.methods.Add(signature, rmm); } private void LoadFieldMapping(XmlElement ele, RenameMappingType type) { string signature = ele.Attributes["signature"].Value; string newName = ele.Attributes["newName"].Value; var rmf = new RenameMappingField { signature = signature, newName = newName, status = RenameStatus.Renamed, }; type.fields.Add(signature, rmf); } private void LoadPropertyMapping(XmlElement ele, RenameMappingType type) { string signature = ele.Attributes["signature"].Value; string newName = ele.Attributes["newName"].Value; var rmp = new RenameMappingProperty { signature = signature, newName = newName, status = RenameStatus.Renamed, }; type.properties.Add(signature, rmp); } private void LoadEventMapping(XmlElement ele, RenameMappingType type) { string signature = ele.Attributes["signature"].Value; string newName = ele.Attributes["newName"].Value; var rme = new RenameMappingEvent { signature = signature, newName = newName, status = RenameStatus.Renamed, }; type.events.Add(signature, rme); } private List GetSortedValueList(Dictionary dic, Comparison comparer) { var list = dic.Values.ToList(); list.Sort(comparer); return list; } public void WriteXmlMappingFile() { if (string.IsNullOrEmpty(_mappingFile)) { return; } var doc = new XmlDocument(); var root = doc.CreateElement("mapping"); doc.AppendChild(root); var sortedModRenames = _modRenames.ToList(); sortedModRenames.Sort((a, b) => a.Value.oldName.CompareTo(b.Value.oldName)); foreach (var kvp in sortedModRenames) { ModuleDef mod = kvp.Key; RenameRecord record = kvp.Value; var assemblyNode = doc.CreateElement("assembly"); assemblyNode.SetAttribute("name", mod.Assembly.Name); foreach (TypeDef type in mod.GetTypes()) { WriteTypeMapping(assemblyNode, type); } root.AppendChild(assemblyNode); } var sortedAsses = GetSortedValueList(_assemblies, (a, b) => a.assName.CompareTo(b.assName)); foreach (RenameMappingAssembly ass in sortedAsses) { if (_modRenames.Keys.Any(m => m.Assembly.Name == ass.assName)) { continue; } var assemblyNode = doc.CreateElement("assembly"); assemblyNode.SetAttribute("name", ass.assName); var sortedTypes = GetSortedValueList(ass.types, (a, b) => a.oldFullName.CompareTo(b.oldFullName)); foreach (var type in sortedTypes) { WriteTypeMapping(assemblyNode, type.oldFullName, type); } root.AppendChild(assemblyNode); } Directory.CreateDirectory(Path.GetDirectoryName(_mappingFile)); doc.Save(_mappingFile); Debug.Log($"Mapping file saved to {Path.GetFullPath(_mappingFile)}"); } private void WriteTypeMapping(XmlElement assNode, TypeDef type) { _typeRenames.TryGetValue(type, out var record); var typeNode = assNode.OwnerDocument.CreateElement("type"); typeNode.SetAttribute("fullName", record?.signature ?? type.FullName); typeNode.SetAttribute("newFullName", record != null && record.status == RenameStatus.Renamed ? record.newName : ""); typeNode.SetAttribute("status", record != null ? record.status.ToString() : RenameStatus.NotRenamed.ToString()); foreach (FieldDef field in type.Fields) { WriteFieldMapping(typeNode, field); } foreach (PropertyDef property in type.Properties) { WritePropertyMapping(typeNode, property); } foreach (EventDef eventDef in type.Events) { WriteEventMapping(typeNode, eventDef); } foreach (MethodDef method in type.Methods) { WriteMethodMapping(typeNode, method); } if ((record != null && record.status == RenameStatus.Renamed) || typeNode.ChildNodes.Count > 0) { assNode.AppendChild(typeNode); } } private void WriteTypeMapping(XmlElement assNode, string fullName, RenameMappingType type) { var typeNode = assNode.OwnerDocument.CreateElement("type"); typeNode.SetAttribute("fullName", fullName); typeNode.SetAttribute("newFullName", type.status == RenameStatus.Renamed ? type.newFullName : ""); typeNode.SetAttribute("status", type.status.ToString()); foreach (var e in type.fields) { string signature = e.Key; RenameMappingField field = e.Value; WriteFieldMapping(typeNode, e.Key, e.Value); } foreach (var e in type.properties) { WritePropertyMapping(typeNode, e.Key, e.Value); } foreach (var e in type.events) { WriteEventMapping(typeNode, e.Key, e.Value); } foreach (var e in type.methods) { WriteMethodMapping(typeNode, e.Key, e.Value); } assNode.AppendChild(typeNode); } private void WriteFieldMapping(XmlElement typeEle, FieldDef field) { if (!_fieldRenames.TryGetValue(field, out var record) || record.status == RenameStatus.NotRenamed) { return; } var fieldNode = typeEle.OwnerDocument.CreateElement("field"); fieldNode.SetAttribute("signature", record?.signature); fieldNode.SetAttribute("newName", record.newName); //fieldNode.SetAttribute("status", record.status.ToString()); typeEle.AppendChild(fieldNode); } private void WriteFieldMapping(XmlElement typeEle, string signature, RenameMappingField field) { var fieldNode = typeEle.OwnerDocument.CreateElement("field"); fieldNode.SetAttribute("signature", signature); fieldNode.SetAttribute("newName", field.newName); //fieldNode.SetAttribute("status", record.status.ToString()); typeEle.AppendChild(fieldNode); } private void WritePropertyMapping(XmlElement typeEle, PropertyDef property) { if (!_propertyRenames.TryGetValue(property, out var record) || record.status == RenameStatus.NotRenamed) { return; } var propertyNode = typeEle.OwnerDocument.CreateElement("property"); propertyNode.SetAttribute("signature", record.signature); propertyNode.SetAttribute("newName", record.newName); //propertyNode.SetAttribute("status", record.status.ToString()); typeEle.AppendChild(propertyNode); } private void WritePropertyMapping(XmlElement typeEle, string signature, RenameMappingProperty property) { var propertyNode = typeEle.OwnerDocument.CreateElement("property"); propertyNode.SetAttribute("signature", signature); propertyNode.SetAttribute("newName", property.newName); //propertyNode.SetAttribute("status", record.status.ToString()); typeEle.AppendChild(propertyNode); } private void WriteEventMapping(XmlElement typeEle, EventDef eventDef) { if (!_eventRenames.TryGetValue(eventDef, out var record) || record.status == RenameStatus.NotRenamed) { return; } var eventNode = typeEle.OwnerDocument.CreateElement("event"); eventNode.SetAttribute("signature", record.signature); eventNode.SetAttribute("newName", record.newName); typeEle.AppendChild(eventNode); } private void WriteEventMapping(XmlElement typeEle, string signature, RenameMappingEvent eventDef) { var eventNode = typeEle.OwnerDocument.CreateElement("event"); eventNode.SetAttribute("signature", signature); eventNode.SetAttribute("newName", eventDef.newName); typeEle.AppendChild(eventNode); } private void WriteMethodMapping(XmlElement typeEle, MethodDef method) { if (!_methodRenames.TryGetValue(method, out var record) || record.status == RenameStatus.NotRenamed) { return; } var methodNode = typeEle.OwnerDocument.CreateElement("method"); methodNode.SetAttribute("signature", record.signature); methodNode.SetAttribute("newName", record.newName); methodNode.SetAttribute("oldStackTraceSignature", record.oldStackTraceSignature); methodNode.SetAttribute("newStackTraceSignature", MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method)); //methodNode.SetAttribute("status", record != null ? record.status.ToString() : RenameStatus.NotRenamed.ToString()); foreach (Parameter param in method.Parameters) { if (param.ParamDef != null) { WriteMethodParamMapping(methodNode, param.ParamDef); } } typeEle.AppendChild(methodNode); } private void WriteMethodMapping(XmlElement typeEle, string signature, RenameMappingMethod method) { var methodNode = typeEle.OwnerDocument.CreateElement("method"); methodNode.SetAttribute("signature", signature); methodNode.SetAttribute("newName", method.newName); typeEle.AppendChild(methodNode); } private void WriteMethodParamMapping(XmlElement methodEle, ParamDef param) { if (!_paramRenames.TryGetValue(param, out var record) || record.status == RenameStatus.NotRenamed) { return; } var paramNode = methodEle.OwnerDocument.CreateElement("param"); paramNode.SetAttribute("index", param.Sequence.ToString()); paramNode.SetAttribute("newName", record.newName); //paramNode.SetAttribute("status", record.status.ToString()); methodEle.AppendChild(paramNode); } public void AddRename(ModuleDef mod, string newName) { RenameRecord record = _modRenames[mod]; record.status = RenameStatus.Renamed; record.newName = newName; } public void AddRename(TypeDef type, string newName) { RenameRecord record = _typeRenames[type]; record.status = RenameStatus.Renamed; record.newName = newName; } public void AddRename(MethodDef method, string newName) { RenameRecord record = _methodRenames[method]; record.status = RenameStatus.Renamed; record.newName = newName; } public void InitAndAddRename(VirtualMethodGroup methodGroup, string newName) { RenameRecord methodRecord = _methodRenames[methodGroup.methods[0]]; _virtualMethodGroups.Add(methodGroup, new RenameRecord { status = RenameStatus.Renamed, signature = methodRecord.signature, oldName = methodRecord.oldName, newName = newName, }); } public void AddRename(FieldDef field, string newName) { RenameRecord record = _fieldRenames[field]; record.status = RenameStatus.Renamed; record.newName = newName; } public void AddRename(PropertyDef property, string newName) { RenameRecord record = _propertyRenames[property]; record.status = RenameStatus.Renamed; record.newName = newName; } public void AddRename(EventDef eventDef, string newName) { RenameRecord record = _eventRenames[eventDef]; record.status = RenameStatus.Renamed; record.newName = newName; } public bool TryGetExistRenameMapping(TypeDef type, out string newNamespace, out string newName) { if (_typeRenames.TryGetValue(type, out var record) && record.renameMappingData != null) { var rmt = (RenameMappingType)record.renameMappingData; (newNamespace, newName) = MetaUtil.SplitNamespaceAndName(rmt.newFullName); return true; } newNamespace = null; newName = null; return false; } public bool TryGetExistRenameMapping(MethodDef method, out string newName) { if (_methodRenames.TryGetValue(method, out var record) && record.renameMappingData != null) { newName = ((RenameMappingMethod)record.renameMappingData).newName; return true; } newName = null; return false; } public bool TryGetExistRenameMapping(FieldDef field, out string newName) { if (_fieldRenames.TryGetValue(field, out var record) && record.renameMappingData != null) { newName = ((RenameMappingField)record.renameMappingData).newName; return true; } newName = null; return false; } public bool TryGetExistRenameMapping(PropertyDef property, out string newName) { if (_propertyRenames.TryGetValue(property, out var record) && record.renameMappingData != null) { newName = ((RenameMappingProperty)record.renameMappingData).newName; return true; } newName = null; return false; } public bool TryGetExistRenameMapping(EventDef eventDef, out string newName) { if (_eventRenames.TryGetValue(eventDef, out var record) && record.renameMappingData != null) { newName = ((RenameMappingEvent)record.renameMappingData).newName; return true; } newName = null; return false; } public bool TryGetRename(VirtualMethodGroup group, out string newName) { if (_virtualMethodGroups.TryGetValue(group, out var record)) { newName = record.newName; return true; } newName = null; return false; } } }