├── Assemblies ├── Mono.Cecil.dll ├── Mono.Cecil.Mdb.dll ├── Mono.Cecil.Pdb.dll ├── nunit.framework.dll └── Mono.Cecil.Rocks.dll ├── IRewriteStep.cs ├── DebugSymbolFormat.cs ├── IMethodDefinitionVisitor.cs ├── packages └── repositories.config ├── packages.config ├── .gitignore ├── Test ├── Test.Driver │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ReferenceRewriting.cs │ └── Test.Driver.csproj ├── Test.Target │ ├── ObjectStore.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Test.Target.csproj ├── Test.UnitTests │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TypeAliasesTest.cs │ └── Test.UnitTests.csproj └── Test.Support │ ├── Properties │ └── AssemblyInfo.cs │ ├── ArrayList.cs │ └── Test.Support.csproj ├── RewriteStep.cs ├── IReferenceVisitor.cs ├── MethodAliases.cs ├── RewriteOperation.cs ├── MethodDefinitionDispatcher.cs ├── Properties └── AssemblyInfo.cs ├── NuGetAssemblyResolver.cs ├── RemoveStrongNamesFromAssemblyReferences.cs ├── SearchPathAssemblyResolver.cs ├── NuGetPackageResolver.cs ├── RewriteAssemblyManifest.cs ├── RewriteMethodSpecMemberRefs.cs ├── rrw.csproj ├── TypeAliases.cs ├── Program.cs ├── RewriteContext.cs ├── ReferenceDispatcher.cs ├── EnumeratorGenericConstraintsFixer.cs ├── rrw.sln ├── MiniJSON.cs ├── RewriteTypeReferences.cs └── Options.cs /Assemblies/Mono.Cecil.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/ReferenceRewriter/HEAD/Assemblies/Mono.Cecil.dll -------------------------------------------------------------------------------- /Assemblies/Mono.Cecil.Mdb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/ReferenceRewriter/HEAD/Assemblies/Mono.Cecil.Mdb.dll -------------------------------------------------------------------------------- /Assemblies/Mono.Cecil.Pdb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/ReferenceRewriter/HEAD/Assemblies/Mono.Cecil.Pdb.dll -------------------------------------------------------------------------------- /Assemblies/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/ReferenceRewriter/HEAD/Assemblies/nunit.framework.dll -------------------------------------------------------------------------------- /Assemblies/Mono.Cecil.Rocks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/ReferenceRewriter/HEAD/Assemblies/Mono.Cecil.Rocks.dll -------------------------------------------------------------------------------- /IRewriteStep.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.ReferenceRewriter 2 | { 3 | interface IRewriteStep 4 | { 5 | void Execute(RewriteContext context); 6 | } 7 | } -------------------------------------------------------------------------------- /DebugSymbolFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.ReferenceRewriter 2 | { 3 | public enum DebugSymbolFormat 4 | { 5 | None, 6 | Pdb, 7 | Mdb, 8 | } 9 | } -------------------------------------------------------------------------------- /IMethodDefinitionVisitor.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | namespace Unity.ReferenceRewriter 4 | { 5 | interface IMethodDefinitionVisitor 6 | { 7 | void Visit(MethodDefinition method); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | *.suo 4 | *.user 5 | *.pidb 6 | *.userprefs 7 | *.xml 8 | *.nupkg 9 | **/test-results/* 10 | *Resharper* 11 | Test/*.dll 12 | Test/*.mdb 13 | Test/*.pdb 14 | packages/* 15 | !packages/repositories.config 16 | -------------------------------------------------------------------------------- /Test/Test.Driver/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /RewriteStep.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.ReferenceRewriter 2 | { 3 | abstract class RewriteStep : IRewriteStep 4 | { 5 | protected RewriteContext Context { get; private set; } 6 | 7 | public void Execute(RewriteContext context) 8 | { 9 | Context = context; 10 | 11 | Run(); 12 | } 13 | 14 | protected abstract void Run(); 15 | } 16 | } -------------------------------------------------------------------------------- /Test/Test.Target/ObjectStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace Test.Target 4 | { 5 | public class ObjectStore 6 | { 7 | private readonly ArrayList _list = new ArrayList(); 8 | 9 | public void AddObject(object obj) 10 | { 11 | _list.Add(obj); 12 | } 13 | 14 | public void DeleteObject(object obj) 15 | { 16 | _list.Remove(obj); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /IReferenceVisitor.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | 4 | namespace Unity.ReferenceRewriter 5 | { 6 | interface IReferenceVisitor 7 | { 8 | void Visit(TypeReference type, string referencingEntityName); 9 | void Visit(FieldReference field, string referencingEntityName); 10 | void Visit(MethodReference method, string referencingEntityName); 11 | 12 | bool MethodChanged { get; } 13 | MethodReference ParamsMethod { get; } 14 | 15 | void RewriteObjectListToParamsCall(MethodBody methodBody, int instructionIndex); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MethodAliases.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.ReferenceRewriter 7 | { 8 | class MethodAliases 9 | { 10 | private static readonly SortedSet> _aliases; 11 | 12 | static MethodAliases() 13 | { 14 | _aliases = new SortedSet> 15 | { 16 | new Tuple( 17 | "Dispose", "Close") 18 | }; 19 | } 20 | 21 | 22 | public static bool AreAliases(string typeA, string typeB) 23 | { 24 | var areAliases = _aliases.Contains(new Tuple(typeA, typeB)) || 25 | _aliases.Contains(new Tuple(typeB, typeA)); 26 | 27 | return areAliases; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RewriteOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Unity.ReferenceRewriter 5 | { 6 | public class RewriteOperation 7 | { 8 | private readonly List _steps; 9 | 10 | private RewriteOperation(params IRewriteStep[] steps) : this(steps as IEnumerable) 11 | { 12 | } 13 | 14 | private RewriteOperation(IEnumerable steps) 15 | { 16 | _steps = new List(steps); 17 | } 18 | 19 | public void Execute(RewriteContext context) 20 | { 21 | if (context == null) 22 | throw new ArgumentNullException("context"); 23 | 24 | foreach (var step in _steps) 25 | step.Execute(context); 26 | } 27 | 28 | public static RewriteOperation Create(Func supportNamespaceMapper) 29 | { 30 | if (supportNamespaceMapper == null) 31 | throw new ArgumentNullException("supportNamespaceMapper"); 32 | 33 | return new RewriteOperation( 34 | new RewriteAssemblyManifest(), 35 | new RewriteTypeReferences(supportNamespaceMapper), 36 | new RewriteMethodSpecMemberRefs(), 37 | new EnumeratorGenericConstraintsFixer(), 38 | new RemoveStrongNamesFromAssemblyReferences() 39 | ); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /MethodDefinitionDispatcher.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | namespace Unity.ReferenceRewriter 4 | { 5 | class MethodDefinitionDispatcher 6 | { 7 | private readonly ModuleDefinition _module; 8 | private readonly IMethodDefinitionVisitor _visitor; 9 | 10 | public static void DispatchOn(ModuleDefinition module, IMethodDefinitionVisitor visitor) 11 | { 12 | new MethodDefinitionDispatcher(module, visitor).Dispatch(); 13 | } 14 | 15 | private MethodDefinitionDispatcher(ModuleDefinition module, IMethodDefinitionVisitor visitor) 16 | { 17 | _module = module; 18 | _visitor = visitor; 19 | } 20 | 21 | private void Dispatch() 22 | { 23 | foreach (var type in _module.Types) 24 | Dispatch(type); 25 | } 26 | 27 | private void Dispatch(TypeDefinition type) 28 | { 29 | foreach (var nestedType in type.NestedTypes) 30 | Dispatch(nestedType); 31 | 32 | foreach (var method in type.Methods) 33 | Visit(method); 34 | 35 | foreach (var property in type.Properties) 36 | { 37 | Visit(property.GetMethod); 38 | Visit(property.SetMethod); 39 | } 40 | 41 | foreach (var @event in type.Events) 42 | { 43 | Visit(@event.AddMethod); 44 | Visit(@event.InvokeMethod); 45 | Visit(@event.RemoveMethod); 46 | } 47 | } 48 | 49 | private void Visit(MethodDefinition method) 50 | { 51 | if (method == null) 52 | return; 53 | 54 | _visitor.Visit(method); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("rrw")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("rrw")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("162e964d-d0ab-49dc-9f1f-207ca039f151")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Test/Test.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Test.UnitTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Test.UnitTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4709f0f7-f40b-4bdb-a42e-c8a47022b08f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Test/Test.Driver/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Test.Driver")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Test.Driver")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("24055476-9996-4074-b26e-e7111dd0cfab")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Test/Test.Support/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Test.Support")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Test.Support")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8c99bed6-952c-4deb-a751-e6a611b85cd8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Test/Test.Target/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Test.Target")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Test.Target")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1cd969b4-ff17-42dd-913d-f3d4c878f0ef")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /NuGetAssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Mono.Cecil; 5 | 6 | namespace Unity.ReferenceRewriter 7 | { 8 | internal class NuGetAssemblyResolver : SearchPathAssemblyResolver 9 | { 10 | private readonly Dictionary _references; 11 | private readonly Dictionary _assemblies = new Dictionary(StringComparer.InvariantCulture); 12 | 13 | public NuGetAssemblyResolver(string projectLockFile) 14 | { 15 | var resolver = new NuGetPackageResolver 16 | { 17 | ProjectLockFile = projectLockFile, 18 | }; 19 | resolver.Resolve(); 20 | var references = resolver.ResolvedReferences; 21 | 22 | _references = new Dictionary(references.Length, StringComparer.InvariantCultureIgnoreCase); 23 | foreach (var reference in references) 24 | { 25 | var fileName = Path.GetFileName(reference); 26 | string existingReference; 27 | if (_references.TryGetValue(fileName, out existingReference)) 28 | throw new Exception(string.Format("Reference \"{0}\" already added as \"{1}\".", reference, existingReference)); 29 | _references.Add(fileName, reference); 30 | } 31 | } 32 | 33 | public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) 34 | { 35 | AssemblyDefinition assembly; 36 | if (_assemblies.TryGetValue(name.Name, out assembly)) 37 | return assembly; 38 | 39 | var fileName = name.Name + (name.IsWindowsRuntime ? ".winmd" : ".dll"); 40 | string reference; 41 | if (_references.TryGetValue(fileName, out reference)) 42 | { 43 | assembly = AssemblyDefinition.ReadAssembly(reference, parameters); 44 | if (string.Equals(assembly.Name.Name, name.Name, StringComparison.InvariantCulture)) 45 | { 46 | _assemblies.Add(name.Name, assembly); 47 | return assembly; 48 | } 49 | } 50 | 51 | return base.Resolve(name, parameters); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /RemoveStrongNamesFromAssemblyReferences.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Unity.ReferenceRewriter 9 | { 10 | class RemoveStrongNamesFromAssemblyReferences : RewriteStep 11 | { 12 | protected override void Run() 13 | { 14 | foreach (var reference in Context.TargetModule.AssemblyReferences) 15 | { 16 | if (ShouldRemoveStrongName(reference)) 17 | { 18 | RemoveStrongName(reference); 19 | } 20 | } 21 | } 22 | 23 | private bool ShouldRemoveStrongName(AssemblyNameReference reference) 24 | { 25 | // Strong name is not present already 26 | if (reference.PublicKeyToken == null || reference.PublicKeyToken.Length == 0) 27 | { 28 | return false; 29 | } 30 | 31 | // Strong name must be kept 32 | if (Context.StrongNameReferences.Any(r => r == reference.Name)) 33 | { 34 | return false; 35 | } 36 | 37 | AssemblyDefinition assembly; 38 | 39 | try 40 | { 41 | // Can't find target assembly 42 | assembly = Context.AssemblyResolver.Resolve(reference); 43 | } 44 | catch (AssemblyResolutionException) 45 | { 46 | return false; 47 | } 48 | 49 | // Don't remove strong name to framework references 50 | var assemblyDir = NormalizePath(Path.GetDirectoryName(assembly.MainModule.FullyQualifiedName)); 51 | if (Context.FrameworkPaths.Any(path => NormalizePath(path) == assemblyDir)) 52 | { 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | private void RemoveStrongName(AssemblyNameReference reference) 60 | { 61 | Context.RewriteTarget = true; 62 | reference.PublicKeyToken = new byte[0]; 63 | } 64 | 65 | public static string NormalizePath(string path) 66 | { 67 | return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToUpperInvariant(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Test/Test.Support/ArrayList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Test.Support.Collections 6 | { 7 | public class ArrayList : IList 8 | { 9 | private readonly List _list; 10 | 11 | public ArrayList() 12 | { 13 | _list = new List(); 14 | } 15 | 16 | public ArrayList(int capacity) 17 | { 18 | _list = new List(capacity); 19 | } 20 | 21 | public int Count { get { return _list.Count; } } 22 | 23 | public object SyncRoot { get { return ((ICollection) _list).SyncRoot; } } 24 | 25 | public bool IsSynchronized { get { return ((ICollection)_list).IsSynchronized; } } 26 | 27 | public object this[int index] 28 | { 29 | get { return _list[index]; } 30 | set { _list[index] = value; } 31 | } 32 | 33 | public IEnumerator GetEnumerator() 34 | { 35 | return _list.GetEnumerator(); 36 | } 37 | 38 | public void CopyTo(Array array, int index) 39 | { 40 | ((ICollection)_list).CopyTo(array, index); 41 | } 42 | 43 | public int Add(object value) 44 | { 45 | _list.Add(value); 46 | return _list.Count - 1; 47 | } 48 | 49 | public bool Contains(object value) 50 | { 51 | return _list.Contains(value); 52 | } 53 | 54 | public void Clear() 55 | { 56 | _list.Clear(); 57 | } 58 | 59 | public int IndexOf(object value) 60 | { 61 | return _list.IndexOf(value); 62 | } 63 | 64 | public void Insert(int index, object value) 65 | { 66 | _list.Insert(index, value); 67 | } 68 | 69 | public void Remove(object value) 70 | { 71 | _list.Remove(value); 72 | } 73 | 74 | public void RemoveAt(int index) 75 | { 76 | _list.RemoveAt(index); 77 | } 78 | 79 | public bool IsReadOnly { get { return false; } } 80 | public bool IsFixedSize { get { return false; } } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Test/Test.Driver/ReferenceRewriting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | using Mono.Cecil.Cil; 7 | using NUnit.Framework; 8 | using Unity.ReferenceRewriter; 9 | 10 | namespace Test.Driver 11 | { 12 | [TestFixture] 13 | public class ReferenceRewriting 14 | { 15 | [Test] 16 | public void AssemblyReferences() 17 | { 18 | AssertRewrite( 19 | ctx => Assert.AreEqual("2.0.0.0", ctx.TargetModule.AssemblyReferences.Single(r => r.Name == "mscorlib").Version.ToString(4)), 20 | ctx => Assert.AreEqual("4.0.0.0", ctx.TargetModule.AssemblyReferences.Single(r => r.Name == "mscorlib").Version.ToString(4))); 21 | } 22 | 23 | [Test] 24 | public void ArrayListReferences() 25 | { 26 | AssertRewrite( 27 | ctx => {}, 28 | ctx => 29 | { 30 | var os = ctx.TargetModule.GetType("Test.Target.ObjectStore"); 31 | var add = os.Methods.Single(m => m.Name == "AddObject"); 32 | 33 | var call = (MethodReference) add.Body.Instructions.Single(i => i.OpCode.OperandType == OperandType.InlineMethod).Operand; 34 | 35 | Assert.AreEqual("Test.Support", call.DeclaringType.Scope.Name); 36 | Assert.AreEqual(1, ctx.TargetModule.AssemblyReferences.Count(r => r.Name == "Test.Support")); 37 | }); 38 | } 39 | 40 | public static void AssertRewrite(Action preAssertions, Action postAssertions) 41 | { 42 | var context = RewriteContext.For( 43 | "Test.Target.dll", 44 | DebugSymbolFormat.None, 45 | "Test.Support.dll", 46 | string.Empty, 47 | new string[] { Path.GetDirectoryName(typeof(object).Assembly.Location) }, 48 | string.Empty, 49 | new string[] {}, 50 | string.Empty, 51 | new string[0], 52 | new string[0], 53 | new Dictionary>(), 54 | new Dictionary>()); 55 | 56 | var operation = RewriteOperation.Create(ns => ns.StartsWith("System") ? "Test.Support" + ns.Substring("System".Length) : ns); 57 | 58 | preAssertions(context); 59 | operation.Execute(context); 60 | postAssertions(context); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SearchPathAssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | 7 | namespace Unity.ReferenceRewriter 8 | { 9 | internal class SearchPathAssemblyResolver : IAssemblyResolver 10 | { 11 | private readonly Dictionary _assemblies = new Dictionary(StringComparer.InvariantCulture); 12 | private readonly List _searchPaths = new List(); 13 | 14 | public void RegisterAssembly(AssemblyDefinition assembly) 15 | { 16 | var name = assembly.Name.Name; 17 | if (_assemblies.ContainsKey(name)) 18 | throw new Exception(string.Format("Assembly \"{0}\" is already registered.", name)); 19 | _assemblies.Add(name, assembly); 20 | } 21 | 22 | public void RegisterSearchPath(string path) 23 | { 24 | if (_searchPaths.Any(p => string.Equals(p, path, StringComparison.InvariantCultureIgnoreCase))) 25 | return; 26 | _searchPaths.Add(path); 27 | } 28 | 29 | public AssemblyDefinition Resolve(string fullName) 30 | { 31 | return Resolve(fullName, new ReaderParameters() { AssemblyResolver = this }); 32 | } 33 | 34 | public AssemblyDefinition Resolve(string fullName, ReaderParameters parameters) 35 | { 36 | return Resolve(AssemblyNameReference.Parse(fullName), parameters); 37 | } 38 | 39 | public AssemblyDefinition Resolve(AssemblyNameReference name) 40 | { 41 | return Resolve(name, new ReaderParameters() {AssemblyResolver = this}); 42 | } 43 | 44 | public virtual AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) 45 | { 46 | AssemblyDefinition assembly; 47 | if (_assemblies.TryGetValue(name.Name, out assembly)) 48 | return assembly; 49 | 50 | foreach (var searchPath in _searchPaths) 51 | { 52 | var fileName = name.Name + (name.IsWindowsRuntime ? ".winmd" : ".dll"); 53 | var filePath = Path.Combine(searchPath, fileName); 54 | if (!File.Exists(filePath)) 55 | continue; 56 | 57 | assembly = AssemblyDefinition.ReadAssembly(filePath, parameters); 58 | if (!string.Equals(assembly.Name.Name, name.Name, StringComparison.InvariantCulture)) 59 | continue; 60 | _assemblies.Add(name.Name, assembly); 61 | return assembly; 62 | } 63 | 64 | throw new AssemblyResolutionException(name); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Test/Test.Target/Test.Target.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F4896833-9783-490F-B7A1-12C550AF43BA} 8 | Library 9 | Properties 10 | Test.Target 11 | Test.Target 12 | v3.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 50 | -------------------------------------------------------------------------------- /Test/Test.Support/Test.Support.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9} 8 | Library 9 | Properties 10 | Test.Support 11 | Test.Support 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /NuGetPackageResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using MiniJSON; 5 | 6 | namespace Unity.ReferenceRewriter 7 | { 8 | internal sealed class NuGetPackageResolver 9 | { 10 | #region Properties 11 | 12 | public string PackagesDirectory 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | public string ProjectLockFile 19 | { 20 | get; 21 | set; 22 | } 23 | 24 | public string TargetMoniker 25 | { 26 | get; 27 | set; 28 | } 29 | 30 | public string[] ResolvedReferences 31 | { 32 | get; 33 | private set; 34 | } 35 | 36 | #endregion 37 | 38 | public NuGetPackageResolver() 39 | { 40 | TargetMoniker = "UAP,Version=v10.0"; 41 | } 42 | 43 | public void Resolve() 44 | { 45 | var text = File.ReadAllText(ProjectLockFile); 46 | var lockFile = (Dictionary)Json.Deserialize(text); 47 | var targets = (Dictionary)lockFile["targets"]; 48 | var target = (Dictionary)targets[TargetMoniker]; 49 | 50 | var references = new List(); 51 | var packagesPath = GetPackagesPath().ConvertToWindowsPath(); 52 | 53 | foreach (var packagePair in target) 54 | { 55 | var package = (Dictionary)packagePair.Value; 56 | 57 | object compileObject; 58 | if (!package.TryGetValue("compile", out compileObject)) 59 | continue; 60 | var compile = (Dictionary)compileObject; 61 | 62 | var parts = packagePair.Key.Split('/'); 63 | var packageId = parts[0]; 64 | var packageVersion = parts[1]; 65 | var packagePath = Path.Combine(packagesPath, packageId, packageVersion); 66 | if (!Directory.Exists(packagePath)) 67 | throw new Exception(string.Format("Package directory not found: \"{0}\".", packagePath)); 68 | 69 | foreach (var name in compile.Keys) 70 | { 71 | if (string.Equals(Path.GetFileName(name), "_._", StringComparison.InvariantCultureIgnoreCase)) 72 | continue; 73 | var reference = Path.Combine(packagePath, name.ConvertToWindowsPath()); 74 | if (!File.Exists(reference)) 75 | throw new Exception(string.Format("Reference not found: \"{0}\".", reference)); 76 | references.Add(reference); 77 | } 78 | 79 | if (package.ContainsKey("frameworkAssemblies")) 80 | throw new NotImplementedException("Support for \"frameworkAssemblies\" property has not been implemented yet."); 81 | } 82 | 83 | ResolvedReferences = references.ToArray(); 84 | } 85 | 86 | private string GetPackagesPath() 87 | { 88 | var value = PackagesDirectory; 89 | if (!string.IsNullOrEmpty(value)) 90 | return value; 91 | value = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); 92 | if (!string.IsNullOrEmpty(value)) 93 | return value; 94 | var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 95 | return Path.Combine(userProfile, ".nuget", "packages"); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /RewriteAssemblyManifest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Mono.Cecil; 5 | 6 | namespace Unity.ReferenceRewriter 7 | { 8 | class RewriteAssemblyManifest : RewriteStep 9 | { 10 | protected override void Run() 11 | { 12 | if (Context.TargetModule.Assembly.Name.HasPublicKey) 13 | RemoveStrongName(); 14 | 15 | foreach (var reference in Context.TargetModule.AssemblyReferences) 16 | RewriteAssemblyReference(reference); 17 | } 18 | 19 | private void RemoveStrongName() 20 | { 21 | if (!ShouldRemoveStrongName(Context.TargetModule.Assembly.Name)) 22 | return; 23 | 24 | Context.RewriteTarget = true; 25 | Context.TargetModule.Assembly.Name.PublicKey = new byte[0]; 26 | Context.TargetModule.Attributes = ModuleAttributes.ILOnly; 27 | } 28 | 29 | private void RewriteAssemblyReference(AssemblyNameReference reference) 30 | { 31 | try 32 | { 33 | if (ShouldRewriteToWinmdReference(reference)) 34 | { 35 | Context.RewriteTarget = true; 36 | reference.Version = new Version(255, 255, 255, 255); 37 | reference.IsWindowsRuntime = true; 38 | } 39 | 40 | var assembly = Context.AssemblyResolver.Resolve(reference); 41 | 42 | if ((reference.IsWindowsRuntime != assembly.Name.IsWindowsRuntime) || (IsFrameworkAssembly(assembly) && ShouldRewriteFrameworkReference(reference, assembly))) 43 | { 44 | Context.RewriteTarget = true; 45 | reference.Version = assembly.Name.Version; 46 | reference.PublicKeyToken = Copy(assembly.Name.PublicKeyToken); 47 | reference.IsWindowsRuntime = assembly.Name.IsWindowsRuntime; 48 | } 49 | 50 | } 51 | catch (AssemblyResolutionException) 52 | { 53 | } 54 | } 55 | 56 | private static bool ShouldRewriteFrameworkReference(AssemblyNameReference reference, AssemblyDefinition assembly) 57 | { 58 | return reference.Version != assembly.Name.Version 59 | || !reference.PublicKeyToken.SequenceEqual(assembly.Name.PublicKeyToken); 60 | } 61 | 62 | private bool ShouldRemoveStrongName(AssemblyNameReference reference) 63 | { 64 | if (reference.PublicKeyToken == null || reference.PublicKeyToken.Length == 0) 65 | return false; 66 | 67 | return Context.StrongNameReferences.All(r => r != reference.Name); 68 | } 69 | 70 | private bool ShouldRewriteToWinmdReference(AssemblyNameReference reference) 71 | { 72 | return Context.WinmdReferences.Contains(reference.Name); 73 | } 74 | 75 | private static byte[] Copy(byte[] bytes) 76 | { 77 | var copy = new byte[bytes.Length]; 78 | Buffer.BlockCopy(bytes, 0, copy, 0, bytes.Length); 79 | return copy; 80 | } 81 | 82 | private bool IsFrameworkAssembly(AssemblyDefinition assembly) 83 | { 84 | bool isFrameworkAssembly = false; 85 | 86 | foreach (var path in Context.FrameworkPaths) 87 | { 88 | if (FullPath(Path.GetDirectoryName(assembly.MainModule.FullyQualifiedName)) == FullPath(path)) 89 | { 90 | isFrameworkAssembly = true; 91 | } 92 | } 93 | 94 | return isFrameworkAssembly; 95 | } 96 | 97 | private static string FullPath(string path) 98 | { 99 | var fullPath = Path.GetFullPath(path); 100 | return fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /RewriteMethodSpecMemberRefs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Mono.Cecil; 3 | 4 | namespace Unity.ReferenceRewriter 5 | { 6 | class RewriteMethodSpecMemberRefs : RewriteStep, IMethodDefinitionVisitor 7 | { 8 | private static string GetAssemblyName(IMetadataScope scope) 9 | { 10 | switch (scope.MetadataScopeType) 11 | { 12 | case MetadataScopeType.AssemblyNameReference: 13 | return ((AssemblyNameReference)scope).Name; 14 | case MetadataScopeType.ModuleDefinition: 15 | return ((ModuleDefinition)scope).Assembly.Name.Name; 16 | default: 17 | throw new NotSupportedException(string.Format("Metadata scope type {0} is not supported.", scope.MetadataScopeType)); 18 | } 19 | } 20 | 21 | private static bool IsSameScope(IMetadataScope left, IMetadataScope right) 22 | { 23 | return string.Equals(GetAssemblyName(left), GetAssemblyName(right), StringComparison.Ordinal); 24 | } 25 | 26 | protected override void Run() 27 | { 28 | MethodDefinitionDispatcher.DispatchOn(Context.TargetModule, this); 29 | } 30 | 31 | public void Visit(MethodDefinition method) 32 | { 33 | if (!method.HasBody) 34 | return; 35 | 36 | foreach (var instruction in method.Body.Instructions) 37 | { 38 | var typeReference = instruction.Operand as TypeReference; 39 | if (typeReference != null) 40 | { 41 | if (typeReference.IsDefinition) 42 | continue; 43 | if (typeReference.IsArray || typeReference.IsGenericParameter || typeReference.IsGenericInstance) 44 | continue; 45 | if (IsSameScope(typeReference.Scope, method.DeclaringType.Scope)) 46 | { 47 | instruction.Operand = typeReference.Resolve(); 48 | this.Context.RewriteTarget = true; 49 | } 50 | continue; 51 | } 52 | 53 | var memberReference = instruction.Operand as MemberReference; 54 | if (memberReference != null) 55 | { 56 | if (memberReference.IsDefinition || memberReference.DeclaringType.IsGenericInstance || memberReference.DeclaringType.IsArray) 57 | continue; 58 | 59 | var methodReference = memberReference as MethodReference; 60 | if (methodReference != null) 61 | { 62 | var genericInstanceMethod = methodReference as GenericInstanceMethod; 63 | if (genericInstanceMethod != null) 64 | { 65 | var elementMethod = genericInstanceMethod.ElementMethod.Resolve(); 66 | if ((elementMethod != null) && IsSameScope(elementMethod.DeclaringType.Scope, method.DeclaringType.Scope)) 67 | { 68 | var genericInstanceMethodFixed = new GenericInstanceMethod(elementMethod); 69 | foreach (var argument in genericInstanceMethod.GenericArguments) 70 | genericInstanceMethodFixed.GenericArguments.Add(argument); 71 | instruction.Operand = genericInstanceMethodFixed; 72 | this.Context.RewriteTarget = true; 73 | } 74 | } 75 | else 76 | { 77 | if (IsSameScope(methodReference.DeclaringType.Scope, method.DeclaringType.Scope)) 78 | { 79 | instruction.Operand = methodReference.Resolve(); 80 | this.Context.RewriteTarget = true; 81 | } 82 | } 83 | continue; 84 | } 85 | 86 | 87 | var fieldReference = memberReference as FieldReference; 88 | if (fieldReference != null) 89 | { 90 | if (IsSameScope(fieldReference.DeclaringType.Scope, method.DeclaringType.Scope)) 91 | { 92 | instruction.Operand = fieldReference.Resolve(); 93 | this.Context.RewriteTarget = true; 94 | } 95 | continue; 96 | } 97 | 98 | continue; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Test/Test.UnitTests/TypeAliasesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Unity.ReferenceRewriter; 4 | 5 | namespace Test.UnitTests 6 | { 7 | [TestClass] 8 | public class TypeAliasesTest 9 | { 10 | [TestMethod] 11 | public void TestGetTemplateArgumentsSimpleOne() 12 | { 13 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IVector`1"); 14 | Assert.IsNotNull(result); 15 | Assert.AreEqual(1, result.Length); 16 | Assert.AreEqual("System.Int32", result[0]); 17 | } 18 | 19 | [TestMethod] 20 | public void TestGetTemplateArgumentsSimpleTwo() 21 | { 22 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IMap`2"); 23 | Assert.IsNotNull(result); 24 | Assert.AreEqual(2, result.Length); 25 | Assert.AreEqual("System.Int32", result[0]); 26 | Assert.AreEqual("System.Int32", result[1]); 27 | } 28 | 29 | [TestMethod] 30 | public void TestGetTemplateArgumentsNestedOneOne() 31 | { 32 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IVector`1>"); 33 | Assert.IsNotNull(result); 34 | Assert.AreEqual(1, result.Length); 35 | Assert.AreEqual("Windows.Foundation.Collections.IVector`1", result[0]); 36 | } 37 | 38 | [TestMethod] 39 | public void TestGetTemplateArgumentsNestedOneTwo() 40 | { 41 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IVector`1>"); 42 | Assert.IsNotNull(result); 43 | Assert.AreEqual(1, result.Length); 44 | Assert.AreEqual("Windows.Foundation.Collections.IMap`2", result[0]); 45 | } 46 | 47 | [TestMethod] 48 | public void TestGetTemplateArgumentsNestedTwoOneOne() 49 | { 50 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IMap`2,Windows.Foundation.Collections.IVector`1>"); 51 | Assert.IsNotNull(result); 52 | Assert.AreEqual(2, result.Length); 53 | Assert.AreEqual("Windows.Foundation.Collections.IVector`1", result[0]); 54 | Assert.AreEqual("Windows.Foundation.Collections.IVector`1", result[1]); 55 | } 56 | 57 | [TestMethod] 58 | public void TestGetTemplateArgumentsNestedComplex() 59 | { 60 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IMap`2,Windows.Foundation.Collections.IVector`1>,Windows.Foundation.Collections.IMap`2,System.Int32>>"); 61 | Assert.IsNotNull(result); 62 | Assert.AreEqual(2, result.Length); 63 | Assert.AreEqual("Windows.Foundation.Collections.IMap`2,Windows.Foundation.Collections.IVector`1>", result[0]); 64 | Assert.AreEqual("Windows.Foundation.Collections.IMap`2,System.Int32>", result[1]); 65 | } 66 | 67 | [TestMethod] 68 | public void TestGetTemplateArgumentsHandleSpaces() 69 | { 70 | var result = TypeAliases.GetTemplateArguments("Windows.Foundation.Collections.IMap`2< Windows.Foundation.Collections.IVector`1, Windows.Foundation.Collections.IVector`1 >"); 71 | Assert.IsNotNull(result); 72 | Assert.AreEqual(2, result.Length); 73 | Assert.AreEqual("Windows.Foundation.Collections.IVector`1", result[0]); 74 | Assert.AreEqual("Windows.Foundation.Collections.IVector`1", result[1]); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Test/Test.Driver/Test.Driver.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81} 8 | Library 9 | Properties 10 | Test.Driver 11 | Test.Driver 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\..\Assemblies\Mono.Cecil.dll 35 | 36 | 37 | ..\..\Assemblies\Mono.Cecil.Mdb.dll 38 | 39 | 40 | ..\..\Assemblies\Mono.Cecil.Pdb.dll 41 | 42 | 43 | ..\..\Assemblies\Mono.Cecil.Rocks.dll 44 | 45 | 46 | ..\..\Assemblies\nunit.framework.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {07911f00-c501-4f6d-a327-d51778fa10bb} 64 | rrw 65 | 66 | 67 | {928dc014-f1ce-4b1b-bad4-a7b9e33f8ab9} 68 | Test.Support 69 | 70 | 71 | {f4896833-9783-490f-b7a1-12c550af43ba} 72 | Test.Target 73 | 74 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /rrw.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {07911F00-C501-4F6D-A327-D51778FA10BB} 8 | Exe 9 | Properties 10 | Unity.ReferenceRewriter 11 | rrw 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | Assemblies\Mono.Cecil.dll 37 | 38 | 39 | Assemblies\Mono.Cecil.Mdb.dll 40 | 41 | 42 | Assemblies\Mono.Cecil.Pdb.dll 43 | 44 | 45 | Assemblies\Mono.Cecil.Rocks.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /Test/Test.UnitTests/Test.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA} 7 | Library 8 | Properties 9 | Test.UnitTests 10 | Test.UnitTests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {07911f00-c501-4f6d-a327-d51778fa10bb} 59 | rrw 60 | 61 | 62 | 63 | 64 | 65 | 66 | False 67 | 68 | 69 | False 70 | 71 | 72 | False 73 | 74 | 75 | False 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /TypeAliases.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.ReferenceRewriter 7 | { 8 | public static class TypeAliases 9 | { 10 | private static readonly SortedSet> _aliases; 11 | 12 | static TypeAliases() 13 | { 14 | _aliases = new SortedSet> 15 | { 16 | new Tuple( 17 | "System.Collections.Generic.IReadOnlyList", "Windows.Foundation.Collections.IVectorView"), 18 | new Tuple( 19 | "System.Collections.Generic.IEnumerable", "Windows.Foundation.Collections.IIterable"), 20 | new Tuple( 21 | "System.Collections.Generic.KeyValuePair", "Windows.Foundation.Collections.IKeyValuePair"), 22 | new Tuple( 23 | "System.Collections.Generic.IDictionary", "Windows.Foundation.Collections.IMap"), 24 | new Tuple( 25 | "System.Collections.Generic.IReadOnlyDictionary", "Windows.Foundation.Collections.IMapView"), 26 | new Tuple( 27 | "System.Collections.Generic.IList", "Windows.Foundation.Collections.IVector"), 28 | new Tuple( 29 | "System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken", "Windows.Foundation.EventRegistrationToken"), 30 | new Tuple( 31 | "System.DateTimeOffset", "Windows.Foundation.DateTime"), 32 | new Tuple( 33 | "System.TimeSpan", "Windows.Foundation.TimeSpan"), 34 | new Tuple( 35 | "System.Exception", "Windows.Foundation.HResult"), 36 | new Tuple( 37 | "System.Uri", "Windows.Foundation.Uri"), 38 | new Tuple( 39 | "System.EventHandler", "Windows.Foundation.EventHandler"), 40 | new Tuple( 41 | "System.Nullable", "Windows.Foundation.IReference"), 42 | new Tuple( 43 | "System.Collections.Specialized.INotifyCollectionChanged", "Windows.UI.Xaml.Interop.INotifyCollectionChanged"), 44 | new Tuple( 45 | "System.Collections.IList", "Windows.UI.Xaml.Interop.IBindableVector"), 46 | new Tuple( 47 | "System.Collections.IEnumerable", "Windows.UI.Xaml.Interop.IBindableIterable"), 48 | new Tuple( 49 | "System.Collections.Specialized.NotifyCollectionChangedAction", "Windows.UI.Xaml.Interop.NotifyCollectionChangedAction"), 50 | new Tuple( 51 | "System.Collections.Specialized.NotifyCollectionChangedEventHandler", "Windows.UI.Xaml.Interop.NotifyCollectionChangedEventHandler"), 52 | new Tuple( 53 | "System.Collections.Specialized.NotifyCollectionChangedEventArgs", "Windows.UI.Xaml.Interop.NotifyCollectionChangedEventArgs"), 54 | new Tuple( 55 | "System.Type", "Windows.UI.Xaml.Interop.TypeName"), 56 | new Tuple( 57 | "System.Numerics.Matrix4x4", "Windows.Foundation.Numerics.Matrix4x4") 58 | }; 59 | } 60 | 61 | public static bool AreAliases(string typeA, string typeB) 62 | { 63 | string[] templateA = GetTemplateArguments(typeA), 64 | templateB = GetTemplateArguments(typeB); 65 | 66 | var namesMatch = typeA == typeB; 67 | bool templatesMatch = templateA.Length == templateB.Length; 68 | 69 | if (templatesMatch) 70 | { 71 | for (int i = 0; i < templateA.Length; i++) 72 | { 73 | templatesMatch &= templateA[i] == templateB[i] || 74 | (templateA[i] != string.Empty && templateB[i] != string.Empty && AreAliases(templateA[i], templateB[i])); 75 | } 76 | } 77 | 78 | if (templateA.Length != 0) 79 | { 80 | // Character '`' denotes start of template arguments 81 | typeA = typeA.Substring(0, typeA.IndexOf('`')); 82 | } 83 | 84 | if (templateB.Length != 0) 85 | { 86 | typeB = typeB.Substring(0, typeB.IndexOf('`')); 87 | } 88 | 89 | var areAliases = typeA == typeB || 90 | _aliases.Contains(new Tuple(typeA, typeB)) || 91 | _aliases.Contains(new Tuple(typeB, typeA)); 92 | 93 | return namesMatch || (templatesMatch && areAliases); 94 | } 95 | 96 | public static string[] GetTemplateArguments(string type) 97 | { 98 | string template; 99 | 100 | int startIndex = type.IndexOf('<') + 1; 101 | int length = type.LastIndexOf('>') - startIndex; 102 | 103 | if (startIndex > 1 && length > 0) 104 | { 105 | template = type.Substring(startIndex, length); 106 | } 107 | else if (startIndex == 0 && length == -1) 108 | { 109 | template = string.Empty; 110 | } 111 | else 112 | { 113 | throw new ArgumentException("Invalid type name!", type); 114 | } 115 | 116 | var arguments = new List(); 117 | while (template.Length > 0) 118 | { 119 | int start = 0; 120 | uint depth = 0; 121 | string argument = null; 122 | do 123 | { 124 | int idx = template.IndexOfAny(new char[] {',', '<', '>'}, start); 125 | if (idx < 0) 126 | { 127 | argument = template; 128 | template = ""; 129 | } 130 | else 131 | { 132 | switch (template[idx]) 133 | { 134 | case ',': 135 | if (depth > 0) 136 | start = idx + 1; 137 | else 138 | { 139 | argument = template.Substring(0, idx); 140 | template = template.Remove(0, idx + 1); 141 | } 142 | break; 143 | case '<': 144 | ++depth; 145 | start = idx + 1; 146 | break; 147 | case '>': 148 | --depth; 149 | start = idx + 1; 150 | break; 151 | } 152 | } 153 | 154 | if (!string.IsNullOrEmpty(argument)) 155 | arguments.Add(argument.Trim()); 156 | } 157 | while (argument == null); 158 | } 159 | 160 | return arguments.ToArray(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Options; 6 | 7 | namespace Unity.ReferenceRewriter 8 | { 9 | static class Program 10 | { 11 | static int Main(string[] args) 12 | { 13 | var help = false; 14 | var targetModule = ""; 15 | var targetModuleOutput = ""; 16 | var supportModule = ""; 17 | var supportModulePartialNamespace = ""; 18 | var frameworkPath = ""; 19 | var projectLockFile = ""; 20 | var additionalReference = ""; 21 | var platformPath = ""; 22 | var systemNamespace = ""; 23 | var strongNamedReferences = ""; 24 | var winmdReferences = ""; 25 | var symbolFormat = DebugSymbolFormat.None; 26 | var alt = new Dictionary>(); 27 | var ignore = new Dictionary>(); 28 | 29 | var set = new OptionSet 30 | { 31 | { "target=", "The target module to rewrite.", t => targetModule = t }, 32 | { "output=", "Where to write the rewritten target module. Default is write over.", o => targetModuleOutput = o }, 33 | { "support=", "The support module containing the replacement API.", s => supportModule = s }, 34 | { "supportpartialns=", "Namespace in the support module that implements partial types.", s => supportModulePartialNamespace = s }, 35 | { "framework=", "A comma separated list of the directories of the target framework. Reference rewriter will assume that it will not process files in those directories", 36 | f => frameworkPath = f }, 37 | { "lock=", "Path to project.lock.json file.", l => projectLockFile = l }, 38 | { "additionalreferences=", "A comma separated list of the directories to reference additionally. Reference rewriter will assume that it will process files in those directories", 39 | ar => additionalReference = ar }, 40 | { "platform=", "Path to platform assembly.", p => platformPath = p }, 41 | { "system=", "The support namespace for System.", s => systemNamespace = s }, 42 | { "snrefs=", "A comma separated list of assembly names that retain their strong names.", s => strongNamedReferences = s }, 43 | { "winmdrefs=", "A comma separated list of assembly names that should be redirected to winmd references.", s => winmdReferences = s }, 44 | { "dbg=", "File format of the debug symbols. Either none, mdb or pdb.", d => symbolFormat = SymbolFormat(d) }, 45 | { "alt=", "A semicolon separated list of alternative namespace and assembly mappings.", a => AltFormat(alt, a) }, 46 | { "ignore=", "A semicolon separated list of assembly qualified type names that should not be resolved.", i => IgnoreFormat(ignore, i) }, 47 | 48 | { "?|h|help", h => help = true }, 49 | }; 50 | 51 | try 52 | { 53 | set.Parse(args); 54 | } 55 | catch (OptionException) 56 | { 57 | Usage(set); 58 | return 3; 59 | } 60 | 61 | if (help || new[] {targetModule, supportModule, systemNamespace }.Any(string.IsNullOrWhiteSpace) || new[] {frameworkPath, projectLockFile}.All(string.IsNullOrWhiteSpace)) 62 | { 63 | Usage(set); 64 | return 2; 65 | } 66 | 67 | try 68 | { 69 | var operation = RewriteOperation.Create( 70 | ns => ns.StartsWith("System") 71 | ? systemNamespace + ns.Substring("System".Length) 72 | : ns); 73 | 74 | var frameworkPaths = frameworkPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 75 | var additionalReferences = additionalReference.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 76 | var strongNamedReferencesArray = strongNamedReferences.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 77 | var winmdReferencesArray = winmdReferences.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 78 | var context = RewriteContext.For(targetModule, symbolFormat, supportModule, supportModulePartialNamespace, frameworkPaths, projectLockFile, additionalReferences, platformPath, strongNamedReferencesArray, winmdReferencesArray, alt, ignore); 79 | 80 | operation.Execute(context); 81 | 82 | if (context.RewriteTarget) 83 | context.Save(string.IsNullOrEmpty(targetModuleOutput) ? targetModule : targetModuleOutput); 84 | } 85 | catch (Exception e) 86 | { 87 | Console.WriteLine("Catastrophic failure while running rrw: {0}", e); 88 | return 1; 89 | } 90 | 91 | return 0; 92 | } 93 | 94 | private static DebugSymbolFormat SymbolFormat(string d) 95 | { 96 | return string.Equals(d, "mdb", StringComparison.OrdinalIgnoreCase) 97 | ? DebugSymbolFormat.Mdb 98 | : string.Equals(d, "pdb", StringComparison.OrdinalIgnoreCase) 99 | ? DebugSymbolFormat.Pdb 100 | : DebugSymbolFormat.None; 101 | } 102 | 103 | private static void AltFormat(IDictionary> alt, string a) 104 | { 105 | foreach (var pair in a.Split(';')) 106 | { 107 | var parts = pair.Split(new char[] { ',' }, 2); 108 | 109 | var @namespace = parts[0]; 110 | var assemblyName = @namespace; 111 | 112 | if (parts.Length > 1) 113 | assemblyName = parts[1]; 114 | 115 | IList assemblyNames; 116 | 117 | if (!alt.TryGetValue(@namespace, out assemblyNames)) 118 | { 119 | assemblyNames = new List(); 120 | alt.Add(@namespace, assemblyNames); 121 | } 122 | 123 | assemblyNames.Add(assemblyName); 124 | } 125 | } 126 | 127 | private static void IgnoreFormat(IDictionary> ignore, string i) 128 | { 129 | foreach (var pair in i.Split(';')) 130 | { 131 | var parts = pair.Split(new char[] { ',' }, 2); 132 | 133 | if (parts.Length != 2) 134 | throw new OptionException("Type name is not assembly qualified.", "ignore"); 135 | 136 | var typeName = parts[0]; 137 | var assemblyName = parts[1]; 138 | 139 | IList typeNames; 140 | 141 | if (!ignore.TryGetValue(assemblyName, out typeNames)) 142 | { 143 | typeNames = new List(); 144 | ignore.Add(assemblyName, typeNames); 145 | } 146 | 147 | typeNames.Add(typeName); 148 | } 149 | } 150 | 151 | private static void Usage(OptionSet set) 152 | { 153 | Console.WriteLine("rrw reference rewriter"); 154 | set.WriteOptionDescriptions(Console.Out); 155 | } 156 | 157 | public static string ConvertToWindowsPath(this string path) 158 | { 159 | return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /RewriteContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | using Mono.Cecil.Mdb; 7 | using Mono.Cecil.Pdb; 8 | using Mono.Collections.Generic; 9 | 10 | namespace Unity.ReferenceRewriter 11 | { 12 | public class RewriteContext 13 | { 14 | public bool RewriteTarget { get; set; } 15 | public ModuleDefinition TargetModule { get; private set; } 16 | public ModuleDefinition SupportModule { get; private set; } 17 | public string SupportModulePartialNamespace { get; private set; } 18 | public IDictionary AltModules { get; private set; } 19 | public IDictionary> IgnoredTypes { get; private set; } 20 | public string[] FrameworkPaths { get; private set; } 21 | public IAssemblyResolver AssemblyResolver { get; private set; } 22 | public Collection StrongNameReferences { get; private set; } 23 | public Collection WinmdReferences { get; private set; } 24 | public DebugSymbolFormat DebugSymbolFormat { get; private set; } 25 | 26 | public static RewriteContext For(string targetModule, DebugSymbolFormat symbolFormat, string supportModule, string supportModulePartialNamespace, 27 | string[] frameworkPaths, string projectLockFile, string[] additionalReferences, string platformPath, ICollection strongNamedReferences, 28 | ICollection winmdReferences, IDictionary> alt, IDictionary> ignore) 29 | { 30 | if (targetModule == null) 31 | throw new ArgumentNullException("targetModule"); 32 | if (supportModule == null) 33 | throw new ArgumentNullException("supportModule"); 34 | 35 | if (string.IsNullOrEmpty(projectLockFile)) 36 | CheckFrameworkPaths(frameworkPaths); 37 | 38 | var resolver = string.IsNullOrEmpty(projectLockFile) ? new SearchPathAssemblyResolver() : new NuGetAssemblyResolver(projectLockFile); 39 | 40 | var targetDirectory = Path.GetDirectoryName(targetModule); 41 | resolver.RegisterSearchPath(targetDirectory); 42 | 43 | foreach (var frameworkPath in frameworkPaths) 44 | { 45 | var fullFrameworkPath = Path.GetFullPath(frameworkPath); 46 | resolver.RegisterSearchPath(fullFrameworkPath); 47 | } 48 | 49 | foreach (var referenceDirectory in additionalReferences) 50 | { 51 | resolver.RegisterSearchPath(Path.GetFullPath(referenceDirectory)); 52 | } 53 | 54 | var support = ModuleDefinition.ReadModule(supportModule, new ReaderParameters {AssemblyResolver = resolver}); 55 | resolver.RegisterAssembly(support.Assembly); 56 | 57 | if (!string.IsNullOrEmpty(platformPath)) 58 | { 59 | var platform = ModuleDefinition.ReadModule(platformPath, new ReaderParameters {AssemblyResolver = resolver}); 60 | resolver.RegisterAssembly(platform.Assembly); 61 | } 62 | 63 | var altModules = new Dictionary(); 64 | 65 | foreach (var pair in alt) 66 | { 67 | var modules = new ModuleDefinition[pair.Value.Count]; 68 | 69 | for (var i = 0; i < modules.Length; ++i) 70 | modules[i] = resolver.Resolve(pair.Value[0], new ReaderParameters { AssemblyResolver = resolver }).MainModule; 71 | 72 | altModules.Add(pair.Key, modules); 73 | } 74 | 75 | var target = ModuleDefinition.ReadModule(targetModule, TargetModuleParameters(targetModule, symbolFormat, resolver)); 76 | resolver.RegisterAssembly(target.Assembly); 77 | 78 | return new RewriteContext 79 | { 80 | TargetModule = target, 81 | SupportModule = support, 82 | SupportModulePartialNamespace = supportModulePartialNamespace, 83 | AltModules = altModules, 84 | IgnoredTypes = ignore, 85 | FrameworkPaths = frameworkPaths, 86 | AssemblyResolver = resolver, 87 | StrongNameReferences = new Collection(strongNamedReferences), 88 | WinmdReferences = new Collection(winmdReferences), 89 | DebugSymbolFormat = symbolFormat, 90 | }; 91 | } 92 | 93 | private static void CheckFrameworkPath(string frameworkPath) 94 | { 95 | if (frameworkPath == null) 96 | throw new ArgumentNullException("frameworkPath"); 97 | if (string.IsNullOrEmpty(frameworkPath)) 98 | throw new ArgumentException("Empty framework path", "frameworkPath"); 99 | if (!Directory.Exists(frameworkPath)) 100 | throw new ArgumentException("Reference path doesn't exist.", "frameworkPath"); 101 | if (!File.Exists(Path.Combine(frameworkPath, "mscorlib.dll"))) 102 | throw new ArgumentException("No mscorlib.dll in the framework path.", "frameworkPath"); 103 | } 104 | 105 | private static void CheckFrameworkPaths(string[] frameworkPaths) 106 | { 107 | int timesFoundMscorlib = 0; 108 | foreach (var path in frameworkPaths) 109 | { 110 | try 111 | { 112 | CheckFrameworkPath(path); 113 | timesFoundMscorlib++; 114 | } 115 | catch (ArgumentException e) 116 | { 117 | if (!e.Message.Contains(@"No mscorlib.dll in the framework path.")) 118 | { 119 | throw; 120 | } 121 | } 122 | } 123 | 124 | if (timesFoundMscorlib == 0) 125 | { 126 | throw new ArgumentException("No mscorlib.dll in the framework path.", "frameworkPaths"); 127 | } 128 | } 129 | 130 | private static ReaderParameters TargetModuleParameters(string targetModule, DebugSymbolFormat symbolFormat, IAssemblyResolver resolver) 131 | { 132 | var targetParameters = new ReaderParameters { AssemblyResolver = resolver }; 133 | 134 | if (File.Exists(Path.ChangeExtension(targetModule, ".pdb")) && symbolFormat == DebugSymbolFormat.Pdb) 135 | targetParameters.SymbolReaderProvider = new PdbReaderProvider(); 136 | 137 | if (File.Exists(targetModule + ".mdb") && symbolFormat == DebugSymbolFormat.Mdb) 138 | targetParameters.SymbolReaderProvider = new MdbReaderProvider(); 139 | 140 | return targetParameters; 141 | } 142 | 143 | public void Save(string targetModule) 144 | { 145 | var parameters = new WriterParameters(); 146 | if (TargetModule.HasSymbols && DebugSymbolFormat != DebugSymbolFormat.None) 147 | parameters.SymbolWriterProvider = DebugSymbolFormat == DebugSymbolFormat.Mdb 148 | ? (ISymbolWriterProvider) new MdbWriterProvider() 149 | : new PdbWriterProvider(); 150 | 151 | TargetModule.Write(targetModule, parameters); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /ReferenceDispatcher.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | using System; 4 | 5 | namespace Unity.ReferenceRewriter 6 | { 7 | class ReferenceDispatcher 8 | { 9 | private readonly ModuleDefinition _module; 10 | private readonly IReferenceVisitor _visitor; 11 | 12 | private ReferenceDispatcher(ModuleDefinition module, IReferenceVisitor visitor) 13 | { 14 | _module = module; 15 | _visitor = visitor; 16 | } 17 | 18 | private void Dispatch() 19 | { 20 | foreach (var type in _module.GetTypes()) 21 | Dispatch(type); 22 | } 23 | 24 | private void Dispatch(TypeDefinition type) 25 | { 26 | Visit(type.BaseType, type.FullName); 27 | 28 | DispatchGenericParameters(type, type.FullName); 29 | DispatchInterfaces(type, type.FullName); 30 | DispatchAttributes(type, type.FullName); 31 | DispatchFields(type, type.FullName); 32 | DispatchProperties(type, type.FullName); 33 | DispatchEvents(type, type.FullName); 34 | DispatchMethods(type); 35 | } 36 | 37 | private void DispatchGenericParameters(IGenericParameterProvider provider, string referencingEntityName) 38 | { 39 | foreach (var parameter in provider.GenericParameters) 40 | { 41 | DispatchAttributes(parameter, referencingEntityName); 42 | 43 | foreach (var constraint in parameter.Constraints) 44 | Visit(constraint, referencingEntityName); 45 | } 46 | } 47 | 48 | private void DispatchMethods(TypeDefinition type) 49 | { 50 | foreach (var method in type.Methods) 51 | DispatchMethod(method); 52 | } 53 | 54 | private void DispatchMethod(MethodDefinition method) 55 | { 56 | Visit(method.ReturnType, method.FullName); 57 | DispatchAttributes(method.MethodReturnType, method.FullName); 58 | DispatchGenericParameters(method, method.FullName); 59 | 60 | foreach (var parameter in method.Parameters) 61 | { 62 | Visit(parameter.ParameterType, method.FullName); 63 | DispatchAttributes(parameter, method.FullName); 64 | } 65 | 66 | foreach (var ov in method.Overrides) 67 | Visit(ov, method.FullName); 68 | 69 | if (method.HasBody) 70 | DispatchMethodBody(method.Body); 71 | } 72 | 73 | private void DispatchMethodBody(MethodBody body) 74 | { 75 | foreach (var variable in body.Variables) 76 | Visit(variable.VariableType, body.Method.FullName); 77 | 78 | for (int i = 0; i < body.Instructions.Count; i++) 79 | { 80 | var instruction = body.Instructions[i]; 81 | var field = instruction.Operand as FieldReference; 82 | if (field != null) 83 | { 84 | Visit(field, body.Method.FullName); 85 | continue; 86 | } 87 | 88 | var method = instruction.Operand as MethodReference; 89 | if (method != null && !IsUnityScriptMethod(method)) 90 | { 91 | Visit(method, body.Method.FullName); 92 | if (_visitor.MethodChanged) 93 | { 94 | _visitor.RewriteObjectListToParamsCall(body, i); 95 | } 96 | 97 | // Can't callvirt on static methods 98 | if (instruction.OpCode == OpCodes.Callvirt) 99 | { 100 | var targetMethod = instruction.Operand as MethodReference; 101 | 102 | if (targetMethod != null && !targetMethod.HasThis) 103 | { 104 | instruction.OpCode = OpCodes.Call; 105 | } 106 | } 107 | 108 | continue; 109 | } 110 | 111 | var type = instruction.Operand as TypeReference; 112 | if (type != null) 113 | Visit(type, body.Method.FullName); 114 | } 115 | } 116 | 117 | private void DispatchGenericArguments(IGenericInstance genericInstance, string referencingEntityName) 118 | { 119 | foreach (var argument in genericInstance.GenericArguments) 120 | Visit(argument, referencingEntityName); 121 | } 122 | 123 | private void DispatchInterfaces(TypeDefinition type, string referencingEntityName) 124 | { 125 | foreach (var iface in type.Interfaces) 126 | Visit(iface, referencingEntityName); 127 | } 128 | 129 | private void DispatchEvents(TypeDefinition type, string referencingEntityName) 130 | { 131 | foreach (var evt in type.Events) 132 | { 133 | Visit(evt.EventType, referencingEntityName); 134 | DispatchAttributes(evt, referencingEntityName); 135 | } 136 | } 137 | 138 | private void DispatchProperties(TypeDefinition type, string referencingEntityName) 139 | { 140 | foreach (var property in type.Properties) 141 | { 142 | Visit(property.PropertyType, referencingEntityName); 143 | DispatchAttributes(property, referencingEntityName); 144 | } 145 | } 146 | 147 | private void DispatchFields(TypeDefinition type, string referencingEntityName) 148 | { 149 | foreach (var field in type.Fields) 150 | { 151 | Visit(field.FieldType, referencingEntityName); 152 | DispatchAttributes(field, referencingEntityName); 153 | } 154 | } 155 | 156 | private void DispatchAttributes(ICustomAttributeProvider provider, string referencingEntityName) 157 | { 158 | if (!provider.HasCustomAttributes) 159 | return; 160 | 161 | foreach (var attribute in provider.CustomAttributes) 162 | DispatchAttribute(attribute, referencingEntityName); 163 | } 164 | 165 | private void DispatchAttribute(CustomAttribute attribute, string referencingEntityName) 166 | { 167 | Visit(attribute.Constructor, referencingEntityName); 168 | 169 | foreach (var argument in attribute.ConstructorArguments) 170 | DispatchCustomAttributeArgument(argument, referencingEntityName); 171 | 172 | foreach (var namedArgument in attribute.Properties) 173 | DispatchCustomAttributeArgument(namedArgument.Argument, referencingEntityName); 174 | 175 | foreach (var namedArgument in attribute.Fields) 176 | DispatchCustomAttributeArgument(namedArgument.Argument, referencingEntityName); 177 | } 178 | 179 | private void DispatchCustomAttributeArgument(CustomAttributeArgument argument, string referencingEntityName) 180 | { 181 | var reference = argument.Value as TypeReference; 182 | if (reference == null) 183 | return; 184 | 185 | Visit(reference, referencingEntityName); 186 | } 187 | 188 | private void Visit(MethodReference method, string referencingEntityName) 189 | { 190 | if (method == null) 191 | return; 192 | 193 | var genericInstance = method as GenericInstanceMethod; 194 | if (genericInstance != null) 195 | DispatchGenericArguments(genericInstance, referencingEntityName); 196 | 197 | Visit(method.DeclaringType, referencingEntityName); 198 | Visit(method.ReturnType, referencingEntityName); 199 | 200 | foreach (var parameter in method.Parameters) 201 | Visit(parameter.ParameterType, referencingEntityName); 202 | 203 | _visitor.Visit(method, referencingEntityName); 204 | } 205 | 206 | private void Visit(FieldReference field, string referencingEntityName) 207 | { 208 | if (field == null) 209 | return; 210 | 211 | Visit(field.DeclaringType, referencingEntityName); 212 | Visit(field.FieldType, referencingEntityName); 213 | 214 | if (field.DeclaringType.Scope == _module) 215 | return; 216 | 217 | _visitor.Visit(field, referencingEntityName); 218 | } 219 | 220 | private void Visit(TypeReference type, string referencingEntityName) 221 | { 222 | if (type == null) 223 | return; 224 | 225 | if (type.Scope == _module) 226 | return; 227 | 228 | if (type.GetElementType().IsGenericParameter) 229 | return; 230 | 231 | var genericInstance = type as GenericInstanceType; 232 | if (genericInstance != null) 233 | DispatchGenericArguments(genericInstance, referencingEntityName); 234 | 235 | _visitor.Visit(type.GetElementType(), referencingEntityName); 236 | } 237 | 238 | public static void DispatchOn(ModuleDefinition module, IReferenceVisitor visitor) 239 | { 240 | new ReferenceDispatcher(module, visitor).Dispatch(); 241 | } 242 | 243 | public static bool IsUnityScriptMethod(MethodReference method) 244 | { 245 | var ret = false; 246 | var type = method.DeclaringType.FullName; 247 | 248 | if (type.StartsWith("UnityScript.Lang")) 249 | { 250 | ret = true; 251 | } 252 | 253 | return ret; 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /EnumeratorGenericConstraintsFixer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Mono.Cecil; 3 | using System.Linq; 4 | 5 | namespace Unity.ReferenceRewriter 6 | { 7 | /* 8 | * There's a bug in Mono compiler that makes this code throw a runtime exception when running under .NET: 9 | * 10 | * public class SomeBaseClass {} 11 | * public class SomeChildClass : SomeBaseClass {} 12 | * public delegate void ResourceReady(int id, TType resource) where TType : SomeBaseClass; 13 | * 14 | * public abstract class Base 15 | * { 16 | * public abstract IEnumerator GetResource(int count, ResourceReady resourceReadyCallback) where TType : SomeBaseClass; 17 | * } 18 | * 19 | * public class Child : Base 20 | * { 21 | * public override IEnumerator GetResource(int count, ResourceReady resourceReadyCallback) 22 | * { 23 | * for (int i = 0; i < count; i++) 24 | * { 25 | * var item = default(TType); 26 | * resourceReadyCallback(i, item); 27 | * yield return item; 28 | * } 29 | * } 30 | * } 31 | * 32 | * public class Test 33 | * { 34 | * void Test() 35 | * { 36 | * SomeBaseClass item = new SomeChildClass(); 37 | * var enumerator = item.GetResource(3, Callback); 38 | * while (enumerator.MoveNext()) { } 39 | * } 40 | * } 41 | * 42 | * void Callback(int id, SomeChildClass resource) { } 43 | * 44 | * The exception reads: 45 | * 46 | * Exception: GenericArguments[0], 'TType', on 'ResourceReady`1[TType]' violates the constraint of type parameter 'TType'. 47 | * Compiling with Microsoft C# compiler, doesn't have the same behaviour and generates slightly different code. 48 | * This happens only when all conditions are met: 49 | * 50 | * 1. There's a base class with a method which has a generic parameter; 51 | * 2. That generic parameter has a constraint; 52 | * 3. The method returns an IEnumerator; 53 | * 4. A method also takes a delegate which has the same generic parameter; 54 | * 5. There's a child class that inherits from base class and overrides that method 55 | * 56 | * The bug itself happens on a compiler generated inner class of Child for Enumerator. It is a generic class, and should contain 57 | * constraint, but it doesn't: 58 | * 59 | * .class nested private auto ansi sealed beforefieldinit 'c__Iterator0`1' 60 | * extends [mscorlib]System.Object 61 | * implements [mscorlib]System.Collections.IEnumerator, 62 | * class [mscorlib]System.Collections.Generic.IEnumerator`1, 63 | * [mscorlib]System.IDisposable 64 | * 65 | * The task of this class is find such methods and modify them to look like this: 66 | * 67 | * .class nested private auto ansi sealed beforefieldinit 'c__Iterator0`1'<(SomeBaseClass) TType> 68 | * extends [mscorlib]System.Object 69 | * implements [mscorlib]System.Collections.IEnumerator, 70 | * class [mscorlib]System.Collections.Generic.IEnumerator`1, 71 | * [mscorlib]System.IDisposable 72 | */ 73 | class EnumeratorGenericConstraintsFixer : RewriteStep, IMethodDefinitionVisitor 74 | { 75 | protected override void Run() 76 | { 77 | MethodDefinitionDispatcher.DispatchOn(Context.TargetModule, this); 78 | } 79 | 80 | public void Visit(MethodDefinition method) 81 | { 82 | if (IsBroken(method)) 83 | { 84 | Fix(method); 85 | } 86 | } 87 | 88 | private bool IsBroken(MethodDefinition method) 89 | { 90 | if (!method.HasBody) 91 | { 92 | return false; 93 | } 94 | 95 | if (!method.HasGenericParameters) 96 | { 97 | return false; 98 | } 99 | 100 | if (!method.IsVirtual || method.IsAbstract) 101 | { 102 | return false; 103 | } 104 | 105 | if (!method.ReturnType.FullName.Equals("System.Collections.IEnumerator")) 106 | { 107 | return false; 108 | } 109 | 110 | if (method.GenericParameters.All(x => x.Constraints.Count == 0 && !x.HasDefaultConstructorConstraint 111 | && !x.HasNotNullableValueTypeConstraint && !x.HasReferenceTypeConstraint)) 112 | { 113 | return false; 114 | } 115 | 116 | var overridenMethod = GetOverridenMethod(method); 117 | 118 | if (overridenMethod == null) 119 | { 120 | return false; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | private MethodDefinition GetOverridenMethod(MethodDefinition overridingMethod) 127 | { 128 | MethodDefinition overridenMethod = null; 129 | var declaringType = overridingMethod.DeclaringType; 130 | 131 | while (true) 132 | { 133 | if (declaringType.BaseType == null) 134 | { 135 | return overridenMethod; 136 | } 137 | 138 | var declaringBaseType = declaringType.BaseType.Resolve(); 139 | var baseMethodName = overridingMethod.FullName.Replace(overridingMethod.DeclaringType.FullName + "::", 140 | declaringBaseType.FullName + "::"); 141 | var methodInBaseType = declaringBaseType.Methods.FirstOrDefault(x => x.FullName == baseMethodName); 142 | 143 | if (methodInBaseType != null) 144 | { 145 | overridenMethod = methodInBaseType; 146 | } 147 | 148 | declaringType = declaringBaseType; 149 | } 150 | } 151 | 152 | private void Fix(MethodDefinition method) 153 | { 154 | var iteratorClass = FindIteratorClass(method); 155 | 156 | if (iteratorClass == null) 157 | { 158 | return; 159 | } 160 | 161 | // Generic parameters are matched by their name, not index 162 | foreach (var parameter in method.GenericParameters) 163 | { 164 | var methodParameter = parameter; 165 | foreach (var classParameter in iteratorClass.GenericParameters.Where(x => x.FullName == methodParameter.FullName)) 166 | { 167 | ChangeConstraintAttributesIfNeeded(methodParameter, classParameter); 168 | 169 | foreach (var constraint in methodParameter.Constraints) 170 | { 171 | if (!classParameter.Constraints.Contains(constraint)) 172 | { 173 | classParameter.Constraints.Add(constraint); 174 | Context.RewriteTarget = true; 175 | } 176 | } 177 | } 178 | } 179 | } 180 | 181 | private TypeDefinition FindIteratorClass(MethodDefinition method) 182 | { 183 | var declaringType = method.DeclaringType; 184 | 185 | foreach (var nestedType in declaringType.NestedTypes) 186 | { 187 | if (!nestedType.Name.Contains(method.Name)) 188 | { 189 | continue; 190 | } 191 | 192 | if (!nestedType.HasGenericParameters) 193 | { 194 | continue; 195 | } 196 | 197 | if (nestedType.GenericParameters.Count < method.GenericParameters.Count) 198 | { 199 | continue; 200 | } 201 | 202 | foreach (var methodParameter in method.GenericParameters) 203 | { 204 | if (nestedType.GenericParameters.All(x => x.Name != methodParameter.Name)) 205 | { 206 | continue; 207 | } 208 | } 209 | 210 | if (method.Body.Variables.All(x => x.VariableType.Name != nestedType.Name)) 211 | { 212 | continue; 213 | } 214 | 215 | return nestedType; 216 | } 217 | 218 | return null; 219 | } 220 | 221 | private void ChangeConstraintAttributesIfNeeded(GenericParameter source, GenericParameter target) 222 | { 223 | if (target.HasDefaultConstructorConstraint != source.HasDefaultConstructorConstraint) 224 | { 225 | target.HasDefaultConstructorConstraint = source.HasDefaultConstructorConstraint; 226 | Context.RewriteTarget = true; 227 | } 228 | 229 | if (target.HasNotNullableValueTypeConstraint != source.HasNotNullableValueTypeConstraint) 230 | { 231 | target.HasNotNullableValueTypeConstraint = source.HasNotNullableValueTypeConstraint; 232 | Context.RewriteTarget = true; 233 | } 234 | 235 | if (target.HasReferenceTypeConstraint != source.HasReferenceTypeConstraint) 236 | { 237 | target.HasReferenceTypeConstraint = source.HasReferenceTypeConstraint; 238 | Context.RewriteTarget = true; 239 | } 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /rrw.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rrw", "rrw.csproj", "{07911F00-C501-4F6D-A327-D51778FA10BB}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Support", "Test\Test.Support\Test.Support.csproj", "{928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Target", "Test\Test.Target\Test.Target.csproj", "{F4896833-9783-490F-B7A1-12C550AF43BA}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Driver", "Test\Test.Driver\Test.Driver.csproj", "{B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.UnitTests", "Test\Test.UnitTests\Test.UnitTests.csproj", "{B3B269A8-E3BB-4FB2-987A-A66C516E39EA}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | net_2_0_Debug|Any CPU = net_2_0_Debug|Any CPU 18 | net_2_0_Release|Any CPU = net_2_0_Release|Any CPU 19 | net_3_5_Debug|Any CPU = net_3_5_Debug|Any CPU 20 | net_3_5_Release|Any CPU = net_3_5_Release|Any CPU 21 | net_4_0_Debug|Any CPU = net_4_0_Debug|Any CPU 22 | net_4_0_Release|Any CPU = net_4_0_Release|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | silverlight_Debug|Any CPU = silverlight_Debug|Any CPU 25 | silverlight_Release|Any CPU = silverlight_Release|Any CPU 26 | winphone_Debug|Any CPU = winphone_Debug|Any CPU 27 | winphone_Release|Any CPU = winphone_Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {07911F00-C501-4F6D-A327-D51778FA10BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {07911F00-C501-4F6D-A327-D51778FA10BB}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_2_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_2_0_Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_2_0_Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_2_0_Release|Any CPU.Build.0 = Release|Any CPU 36 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_3_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_3_5_Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_3_5_Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_3_5_Release|Any CPU.Build.0 = Release|Any CPU 40 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_4_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_4_0_Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_4_0_Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {07911F00-C501-4F6D-A327-D51778FA10BB}.net_4_0_Release|Any CPU.Build.0 = Release|Any CPU 44 | {07911F00-C501-4F6D-A327-D51778FA10BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {07911F00-C501-4F6D-A327-D51778FA10BB}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {07911F00-C501-4F6D-A327-D51778FA10BB}.silverlight_Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {07911F00-C501-4F6D-A327-D51778FA10BB}.silverlight_Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {07911F00-C501-4F6D-A327-D51778FA10BB}.silverlight_Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {07911F00-C501-4F6D-A327-D51778FA10BB}.silverlight_Release|Any CPU.Build.0 = Release|Any CPU 50 | {07911F00-C501-4F6D-A327-D51778FA10BB}.winphone_Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {07911F00-C501-4F6D-A327-D51778FA10BB}.winphone_Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {07911F00-C501-4F6D-A327-D51778FA10BB}.winphone_Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {07911F00-C501-4F6D-A327-D51778FA10BB}.winphone_Release|Any CPU.Build.0 = Release|Any CPU 54 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_2_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_2_0_Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_2_0_Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_2_0_Release|Any CPU.Build.0 = Release|Any CPU 60 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_3_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_3_5_Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_3_5_Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_3_5_Release|Any CPU.Build.0 = Release|Any CPU 64 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_4_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_4_0_Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_4_0_Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.net_4_0_Release|Any CPU.Build.0 = Release|Any CPU 68 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.silverlight_Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.silverlight_Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.silverlight_Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.silverlight_Release|Any CPU.Build.0 = Release|Any CPU 74 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.winphone_Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.winphone_Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.winphone_Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {928DC014-F1CE-4B1B-BAD4-A7B9E33F8AB9}.winphone_Release|Any CPU.Build.0 = Release|Any CPU 78 | {F4896833-9783-490F-B7A1-12C550AF43BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {F4896833-9783-490F-B7A1-12C550AF43BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_2_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_2_0_Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_2_0_Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_2_0_Release|Any CPU.Build.0 = Release|Any CPU 84 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_3_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_3_5_Debug|Any CPU.Build.0 = Debug|Any CPU 86 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_3_5_Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_3_5_Release|Any CPU.Build.0 = Release|Any CPU 88 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_4_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_4_0_Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_4_0_Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {F4896833-9783-490F-B7A1-12C550AF43BA}.net_4_0_Release|Any CPU.Build.0 = Release|Any CPU 92 | {F4896833-9783-490F-B7A1-12C550AF43BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {F4896833-9783-490F-B7A1-12C550AF43BA}.Release|Any CPU.Build.0 = Release|Any CPU 94 | {F4896833-9783-490F-B7A1-12C550AF43BA}.silverlight_Debug|Any CPU.ActiveCfg = Debug|Any CPU 95 | {F4896833-9783-490F-B7A1-12C550AF43BA}.silverlight_Debug|Any CPU.Build.0 = Debug|Any CPU 96 | {F4896833-9783-490F-B7A1-12C550AF43BA}.silverlight_Release|Any CPU.ActiveCfg = Release|Any CPU 97 | {F4896833-9783-490F-B7A1-12C550AF43BA}.silverlight_Release|Any CPU.Build.0 = Release|Any CPU 98 | {F4896833-9783-490F-B7A1-12C550AF43BA}.winphone_Debug|Any CPU.ActiveCfg = Debug|Any CPU 99 | {F4896833-9783-490F-B7A1-12C550AF43BA}.winphone_Debug|Any CPU.Build.0 = Debug|Any CPU 100 | {F4896833-9783-490F-B7A1-12C550AF43BA}.winphone_Release|Any CPU.ActiveCfg = Release|Any CPU 101 | {F4896833-9783-490F-B7A1-12C550AF43BA}.winphone_Release|Any CPU.Build.0 = Release|Any CPU 102 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 103 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.Debug|Any CPU.Build.0 = Debug|Any CPU 104 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_2_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 105 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_2_0_Debug|Any CPU.Build.0 = Debug|Any CPU 106 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_2_0_Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_2_0_Release|Any CPU.Build.0 = Release|Any CPU 108 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_3_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 109 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_3_5_Debug|Any CPU.Build.0 = Debug|Any CPU 110 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_3_5_Release|Any CPU.ActiveCfg = Release|Any CPU 111 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_3_5_Release|Any CPU.Build.0 = Release|Any CPU 112 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_4_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 113 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_4_0_Debug|Any CPU.Build.0 = Debug|Any CPU 114 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_4_0_Release|Any CPU.ActiveCfg = Release|Any CPU 115 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.net_4_0_Release|Any CPU.Build.0 = Release|Any CPU 116 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.Release|Any CPU.ActiveCfg = Release|Any CPU 117 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.Release|Any CPU.Build.0 = Release|Any CPU 118 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.silverlight_Debug|Any CPU.ActiveCfg = Debug|Any CPU 119 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.silverlight_Debug|Any CPU.Build.0 = Debug|Any CPU 120 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.silverlight_Release|Any CPU.ActiveCfg = Release|Any CPU 121 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.silverlight_Release|Any CPU.Build.0 = Release|Any CPU 122 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.winphone_Debug|Any CPU.ActiveCfg = Debug|Any CPU 123 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.winphone_Debug|Any CPU.Build.0 = Debug|Any CPU 124 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.winphone_Release|Any CPU.ActiveCfg = Release|Any CPU 125 | {B9E18B17-DD4D-457D-A7A1-1F1BE28B7A81}.winphone_Release|Any CPU.Build.0 = Release|Any CPU 126 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 127 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 128 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_2_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 129 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_2_0_Debug|Any CPU.Build.0 = Debug|Any CPU 130 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_2_0_Release|Any CPU.ActiveCfg = Release|Any CPU 131 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_2_0_Release|Any CPU.Build.0 = Release|Any CPU 132 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_3_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 133 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_3_5_Debug|Any CPU.Build.0 = Debug|Any CPU 134 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_3_5_Release|Any CPU.ActiveCfg = Release|Any CPU 135 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_3_5_Release|Any CPU.Build.0 = Release|Any CPU 136 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_4_0_Debug|Any CPU.ActiveCfg = Debug|Any CPU 137 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_4_0_Debug|Any CPU.Build.0 = Debug|Any CPU 138 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_4_0_Release|Any CPU.ActiveCfg = Release|Any CPU 139 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.net_4_0_Release|Any CPU.Build.0 = Release|Any CPU 140 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 141 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.Release|Any CPU.Build.0 = Release|Any CPU 142 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.silverlight_Debug|Any CPU.ActiveCfg = Debug|Any CPU 143 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.silverlight_Debug|Any CPU.Build.0 = Debug|Any CPU 144 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.silverlight_Release|Any CPU.ActiveCfg = Release|Any CPU 145 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.silverlight_Release|Any CPU.Build.0 = Release|Any CPU 146 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.winphone_Debug|Any CPU.ActiveCfg = Debug|Any CPU 147 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.winphone_Debug|Any CPU.Build.0 = Debug|Any CPU 148 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.winphone_Release|Any CPU.ActiveCfg = Release|Any CPU 149 | {B3B269A8-E3BB-4FB2-987A-A66C516E39EA}.winphone_Release|Any CPU.Build.0 = Release|Any CPU 150 | EndGlobalSection 151 | GlobalSection(SolutionProperties) = preSolution 152 | HideSolutionNode = FALSE 153 | EndGlobalSection 154 | EndGlobal 155 | -------------------------------------------------------------------------------- /MiniJSON.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Calvin Rien 3 | * 4 | * Based on the JSON parser by Patrick van Bergen 5 | * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html 6 | * 7 | * Simplified it so that it doesn't throw exceptions 8 | * and can be used in Unity iPhone with maximum code stripping. 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining 11 | * a copy of this software and associated documentation files (the 12 | * "Software"), to deal in the Software without restriction, including 13 | * without limitation the rights to use, copy, modify, merge, publish, 14 | * distribute, sublicense, and/or sell copies of the Software, and to 15 | * permit persons to whom the Software is furnished to do so, subject to 16 | * the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | */ 29 | using System; 30 | using System.Collections; 31 | using System.Collections.Generic; 32 | using System.IO; 33 | using System.Text; 34 | 35 | namespace MiniJSON { 36 | // Example usage: 37 | // 38 | // using UnityEngine; 39 | // using System.Collections; 40 | // using System.Collections.Generic; 41 | // using MiniJSON; 42 | // 43 | // public class MiniJSONTest : MonoBehaviour { 44 | // void Start () { 45 | // var jsonString = "{ \"array\": [1.44,2,3], " + 46 | // "\"object\": {\"key1\":\"value1\", \"key2\":256}, " + 47 | // "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " + 48 | // "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " + 49 | // "\"int\": 65536, " + 50 | // "\"float\": 3.1415926, " + 51 | // "\"bool\": true, " + 52 | // "\"null\": null }"; 53 | // 54 | // var dict = Json.Deserialize(jsonString) as Dictionary; 55 | // 56 | // Debug.Log("deserialized: " + dict.GetType()); 57 | // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); 58 | // Debug.Log("dict['string']: " + (string) dict["string"]); 59 | // Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles 60 | // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs 61 | // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); 62 | // 63 | // var str = Json.Serialize(dict); 64 | // 65 | // Debug.Log("serialized: " + str); 66 | // } 67 | // } 68 | 69 | /// 70 | /// This class encodes and decodes JSON strings. 71 | /// Spec. details, see http://www.json.org/ 72 | /// 73 | /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. 74 | /// All numbers are parsed to doubles. 75 | /// 76 | public static class Json { 77 | /// 78 | /// Parses the string json into a value 79 | /// 80 | /// A JSON string. 81 | /// An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false 82 | public static object Deserialize(string json) { 83 | // save the string for debug information 84 | if (json == null) { 85 | return null; 86 | } 87 | 88 | return Parser.Parse(json); 89 | } 90 | 91 | sealed class Parser : IDisposable { 92 | const string WORD_BREAK = "{}[],:\""; 93 | 94 | public static bool IsWordBreak(char c) { 95 | return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; 96 | } 97 | 98 | enum TOKEN { 99 | NONE, 100 | CURLY_OPEN, 101 | CURLY_CLOSE, 102 | SQUARED_OPEN, 103 | SQUARED_CLOSE, 104 | COLON, 105 | COMMA, 106 | STRING, 107 | NUMBER, 108 | TRUE, 109 | FALSE, 110 | NULL 111 | }; 112 | 113 | StringReader json; 114 | 115 | Parser(string jsonString) { 116 | json = new StringReader(jsonString); 117 | } 118 | 119 | public static object Parse(string jsonString) { 120 | using (var instance = new Parser(jsonString)) { 121 | return instance.ParseValue(); 122 | } 123 | } 124 | 125 | public void Dispose() { 126 | json.Dispose(); 127 | json = null; 128 | } 129 | 130 | Dictionary ParseObject() { 131 | Dictionary table = new Dictionary(); 132 | 133 | // ditch opening brace 134 | json.Read(); 135 | 136 | // { 137 | while (true) { 138 | switch (NextToken) { 139 | case TOKEN.NONE: 140 | return null; 141 | case TOKEN.COMMA: 142 | continue; 143 | case TOKEN.CURLY_CLOSE: 144 | return table; 145 | default: 146 | // name 147 | string name = ParseString(); 148 | if (name == null) { 149 | return null; 150 | } 151 | 152 | // : 153 | if (NextToken != TOKEN.COLON) { 154 | return null; 155 | } 156 | // ditch the colon 157 | json.Read(); 158 | 159 | // value 160 | table[name] = ParseValue(); 161 | break; 162 | } 163 | } 164 | } 165 | 166 | List ParseArray() { 167 | List array = new List(); 168 | 169 | // ditch opening bracket 170 | json.Read(); 171 | 172 | // [ 173 | var parsing = true; 174 | while (parsing) { 175 | TOKEN nextToken = NextToken; 176 | 177 | switch (nextToken) { 178 | case TOKEN.NONE: 179 | return null; 180 | case TOKEN.COMMA: 181 | continue; 182 | case TOKEN.SQUARED_CLOSE: 183 | parsing = false; 184 | break; 185 | default: 186 | object value = ParseByToken(nextToken); 187 | 188 | array.Add(value); 189 | break; 190 | } 191 | } 192 | 193 | return array; 194 | } 195 | 196 | object ParseValue() { 197 | TOKEN nextToken = NextToken; 198 | return ParseByToken(nextToken); 199 | } 200 | 201 | object ParseByToken(TOKEN token) { 202 | switch (token) { 203 | case TOKEN.STRING: 204 | return ParseString(); 205 | case TOKEN.NUMBER: 206 | return ParseNumber(); 207 | case TOKEN.CURLY_OPEN: 208 | return ParseObject(); 209 | case TOKEN.SQUARED_OPEN: 210 | return ParseArray(); 211 | case TOKEN.TRUE: 212 | return true; 213 | case TOKEN.FALSE: 214 | return false; 215 | case TOKEN.NULL: 216 | return null; 217 | default: 218 | return null; 219 | } 220 | } 221 | 222 | string ParseString() { 223 | StringBuilder s = new StringBuilder(); 224 | char c; 225 | 226 | // ditch opening quote 227 | json.Read(); 228 | 229 | bool parsing = true; 230 | while (parsing) { 231 | 232 | if (json.Peek() == -1) { 233 | parsing = false; 234 | break; 235 | } 236 | 237 | c = NextChar; 238 | switch (c) { 239 | case '"': 240 | parsing = false; 241 | break; 242 | case '\\': 243 | if (json.Peek() == -1) { 244 | parsing = false; 245 | break; 246 | } 247 | 248 | c = NextChar; 249 | switch (c) { 250 | case '"': 251 | case '\\': 252 | case '/': 253 | s.Append(c); 254 | break; 255 | case 'b': 256 | s.Append('\b'); 257 | break; 258 | case 'f': 259 | s.Append('\f'); 260 | break; 261 | case 'n': 262 | s.Append('\n'); 263 | break; 264 | case 'r': 265 | s.Append('\r'); 266 | break; 267 | case 't': 268 | s.Append('\t'); 269 | break; 270 | case 'u': 271 | var hex = new char[4]; 272 | 273 | for (int i=0; i< 4; i++) { 274 | hex[i] = NextChar; 275 | } 276 | 277 | s.Append((char) Convert.ToInt32(new string(hex), 16)); 278 | break; 279 | } 280 | break; 281 | default: 282 | s.Append(c); 283 | break; 284 | } 285 | } 286 | 287 | return s.ToString(); 288 | } 289 | 290 | object ParseNumber() { 291 | string number = NextWord; 292 | 293 | if (number.IndexOf('.') == -1) { 294 | long parsedInt; 295 | Int64.TryParse(number, out parsedInt); 296 | return parsedInt; 297 | } 298 | 299 | double parsedDouble; 300 | Double.TryParse(number, out parsedDouble); 301 | return parsedDouble; 302 | } 303 | 304 | void EatWhitespace() { 305 | while (Char.IsWhiteSpace(PeekChar)) { 306 | json.Read(); 307 | 308 | if (json.Peek() == -1) { 309 | break; 310 | } 311 | } 312 | } 313 | 314 | char PeekChar { 315 | get { 316 | return Convert.ToChar(json.Peek()); 317 | } 318 | } 319 | 320 | char NextChar { 321 | get { 322 | return Convert.ToChar(json.Read()); 323 | } 324 | } 325 | 326 | string NextWord { 327 | get { 328 | StringBuilder word = new StringBuilder(); 329 | 330 | while (!IsWordBreak(PeekChar)) { 331 | word.Append(NextChar); 332 | 333 | if (json.Peek() == -1) { 334 | break; 335 | } 336 | } 337 | 338 | return word.ToString(); 339 | } 340 | } 341 | 342 | TOKEN NextToken { 343 | get { 344 | EatWhitespace(); 345 | 346 | if (json.Peek() == -1) { 347 | return TOKEN.NONE; 348 | } 349 | 350 | switch (PeekChar) { 351 | case '{': 352 | return TOKEN.CURLY_OPEN; 353 | case '}': 354 | json.Read(); 355 | return TOKEN.CURLY_CLOSE; 356 | case '[': 357 | return TOKEN.SQUARED_OPEN; 358 | case ']': 359 | json.Read(); 360 | return TOKEN.SQUARED_CLOSE; 361 | case ',': 362 | json.Read(); 363 | return TOKEN.COMMA; 364 | case '"': 365 | return TOKEN.STRING; 366 | case ':': 367 | return TOKEN.COLON; 368 | case '0': 369 | case '1': 370 | case '2': 371 | case '3': 372 | case '4': 373 | case '5': 374 | case '6': 375 | case '7': 376 | case '8': 377 | case '9': 378 | case '-': 379 | return TOKEN.NUMBER; 380 | } 381 | 382 | switch (NextWord) { 383 | case "false": 384 | return TOKEN.FALSE; 385 | case "true": 386 | return TOKEN.TRUE; 387 | case "null": 388 | return TOKEN.NULL; 389 | } 390 | 391 | return TOKEN.NONE; 392 | } 393 | } 394 | } 395 | 396 | /// 397 | /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string 398 | /// 399 | /// A Dictionary<string, object> / List<object> 400 | /// A JSON encoded string, or null if object 'json' is not serializable 401 | public static string Serialize(object obj) { 402 | return Serializer.Serialize(obj); 403 | } 404 | 405 | sealed class Serializer { 406 | StringBuilder builder; 407 | 408 | Serializer() { 409 | builder = new StringBuilder(); 410 | } 411 | 412 | public static string Serialize(object obj) { 413 | var instance = new Serializer(); 414 | 415 | instance.SerializeValue(obj); 416 | 417 | return instance.builder.ToString(); 418 | } 419 | 420 | void SerializeValue(object value) { 421 | IList asList; 422 | IDictionary asDict; 423 | string asStr; 424 | 425 | if (value == null) { 426 | builder.Append("null"); 427 | } else if ((asStr = value as string) != null) { 428 | SerializeString(asStr); 429 | } else if (value is bool) { 430 | builder.Append((bool) value ? "true" : "false"); 431 | } else if ((asList = value as IList) != null) { 432 | SerializeArray(asList); 433 | } else if ((asDict = value as IDictionary) != null) { 434 | SerializeObject(asDict); 435 | } else if (value is char) { 436 | SerializeString(new string((char) value, 1)); 437 | } else { 438 | SerializeOther(value); 439 | } 440 | } 441 | 442 | void SerializeObject(IDictionary obj) { 443 | bool first = true; 444 | 445 | builder.Append('{'); 446 | 447 | foreach (object e in obj.Keys) { 448 | if (!first) { 449 | builder.Append(','); 450 | } 451 | 452 | SerializeString(e.ToString()); 453 | builder.Append(':'); 454 | 455 | SerializeValue(obj[e]); 456 | 457 | first = false; 458 | } 459 | 460 | builder.Append('}'); 461 | } 462 | 463 | void SerializeArray(IList anArray) { 464 | builder.Append('['); 465 | 466 | bool first = true; 467 | 468 | foreach (object obj in anArray) { 469 | if (!first) { 470 | builder.Append(','); 471 | } 472 | 473 | SerializeValue(obj); 474 | 475 | first = false; 476 | } 477 | 478 | builder.Append(']'); 479 | } 480 | 481 | void SerializeString(string str) { 482 | builder.Append('\"'); 483 | 484 | char[] charArray = str.ToCharArray(); 485 | foreach (var c in charArray) { 486 | switch (c) { 487 | case '"': 488 | builder.Append("\\\""); 489 | break; 490 | case '\\': 491 | builder.Append("\\\\"); 492 | break; 493 | case '\b': 494 | builder.Append("\\b"); 495 | break; 496 | case '\f': 497 | builder.Append("\\f"); 498 | break; 499 | case '\n': 500 | builder.Append("\\n"); 501 | break; 502 | case '\r': 503 | builder.Append("\\r"); 504 | break; 505 | case '\t': 506 | builder.Append("\\t"); 507 | break; 508 | default: 509 | int codepoint = Convert.ToInt32(c); 510 | if ((codepoint >= 32) && (codepoint <= 126)) { 511 | builder.Append(c); 512 | } else { 513 | builder.Append("\\u"); 514 | builder.Append(codepoint.ToString("x4")); 515 | } 516 | break; 517 | } 518 | } 519 | 520 | builder.Append('\"'); 521 | } 522 | 523 | void SerializeOther(object value) { 524 | // NOTE: decimals lose precision during serialization. 525 | // They always have, I'm just letting you know. 526 | // Previously floats and doubles lost precision too. 527 | if (value is float) { 528 | builder.Append(((float) value).ToString("R")); 529 | } else if (value is int 530 | || value is uint 531 | || value is long 532 | || value is sbyte 533 | || value is byte 534 | || value is short 535 | || value is ushort 536 | || value is ulong) { 537 | builder.Append(value); 538 | } else if (value is double 539 | || value is decimal) { 540 | builder.Append(Convert.ToDouble(value).ToString("R")); 541 | } else { 542 | SerializeString(value.ToString()); 543 | } 544 | } 545 | } 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /RewriteTypeReferences.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Mono.Cecil; 7 | using Mono.Cecil.Cil; 8 | using Mono.Cecil.Rocks; 9 | using MethodBody = Mono.Cecil.Cil.MethodBody; 10 | using TypeAttributes = Mono.Cecil.TypeAttributes; 11 | 12 | namespace Unity.ReferenceRewriter 13 | { 14 | class RewriteTypeReferences : RewriteStep, IReferenceVisitor 15 | { 16 | private readonly Func _supportNamespaceMapper; 17 | public bool MethodChanged { get; private set; } 18 | public MethodReference ParamsMethod { get; private set; } 19 | 20 | public RewriteTypeReferences(Func supportNamespaceMapper) 21 | { 22 | _supportNamespaceMapper = supportNamespaceMapper; 23 | } 24 | 25 | protected override void Run() 26 | { 27 | ReferenceDispatcher.DispatchOn(Context.TargetModule, this); 28 | } 29 | 30 | private AssemblyNameReference SupportAssemblyReference() 31 | { 32 | var supportName = Context.SupportModule.Assembly.Name; 33 | var reference = Context.TargetModule.AssemblyReferences.SingleOrDefault(r => r.FullName == supportName.FullName); 34 | if (reference != null) 35 | return reference; 36 | 37 | reference = new AssemblyNameReference(supportName.Name, supportName.Version) { PublicKeyToken = supportName.PublicKeyToken }; 38 | return reference; 39 | } 40 | 41 | private bool AltAssemblyReference(string @namespace, out AssemblyNameReference[] names) 42 | { 43 | ModuleDefinition[] modules; 44 | 45 | if (!Context.AltModules.TryGetValue(@namespace, out modules)) 46 | { 47 | names = null; 48 | return false; 49 | } 50 | 51 | names = new AssemblyNameReference[modules.Length]; 52 | 53 | for (var i = 0; i < modules.Length; ++i) 54 | { 55 | var name = modules[i].Assembly.Name; 56 | var reference = Context.TargetModule.AssemblyReferences.FirstOrDefault(r => r.FullName == name.FullName); 57 | if (reference == null) 58 | reference = new AssemblyNameReference(name.Name, name.Version) { PublicKeyToken = name.PublicKeyToken }; 59 | names[i] = reference; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | public void Visit(TypeReference type, string referencingEntityName) 66 | { 67 | if (type.IsNested) 68 | { 69 | Visit(type.DeclaringType, referencingEntityName); 70 | return; 71 | } 72 | 73 | if (TryToResolveInSupport(type)) 74 | return; 75 | 76 | var resolvedType = type.Resolve(); 77 | if (resolvedType != null) 78 | { 79 | var targetAssembly = resolvedType.Module.Assembly; 80 | var assemblyNameReference = type.Scope as AssemblyNameReference; 81 | if ((assemblyNameReference != null) && (targetAssembly.Name.Name != assemblyNameReference.Name)) 82 | { 83 | var realType = type.Module.Import(resolvedType); 84 | 85 | if (realType.Scope is AssemblyNameReference) 86 | { 87 | type.Scope = realType.Scope; 88 | Context.RewriteTarget = true; 89 | } 90 | } 91 | 92 | return; 93 | } 94 | 95 | if (TryToResolveInAlt(type)) 96 | return; 97 | 98 | if (IsIgnoredType(type)) 99 | return; 100 | 101 | Console.WriteLine("Error: type `{0}` doesn't exist in target framework. It is referenced from {1} at {2}.", 102 | type.FullName, type.Module.Name, referencingEntityName); 103 | } 104 | 105 | private bool TryToResolveInSupport(TypeReference type) 106 | { 107 | var originalScope = type.Scope; 108 | var originalNamespace = type.Namespace; 109 | 110 | var support = SupportAssemblyReference(); 111 | 112 | type.Scope = support; 113 | type.Namespace = _supportNamespaceMapper(type.Namespace); 114 | 115 | var resolved = type.Resolve(); 116 | if (resolved != null) 117 | { 118 | Context.RewriteTarget = true; 119 | AddSupportReferenceIfNeeded(support); 120 | return true; 121 | } 122 | 123 | type.Scope = originalScope; 124 | type.Namespace = originalNamespace; 125 | return false; 126 | } 127 | 128 | private bool TryToResolveInAlt(TypeReference type) 129 | { 130 | AssemblyNameReference[] names; 131 | 132 | if (!AltAssemblyReference(type.Namespace, out names)) 133 | return false; 134 | 135 | var originalScope = type.Scope; 136 | 137 | foreach (var name in names) 138 | { 139 | type.Scope = name; 140 | 141 | var resolved = type.Resolve(); 142 | if (resolved != null) 143 | { 144 | Context.RewriteTarget = true; 145 | AddSupportReferenceIfNeeded(name); 146 | return true; 147 | } 148 | } 149 | 150 | type.Scope = originalScope; 151 | return false; 152 | } 153 | 154 | private bool IsIgnoredType(TypeReference type) 155 | { 156 | IList ignoredTypes; 157 | if (!Context.IgnoredTypes.TryGetValue(type.Scope.Name, out ignoredTypes)) 158 | return false; 159 | return ignoredTypes.Contains(type.FullName); 160 | } 161 | 162 | private void AddSupportReferenceIfNeeded(AssemblyNameReference support) 163 | { 164 | if (Context.TargetModule.AssemblyReferences.Any(r => r.FullName == support.FullName)) 165 | return; 166 | 167 | Context.TargetModule.AssemblyReferences.Add(support); 168 | } 169 | 170 | public void Visit(FieldReference field, string referencingEntityName) 171 | { 172 | if (field.Resolve() != null) 173 | return; 174 | 175 | if (IsIgnoredType(field.DeclaringType)) 176 | return; 177 | 178 | Console.WriteLine("Error: field `{0}` doesn't exist in target framework. It is referenced from {1} at {2}.", 179 | field, field.Module.Name, referencingEntityName); 180 | } 181 | 182 | public void Visit(MethodReference method, string referencingEntityName) 183 | { 184 | MethodChanged = false; 185 | ParamsMethod = null; 186 | 187 | if (method.DeclaringType.Scope == SupportAssemblyReference() && TryToResolveInSupport(method)) 188 | return; 189 | 190 | if (method.Resolve() != null || method.DeclaringType.IsArray || TryToResolveInSupport(method) || ResolveManually(method) != null) 191 | return; 192 | 193 | if (IsIgnoredType(method.DeclaringType)) 194 | return; 195 | 196 | Console.WriteLine("Error: method `{0}` doesn't exist in target framework. It is referenced from {1} at {2}.", 197 | method, method.Module.Name, referencingEntityName); 198 | } 199 | 200 | private bool TryToResolveInSupport(MethodReference method) 201 | { 202 | if (string.IsNullOrEmpty(Context.SupportModulePartialNamespace)) 203 | return false; 204 | 205 | method = method.GetElementMethod(); 206 | 207 | var originalType = method.DeclaringType; 208 | if (originalType.IsGenericInstance || originalType.HasGenericParameters) 209 | return false; 210 | 211 | var support = SupportAssemblyReference(); 212 | 213 | var ns = Context.SupportModulePartialNamespace; 214 | if (!string.IsNullOrEmpty(originalType.Namespace)) 215 | ns += '.' + originalType.Namespace; 216 | method.DeclaringType = new TypeReference(ns, originalType.Name, Context.TargetModule, support, originalType.IsValueType); 217 | 218 | MethodDefinition resolved = null; 219 | 220 | // We can only change declaring type like this for static methods 221 | if (!method.HasThis) 222 | { 223 | resolved = method.Resolve(); 224 | } 225 | 226 | // If our method is instance, we can have a static method in support module that has explicit "this" parameter 227 | if (resolved == null && method.HasThis) 228 | { 229 | method.HasThis = false; 230 | method.Parameters.Insert(0, new ParameterDefinition(originalType)); 231 | resolved = method.Resolve(); 232 | 233 | // Our explicit "this" parameter can be of type System.Object 234 | if (resolved == null) 235 | { 236 | method.Parameters[0] = new ParameterDefinition(method.DeclaringType.Module.TypeSystem.Object.Resolve()); 237 | resolved = method.Resolve(); 238 | } 239 | 240 | if (resolved == null) 241 | { 242 | method.HasThis = true; 243 | method.Parameters.RemoveAt(0); 244 | } 245 | } 246 | 247 | if (resolved != null) 248 | { 249 | Context.RewriteTarget = true; 250 | AddSupportReferenceIfNeeded(support); 251 | return true; 252 | } 253 | 254 | method.DeclaringType = originalType; 255 | return false; 256 | } 257 | 258 | private MethodDefinition ResolveManually(MethodReference method) 259 | { 260 | var metadataResolver = method.Module.MetadataResolver; 261 | var type = metadataResolver.Resolve(method.DeclaringType); 262 | 263 | if (type == null || !type.HasMethods) 264 | { 265 | return null; 266 | } 267 | 268 | method = method.GetElementMethod(); 269 | 270 | Func, MethodReference, MethodDefinition>[] finderMethods = 271 | {GetMethodDefinition, GetCompatibleMethodDefinition}; 272 | 273 | for (int i = 0; i < finderMethods.Length; i++) 274 | { 275 | while (type != null) 276 | { 277 | var methodDefinition = finderMethods[i](type.Methods, method); 278 | 279 | if (methodDefinition != null) 280 | { 281 | return methodDefinition; 282 | } 283 | 284 | if (type.BaseType == null) 285 | { 286 | break; 287 | } 288 | type = metadataResolver.Resolve(type.BaseType); 289 | } 290 | type = metadataResolver.Resolve(method.DeclaringType); 291 | } 292 | 293 | return null; 294 | } 295 | 296 | private MethodDefinition GetMethodDefinition(IEnumerable methods, MethodReference reference) 297 | { 298 | foreach (var methodDefinition in methods) 299 | { 300 | bool isSameName = methodDefinition.Name == reference.Name || MethodAliases.AreAliases(methodDefinition.Name, reference.Name); 301 | bool isSameGenericParameters = methodDefinition.HasGenericParameters == reference.HasGenericParameters && 302 | (!methodDefinition.HasGenericParameters || methodDefinition.GenericParameters.Count == reference.GenericParameters.Count); 303 | 304 | bool isSameReturnType = AreSame(methodDefinition.ReturnType, reference.ReturnType); 305 | 306 | if (isSameName && isSameGenericParameters && isSameReturnType && methodDefinition.HasParameters == reference.HasParameters) 307 | { 308 | if (!methodDefinition.HasParameters && !reference.HasParameters) 309 | { 310 | return methodDefinition; 311 | } 312 | 313 | if (AreSame(methodDefinition.Parameters, reference.Parameters)) 314 | { 315 | return methodDefinition; 316 | } 317 | } 318 | } 319 | 320 | return null; 321 | } 322 | 323 | private MethodDefinition GetCompatibleMethodDefinition(IEnumerable methods, MethodReference reference) 324 | { 325 | foreach (var methodDefinition in methods) 326 | { 327 | bool isSameName = methodDefinition.Name == reference.Name || MethodAliases.AreAliases(methodDefinition.Name, reference.Name); 328 | bool isSameGenericParameters = methodDefinition.HasGenericParameters == reference.HasGenericParameters && 329 | (!methodDefinition.HasGenericParameters || methodDefinition.GenericParameters.Count == reference.GenericParameters.Count); 330 | 331 | bool isSameReturnType = AreSame(methodDefinition.ReturnType, reference.ReturnType); 332 | 333 | if (isSameName && isSameGenericParameters && isSameReturnType) 334 | { 335 | if (ArgsMatchParamsList(methodDefinition.Parameters, reference.Parameters)) 336 | { 337 | ParamsMethod = Context.TargetModule.Import(methodDefinition); 338 | MethodChanged = true; 339 | Context.RewriteTarget = true; 340 | return methodDefinition; 341 | } 342 | } 343 | } 344 | 345 | return null; 346 | } 347 | 348 | // The idea behind this method is to change method call from Method(obj1, obj2, obj3) to Method(param Object[] objs) 349 | // To do that, we need to first pack all the objects to an array, then push the array onto the stack before calling the method. 350 | // Creating an array is achieved by pushing number of elements on the stack, and using newarr instruction. 351 | // Lastly, we need to pop the array back from the stack and store it in a local variable. 352 | // 353 | // Putting object to array is done by: 354 | // Loading array to the stack, loading index to the stack, loading the reference to value on the stack 355 | // and using stelem.ref to insert it to the array. stelem.ref instruction pops all three inserted values 356 | // from the stack 357 | // 358 | // For example, we need to convert something like this: 359 | // 360 | // IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() 361 | // IL_0005: stloc.0 362 | // IL_0006: ldloc.0 363 | // IL_0007: ldstr "{0}, {1}" 364 | // IL_000c: ldstr "one" 365 | // IL_0011: ldstr "two" 366 | // IL_0016: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object[]) 367 | // IL_001b: pop 368 | // 369 | // To this: 370 | // 371 | // IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() 372 | // IL_0005: stloc.0 373 | // IL_0006: ldloc.0 374 | // IL_0007: ldstr "{0}, {1}\r\n" 375 | // IL_000c: ldstr "one" 376 | // IL_0011: ldstr "two" 377 | // IL_0016: ldc.i4 2 378 | // IL_001b: newarr [mscorlib]System.Object 379 | // IL_0020: stloc 1 380 | // IL_0024: stloc 2 381 | // IL_0028: stloc 3 382 | // IL_002c: ldloc 1 383 | // IL_0030: ldc.i4 0 384 | // IL_0035: ldloc 2 385 | // IL_0039: stelem.ref 386 | // IL_003a: ldloc 1 387 | // IL_003e: ldc.i4 1 388 | // IL_0043: ldloc 3 389 | // IL_0047: stelem.ref 390 | // IL_0048: ldloc 1 391 | // IL_004c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object[]) 392 | // IL_0051: pop 393 | // 394 | // Basically, just before the invalid function call we pack all the arguments (that are already on the stack, 395 | // ready to be passed to invalid function) to a newly created array and call a valid function instead passing 396 | // the array as argument 397 | // 398 | public void RewriteObjectListToParamsCall(MethodBody methodBody, int instructionIndex) 399 | { 400 | methodBody.SimplifyMacros(); 401 | 402 | var parameterType = ParamsMethod.Parameters.Last().ParameterType; 403 | var arrayInfo = new VariableDefinition(parameterType); 404 | methodBody.InitLocals = true; 405 | methodBody.Variables.Add(arrayInfo); 406 | 407 | var instruction = methodBody.Instructions[instructionIndex]; 408 | int numberOfObjects = (instruction.Operand as MethodReference).Parameters.Count - ParamsMethod.Parameters.Count + 1; 409 | 410 | // Firstly, let's create the object array 411 | 412 | // Push number of objects to the stack 413 | var instr = Instruction.Create(OpCodes.Ldc_I4, numberOfObjects); 414 | var firstInstruction = instr; 415 | methodBody.Instructions.Insert(instructionIndex, instr); 416 | instructionIndex++; 417 | 418 | // Create a new array 419 | instr = Instruction.Create(OpCodes.Newarr, ParamsMethod.Parameters.Last().ParameterType.GetElementType()); 420 | methodBody.Instructions.Insert(instructionIndex, instr); 421 | instructionIndex++; 422 | 423 | // Store the newly created array to first variable slot 424 | instr = Instruction.Create(OpCodes.Stloc, arrayInfo); 425 | methodBody.Instructions.Insert(instructionIndex, instr); 426 | instructionIndex++; 427 | 428 | // At this point, all the references to objects that need to be packed to the array are on the stack. 429 | var objectInfo = new VariableDefinition[numberOfObjects]; 430 | for (int i = 0; i < numberOfObjects; i++) 431 | { 432 | objectInfo[i] = new VariableDefinition(parameterType.GetElementType()); 433 | methodBody.Variables.Add(objectInfo[i]); 434 | 435 | instr = Instruction.Create(OpCodes.Stloc, objectInfo[i]); 436 | methodBody.Instructions.Insert(instructionIndex, instr); 437 | instructionIndex++; 438 | } 439 | 440 | // Now that we got the references to objects in local variables rather than the stack, it's high time we insert them to the array 441 | // We need to load them in backwards order, because the last argument was taken off the stack first. 442 | for (int i = 0; i < numberOfObjects; i++) 443 | { 444 | // Load reference to the array to the stack 445 | instr = Instruction.Create(OpCodes.Ldloc, arrayInfo); 446 | methodBody.Instructions.Insert(instructionIndex, instr); 447 | instructionIndex++; 448 | 449 | // Load object index to the stack 450 | instr = Instruction.Create(OpCodes.Ldc_I4, numberOfObjects - i - 1); 451 | methodBody.Instructions.Insert(instructionIndex, instr); 452 | instructionIndex++; 453 | 454 | // Load reference to the object to the stack 455 | instr = Instruction.Create(OpCodes.Ldloc, objectInfo[i]); 456 | methodBody.Instructions.Insert(instructionIndex, instr); 457 | instructionIndex++; 458 | 459 | // Insert the object to the array 460 | if (parameterType.GetElementType().IsValueType) 461 | { 462 | instr = Instruction.Create(OpCodes.Stelem_Any, parameterType.GetElementType()); 463 | methodBody.Instructions.Insert(instructionIndex, instr); 464 | instructionIndex++; 465 | } 466 | else 467 | { 468 | instr = Instruction.Create(OpCodes.Stelem_Ref); 469 | methodBody.Instructions.Insert(instructionIndex, instr); 470 | instructionIndex++; 471 | } 472 | } 473 | 474 | // Finally, load reference to the array to the stack so it can be inserted to the array 475 | instr = Instruction.Create(OpCodes.Ldloc, arrayInfo); 476 | methodBody.Instructions.Insert(instructionIndex, instr); 477 | 478 | instruction.Operand = ParamsMethod; 479 | ParamsMethod = null; 480 | MethodChanged = false; 481 | methodBody.OptimizeMacros(); // This, together with SimplifyMacros() before touching IL code, recalculates IL instruction offsets 482 | 483 | // If any other instruction is referencing the illegal call, we need to rewrite it to reference beginning of object packing instead 484 | // For example, there's a branch jump to call the method. We need to pack the objects anyway before calling the method 485 | foreach (var changeableInstruction in methodBody.Instructions) 486 | { 487 | if (changeableInstruction.Operand is Instruction && 488 | (changeableInstruction as Instruction).Operand == instruction) 489 | { 490 | changeableInstruction.Operand = firstInstruction; 491 | } 492 | } 493 | } 494 | 495 | private bool AreSame(TypeReference a, TypeReference b) 496 | { 497 | var assembly = System.Reflection.Assembly.GetAssembly(typeof (MetadataResolver)); 498 | var type = assembly.GetType("Mono.Cecil.MetadataResolver"); 499 | var compareMethod = type.GetMethod("AreSame", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] {typeof (TypeReference), typeof (TypeReference)}, null); 500 | 501 | var areSameAccordingToCecil = (bool)compareMethod.Invoke(null, new object[] {a, b}); 502 | bool areSameAccordingToTypeAliases = TypeAliases.AreAliases(a.FullName, b.FullName); 503 | 504 | return areSameAccordingToCecil || areSameAccordingToTypeAliases; 505 | } 506 | 507 | private bool AreSame(Mono.Collections.Generic.Collection a, Mono.Collections.Generic.Collection b) 508 | { 509 | if (a.Count != b.Count) 510 | { 511 | return false; 512 | } 513 | 514 | for (int i = 0; i < a.Count; i++) 515 | { 516 | if (!AreSame(a[i].ParameterType, b[i].ParameterType)) 517 | { 518 | return false; 519 | } 520 | } 521 | return true; 522 | } 523 | 524 | private bool ArgsMatchParamsList(Mono.Collections.Generic.Collection a, Mono.Collections.Generic.Collection b) 525 | { 526 | if (a.Count == 0 || a.Last().CustomAttributes.All(x => x.AttributeType.FullName != "System.ParamArrayAttribute")) 527 | { 528 | if (b.Count == 0 || b.Last().CustomAttributes.All(x => x.AttributeType.FullName != "System.ParamArrayAttribute")) 529 | { 530 | return false; 531 | } 532 | else 533 | { 534 | var temp = a; 535 | a = b; 536 | b = temp; 537 | } 538 | } 539 | 540 | int numberOfMatches = 0; 541 | while (numberOfMatches < a.Count - 1 && numberOfMatches < b.Count) 542 | { 543 | if (AreSame(a[numberOfMatches].ParameterType, b[numberOfMatches].ParameterType)) 544 | { 545 | numberOfMatches++; 546 | } 547 | else 548 | { 549 | break; 550 | } 551 | } 552 | 553 | if (numberOfMatches != a.Count - 1) 554 | { 555 | return false; 556 | } 557 | 558 | var paramsArg = a.Last().ParameterType.GetElementType(); 559 | for (int i = a.Count - 1; i < b.Count; i++) 560 | { 561 | bool matches = false; 562 | var type = b[i].ParameterType; 563 | while (type != null && !matches) 564 | { 565 | if (AreSame(type, paramsArg)) 566 | { 567 | matches = true; 568 | } 569 | else 570 | { 571 | type = type.Resolve().BaseType; 572 | } 573 | } 574 | 575 | if (!matches) 576 | { 577 | return false; 578 | } 579 | } 580 | 581 | return true; 582 | } 583 | } 584 | } -------------------------------------------------------------------------------- /Options.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Options.cs 3 | // 4 | // Authors: 5 | // Jonathan Pryor 6 | // Federico Di Gregorio 7 | // 8 | // Copyright (C) 2008 Novell (http://www.novell.com) 9 | // Copyright (C) 2009 Federico Di Gregorio. 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining 12 | // a copy of this software and associated documentation files (the 13 | // "Software"), to deal in the Software without restriction, including 14 | // without limitation the rights to use, copy, modify, merge, publish, 15 | // distribute, sublicense, and/or sell copies of the Software, and to 16 | // permit persons to whom the Software is furnished to do so, subject to 17 | // the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be 20 | // included in all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | // 30 | 31 | // Compile With: 32 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll 33 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll 34 | // 35 | // The LINQ version just changes the implementation of 36 | // OptionSet.Parse(IEnumerable), and confers no semantic changes. 37 | 38 | // 39 | // A Getopt::Long-inspired option parsing library for C#. 40 | // 41 | // NDesk.Options.OptionSet is built upon a key/value table, where the 42 | // key is a option format string and the value is a delegate that is 43 | // invoked when the format string is matched. 44 | // 45 | // Option format strings: 46 | // Regex-like BNF Grammar: 47 | // name: .+ 48 | // type: [=:] 49 | // sep: ( [^{}]+ | '{' .+ '}' )? 50 | // aliases: ( name type sep ) ( '|' name type sep )* 51 | // 52 | // Each '|'-delimited name is an alias for the associated action. If the 53 | // format string ends in a '=', it has a required value. If the format 54 | // string ends in a ':', it has an optional value. If neither '=' or ':' 55 | // is present, no value is supported. `=' or `:' need only be defined on one 56 | // alias, but if they are provided on more than one they must be consistent. 57 | // 58 | // Each alias portion may also end with a "key/value separator", which is used 59 | // to split option values if the option accepts > 1 value. If not specified, 60 | // it defaults to '=' and ':'. If specified, it can be any character except 61 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be 62 | // used (i.e. the separate values should be distinct arguments), then "{}" 63 | // should be used as the separator. 64 | // 65 | // Options are extracted either from the current option by looking for 66 | // the option name followed by an '=' or ':', or is taken from the 67 | // following option IFF: 68 | // - The current option does not contain a '=' or a ':' 69 | // - The current option requires a value (i.e. not a Option type of ':') 70 | // 71 | // The `name' used in the option format string does NOT include any leading 72 | // option indicator, such as '-', '--', or '/'. All three of these are 73 | // permitted/required on any named option. 74 | // 75 | // Option bundling is permitted so long as: 76 | // - '-' is used to start the option group 77 | // - all of the bundled options are a single character 78 | // - at most one of the bundled options accepts a value, and the value 79 | // provided starts from the next character to the end of the string. 80 | // 81 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' 82 | // as '-Dname=value'. 83 | // 84 | // Option processing is disabled by specifying "--". All options after "--" 85 | // are returned by OptionSet.Parse() unchanged and unprocessed. 86 | // 87 | // Unprocessed options are returned from OptionSet.Parse(). 88 | // 89 | // Examples: 90 | // int verbose = 0; 91 | // OptionSet p = new OptionSet () 92 | // .Add ("v", v => ++verbose) 93 | // .Add ("name=|value=", v => Console.WriteLine (v)); 94 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); 95 | // 96 | // The above would parse the argument string array, and would invoke the 97 | // lambda expression three times, setting `verbose' to 3 when complete. 98 | // It would also print out "A" and "B" to standard output. 99 | // The returned array would contain the string "extra". 100 | // 101 | // C# 3.0 collection initializers are supported and encouraged: 102 | // var p = new OptionSet () { 103 | // { "h|?|help", v => ShowHelp () }, 104 | // }; 105 | // 106 | // System.ComponentModel.TypeConverter is also supported, allowing the use of 107 | // custom data types in the callback type; TypeConverter.ConvertFromString() 108 | // is used to convert the value option to an instance of the specified 109 | // type: 110 | // 111 | // var p = new OptionSet () { 112 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, 113 | // }; 114 | // 115 | // Random other tidbits: 116 | // - Boolean options (those w/o '=' or ':' in the option format string) 117 | // are explicitly enabled if they are followed with '+', and explicitly 118 | // disabled if they are followed with '-': 119 | // string a = null; 120 | // var p = new OptionSet () { 121 | // { "a", s => a = s }, 122 | // }; 123 | // p.Parse (new string[]{"-a"}); // sets v != null 124 | // p.Parse (new string[]{"-a+"}); // sets v != null 125 | // p.Parse (new string[]{"-a-"}); // sets v == null 126 | // 127 | 128 | using System; 129 | using System.Collections; 130 | using System.Collections.Generic; 131 | using System.Collections.ObjectModel; 132 | using System.ComponentModel; 133 | using System.Globalization; 134 | using System.IO; 135 | using System.Runtime.Serialization; 136 | using System.Security.Permissions; 137 | using System.Text; 138 | using System.Text.RegularExpressions; 139 | 140 | #if LINQ 141 | using System.Linq; 142 | #endif 143 | 144 | #if TEST 145 | using NDesk.Options; 146 | #endif 147 | 148 | #if NDESK_OPTIONS 149 | namespace NDesk.Options 150 | #else 151 | namespace Mono.Options 152 | #endif 153 | { 154 | static class StringCoda { 155 | 156 | public static IEnumerable WrappedLines (string self, params int[] widths) 157 | { 158 | IEnumerable w = widths; 159 | return WrappedLines (self, w); 160 | } 161 | 162 | public static IEnumerable WrappedLines (string self, IEnumerable widths) 163 | { 164 | if (widths == null) 165 | throw new ArgumentNullException ("widths"); 166 | return CreateWrappedLinesIterator (self, widths); 167 | } 168 | 169 | private static IEnumerable CreateWrappedLinesIterator (string self, IEnumerable widths) 170 | { 171 | if (string.IsNullOrEmpty (self)) { 172 | yield return string.Empty; 173 | yield break; 174 | } 175 | using (IEnumerator ewidths = widths.GetEnumerator ()) { 176 | bool? hw = null; 177 | int width = GetNextWidth (ewidths, int.MaxValue, ref hw); 178 | int start = 0, end; 179 | do { 180 | end = GetLineEnd (start, width, self); 181 | char c = self [end-1]; 182 | if (char.IsWhiteSpace (c)) 183 | --end; 184 | bool needContinuation = end != self.Length && !IsEolChar (c); 185 | string continuation = ""; 186 | if (needContinuation) { 187 | --end; 188 | continuation = "-"; 189 | } 190 | string line = self.Substring (start, end - start) + continuation; 191 | yield return line; 192 | start = end; 193 | if (char.IsWhiteSpace (c)) 194 | ++start; 195 | width = GetNextWidth (ewidths, width, ref hw); 196 | } while (start < self.Length); 197 | } 198 | } 199 | 200 | private static int GetNextWidth (IEnumerator ewidths, int curWidth, ref bool? eValid) 201 | { 202 | if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) { 203 | curWidth = (eValid = ewidths.MoveNext ()).Value ? ewidths.Current : curWidth; 204 | // '.' is any character, - is for a continuation 205 | const string minWidth = ".-"; 206 | if (curWidth < minWidth.Length) 207 | throw new ArgumentOutOfRangeException ("widths", 208 | string.Format ("Element must be >= {0}, was {1}.", minWidth.Length, curWidth)); 209 | return curWidth; 210 | } 211 | // no more elements, use the last element. 212 | return curWidth; 213 | } 214 | 215 | private static bool IsEolChar (char c) 216 | { 217 | return !char.IsLetterOrDigit (c); 218 | } 219 | 220 | private static int GetLineEnd (int start, int length, string description) 221 | { 222 | int end = System.Math.Min (start + length, description.Length); 223 | int sep = -1; 224 | for (int i = start; i < end; ++i) { 225 | if (description [i] == '\n') 226 | return i+1; 227 | if (IsEolChar (description [i])) 228 | sep = i+1; 229 | } 230 | if (sep == -1 || end == description.Length) 231 | return end; 232 | return sep; 233 | } 234 | } 235 | 236 | public class OptionValueCollection : IList, IList { 237 | 238 | List values = new List (); 239 | OptionContext c; 240 | 241 | internal OptionValueCollection (OptionContext c) 242 | { 243 | this.c = c; 244 | } 245 | 246 | #region ICollection 247 | void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} 248 | bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} 249 | object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} 250 | #endregion 251 | 252 | #region ICollection 253 | public void Add (string item) {values.Add (item);} 254 | public void Clear () {values.Clear ();} 255 | public bool Contains (string item) {return values.Contains (item);} 256 | public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} 257 | public bool Remove (string item) {return values.Remove (item);} 258 | public int Count {get {return values.Count;}} 259 | public bool IsReadOnly {get {return false;}} 260 | #endregion 261 | 262 | #region IEnumerable 263 | IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} 264 | #endregion 265 | 266 | #region IEnumerable 267 | public IEnumerator GetEnumerator () {return values.GetEnumerator ();} 268 | #endregion 269 | 270 | #region IList 271 | int IList.Add (object value) {return (values as IList).Add (value);} 272 | bool IList.Contains (object value) {return (values as IList).Contains (value);} 273 | int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} 274 | void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} 275 | void IList.Remove (object value) {(values as IList).Remove (value);} 276 | void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} 277 | bool IList.IsFixedSize {get {return false;}} 278 | object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} 279 | #endregion 280 | 281 | #region IList 282 | public int IndexOf (string item) {return values.IndexOf (item);} 283 | public void Insert (int index, string item) {values.Insert (index, item);} 284 | public void RemoveAt (int index) {values.RemoveAt (index);} 285 | 286 | private void AssertValid (int index) 287 | { 288 | if (c.Option == null) 289 | throw new InvalidOperationException ("OptionContext.Option is null."); 290 | if (index >= c.Option.MaxValueCount) 291 | throw new ArgumentOutOfRangeException ("index"); 292 | if (c.Option.OptionValueType == OptionValueType.Required && 293 | index >= values.Count) 294 | throw new OptionException (string.Format ( 295 | c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 296 | c.OptionName); 297 | } 298 | 299 | public string this [int index] { 300 | get { 301 | AssertValid (index); 302 | return index >= values.Count ? null : values [index]; 303 | } 304 | set { 305 | values [index] = value; 306 | } 307 | } 308 | #endregion 309 | 310 | public List ToList () 311 | { 312 | return new List (values); 313 | } 314 | 315 | public string[] ToArray () 316 | { 317 | return values.ToArray (); 318 | } 319 | 320 | public override string ToString () 321 | { 322 | return string.Join (", ", values.ToArray ()); 323 | } 324 | } 325 | 326 | public class OptionContext { 327 | private Option option; 328 | private string name; 329 | private int index; 330 | private OptionSet set; 331 | private OptionValueCollection c; 332 | 333 | public OptionContext (OptionSet set) 334 | { 335 | this.set = set; 336 | this.c = new OptionValueCollection (this); 337 | } 338 | 339 | public Option Option { 340 | get {return option;} 341 | set {option = value;} 342 | } 343 | 344 | public string OptionName { 345 | get {return name;} 346 | set {name = value;} 347 | } 348 | 349 | public int OptionIndex { 350 | get {return index;} 351 | set {index = value;} 352 | } 353 | 354 | public OptionSet OptionSet { 355 | get {return set;} 356 | } 357 | 358 | public OptionValueCollection OptionValues { 359 | get {return c;} 360 | } 361 | } 362 | 363 | public enum OptionValueType { 364 | None, 365 | Optional, 366 | Required, 367 | } 368 | 369 | public abstract class Option { 370 | string prototype, description; 371 | string[] names; 372 | OptionValueType type; 373 | int count; 374 | string[] separators; 375 | 376 | protected Option (string prototype, string description) 377 | : this (prototype, description, 1) 378 | { 379 | } 380 | 381 | protected Option (string prototype, string description, int maxValueCount) 382 | { 383 | if (prototype == null) 384 | throw new ArgumentNullException ("prototype"); 385 | if (prototype.Length == 0) 386 | throw new ArgumentException ("Cannot be the empty string.", "prototype"); 387 | if (maxValueCount < 0) 388 | throw new ArgumentOutOfRangeException ("maxValueCount"); 389 | 390 | this.prototype = prototype; 391 | this.description = description; 392 | this.count = maxValueCount; 393 | this.names = (this is OptionSet.Category) 394 | // append GetHashCode() so that "duplicate" categories have distinct 395 | // names, e.g. adding multiple "" categories should be valid. 396 | ? new[]{prototype + this.GetHashCode ()} 397 | : prototype.Split ('|'); 398 | 399 | if (this is OptionSet.Category) 400 | return; 401 | 402 | this.type = ParsePrototype (); 403 | 404 | if (this.count == 0 && type != OptionValueType.None) 405 | throw new ArgumentException ( 406 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + 407 | "OptionValueType.Optional.", 408 | "maxValueCount"); 409 | if (this.type == OptionValueType.None && maxValueCount > 1) 410 | throw new ArgumentException ( 411 | string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), 412 | "maxValueCount"); 413 | if (Array.IndexOf (names, "<>") >= 0 && 414 | ((names.Length == 1 && this.type != OptionValueType.None) || 415 | (names.Length > 1 && this.MaxValueCount > 1))) 416 | throw new ArgumentException ( 417 | "The default option handler '<>' cannot require values.", 418 | "prototype"); 419 | } 420 | 421 | public string Prototype {get {return prototype;}} 422 | public string Description {get {return description;}} 423 | public OptionValueType OptionValueType {get {return type;}} 424 | public int MaxValueCount {get {return count;}} 425 | 426 | public string[] GetNames () 427 | { 428 | return (string[]) names.Clone (); 429 | } 430 | 431 | public string[] GetValueSeparators () 432 | { 433 | if (separators == null) 434 | return new string [0]; 435 | return (string[]) separators.Clone (); 436 | } 437 | 438 | protected static T Parse (string value, OptionContext c) 439 | { 440 | Type tt = typeof (T); 441 | bool nullable = tt.IsValueType && tt.IsGenericType && 442 | !tt.IsGenericTypeDefinition && 443 | tt.GetGenericTypeDefinition () == typeof (Nullable<>); 444 | Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T); 445 | TypeConverter conv = TypeDescriptor.GetConverter (targetType); 446 | T t = default (T); 447 | try { 448 | if (value != null) 449 | t = (T) conv.ConvertFromString (value); 450 | } 451 | catch (Exception e) { 452 | throw new OptionException ( 453 | string.Format ( 454 | c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), 455 | value, targetType.Name, c.OptionName), 456 | c.OptionName, e); 457 | } 458 | return t; 459 | } 460 | 461 | internal string[] Names {get {return names;}} 462 | internal string[] ValueSeparators {get {return separators;}} 463 | 464 | static readonly char[] NameTerminator = new char[]{'=', ':'}; 465 | 466 | private OptionValueType ParsePrototype () 467 | { 468 | char type = '\0'; 469 | List seps = new List (); 470 | for (int i = 0; i < names.Length; ++i) { 471 | string name = names [i]; 472 | if (name.Length == 0) 473 | throw new ArgumentException ("Empty option names are not supported.", "prototype"); 474 | 475 | int end = name.IndexOfAny (NameTerminator); 476 | if (end == -1) 477 | continue; 478 | names [i] = name.Substring (0, end); 479 | if (type == '\0' || type == name [end]) 480 | type = name [end]; 481 | else 482 | throw new ArgumentException ( 483 | string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), 484 | "prototype"); 485 | AddSeparators (name, end, seps); 486 | } 487 | 488 | if (type == '\0') 489 | return OptionValueType.None; 490 | 491 | if (count <= 1 && seps.Count != 0) 492 | throw new ArgumentException ( 493 | string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), 494 | "prototype"); 495 | if (count > 1) { 496 | if (seps.Count == 0) 497 | this.separators = new string[]{":", "="}; 498 | else if (seps.Count == 1 && seps [0].Length == 0) 499 | this.separators = null; 500 | else 501 | this.separators = seps.ToArray (); 502 | } 503 | 504 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional; 505 | } 506 | 507 | private static void AddSeparators (string name, int end, ICollection seps) 508 | { 509 | int start = -1; 510 | for (int i = end+1; i < name.Length; ++i) { 511 | switch (name [i]) { 512 | case '{': 513 | if (start != -1) 514 | throw new ArgumentException ( 515 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 516 | "prototype"); 517 | start = i+1; 518 | break; 519 | case '}': 520 | if (start == -1) 521 | throw new ArgumentException ( 522 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 523 | "prototype"); 524 | seps.Add (name.Substring (start, i-start)); 525 | start = -1; 526 | break; 527 | default: 528 | if (start == -1) 529 | seps.Add (name [i].ToString ()); 530 | break; 531 | } 532 | } 533 | if (start != -1) 534 | throw new ArgumentException ( 535 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 536 | "prototype"); 537 | } 538 | 539 | public void Invoke (OptionContext c) 540 | { 541 | OnParseComplete (c); 542 | c.OptionName = null; 543 | c.Option = null; 544 | c.OptionValues.Clear (); 545 | } 546 | 547 | protected abstract void OnParseComplete (OptionContext c); 548 | 549 | public override string ToString () 550 | { 551 | return Prototype; 552 | } 553 | } 554 | 555 | public abstract class ArgumentSource { 556 | 557 | protected ArgumentSource () 558 | { 559 | } 560 | 561 | public abstract string[] GetNames (); 562 | public abstract string Description { get; } 563 | public abstract bool GetArguments (string value, out IEnumerable replacement); 564 | 565 | public static IEnumerable GetArgumentsFromFile (string file) 566 | { 567 | return GetArguments (File.OpenText (file), true); 568 | } 569 | 570 | public static IEnumerable GetArguments (TextReader reader) 571 | { 572 | return GetArguments (reader, false); 573 | } 574 | 575 | // Cribbed from mcs/driver.cs:LoadArgs(string) 576 | static IEnumerable GetArguments (TextReader reader, bool close) 577 | { 578 | try { 579 | StringBuilder arg = new StringBuilder (); 580 | 581 | string line; 582 | while ((line = reader.ReadLine ()) != null) { 583 | int t = line.Length; 584 | 585 | for (int i = 0; i < t; i++) { 586 | char c = line [i]; 587 | 588 | if (c == '"' || c == '\'') { 589 | char end = c; 590 | 591 | for (i++; i < t; i++){ 592 | c = line [i]; 593 | 594 | if (c == end) 595 | break; 596 | arg.Append (c); 597 | } 598 | } else if (c == ' ') { 599 | if (arg.Length > 0) { 600 | yield return arg.ToString (); 601 | arg.Length = 0; 602 | } 603 | } else 604 | arg.Append (c); 605 | } 606 | if (arg.Length > 0) { 607 | yield return arg.ToString (); 608 | arg.Length = 0; 609 | } 610 | } 611 | } 612 | finally { 613 | if (close) 614 | reader.Close (); 615 | } 616 | } 617 | } 618 | 619 | public class ResponseFileSource : ArgumentSource { 620 | 621 | public override string[] GetNames () 622 | { 623 | return new string[]{"@file"}; 624 | } 625 | 626 | public override string Description { 627 | get {return "Read response file for more options.";} 628 | } 629 | 630 | public override bool GetArguments (string value, out IEnumerable replacement) 631 | { 632 | if (string.IsNullOrEmpty (value) || !value.StartsWith ("@")) { 633 | replacement = null; 634 | return false; 635 | } 636 | replacement = ArgumentSource.GetArgumentsFromFile (value.Substring (1)); 637 | return true; 638 | } 639 | } 640 | 641 | [Serializable] 642 | public class OptionException : Exception { 643 | private string option; 644 | 645 | public OptionException () 646 | { 647 | } 648 | 649 | public OptionException (string message, string optionName) 650 | : base (message) 651 | { 652 | this.option = optionName; 653 | } 654 | 655 | public OptionException (string message, string optionName, Exception innerException) 656 | : base (message, innerException) 657 | { 658 | this.option = optionName; 659 | } 660 | 661 | protected OptionException (SerializationInfo info, StreamingContext context) 662 | : base (info, context) 663 | { 664 | this.option = info.GetString ("OptionName"); 665 | } 666 | 667 | public string OptionName { 668 | get {return this.option;} 669 | } 670 | 671 | [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] 672 | public override void GetObjectData (SerializationInfo info, StreamingContext context) 673 | { 674 | base.GetObjectData (info, context); 675 | info.AddValue ("OptionName", option); 676 | } 677 | } 678 | 679 | public delegate void OptionAction (TKey key, TValue value); 680 | 681 | public class OptionSet : KeyedCollection 682 | { 683 | public OptionSet () 684 | : this (delegate (string f) {return f;}) 685 | { 686 | } 687 | 688 | public OptionSet (Converter localizer) 689 | { 690 | this.localizer = localizer; 691 | this.roSources = new ReadOnlyCollection(sources); 692 | } 693 | 694 | Converter localizer; 695 | 696 | public Converter MessageLocalizer { 697 | get {return localizer;} 698 | } 699 | 700 | List sources = new List (); 701 | ReadOnlyCollection roSources; 702 | 703 | public ReadOnlyCollection ArgumentSources { 704 | get {return roSources;} 705 | } 706 | 707 | 708 | protected override string GetKeyForItem (Option item) 709 | { 710 | if (item == null) 711 | throw new ArgumentNullException ("option"); 712 | if (item.Names != null && item.Names.Length > 0) 713 | return item.Names [0]; 714 | // This should never happen, as it's invalid for Option to be 715 | // constructed w/o any names. 716 | throw new InvalidOperationException ("Option has no names!"); 717 | } 718 | 719 | [Obsolete ("Use KeyedCollection.this[string]")] 720 | protected Option GetOptionForName (string option) 721 | { 722 | if (option == null) 723 | throw new ArgumentNullException ("option"); 724 | try { 725 | return base [option]; 726 | } 727 | catch (KeyNotFoundException) { 728 | return null; 729 | } 730 | } 731 | 732 | protected override void InsertItem (int index, Option item) 733 | { 734 | base.InsertItem (index, item); 735 | AddImpl (item); 736 | } 737 | 738 | protected override void RemoveItem (int index) 739 | { 740 | Option p = Items [index]; 741 | base.RemoveItem (index); 742 | // KeyedCollection.RemoveItem() handles the 0th item 743 | for (int i = 1; i < p.Names.Length; ++i) { 744 | Dictionary.Remove (p.Names [i]); 745 | } 746 | } 747 | 748 | protected override void SetItem (int index, Option item) 749 | { 750 | base.SetItem (index, item); 751 | AddImpl (item); 752 | } 753 | 754 | private void AddImpl (Option option) 755 | { 756 | if (option == null) 757 | throw new ArgumentNullException ("option"); 758 | List added = new List (option.Names.Length); 759 | try { 760 | // KeyedCollection.InsertItem/SetItem handle the 0th name. 761 | for (int i = 1; i < option.Names.Length; ++i) { 762 | Dictionary.Add (option.Names [i], option); 763 | added.Add (option.Names [i]); 764 | } 765 | } 766 | catch (Exception) { 767 | foreach (string name in added) 768 | Dictionary.Remove (name); 769 | throw; 770 | } 771 | } 772 | 773 | public OptionSet Add (string header) 774 | { 775 | if (header == null) 776 | throw new ArgumentNullException ("header"); 777 | Add (new Category (header)); 778 | return this; 779 | } 780 | 781 | internal sealed class Category : Option { 782 | 783 | // Prototype starts with '=' because this is an invalid prototype 784 | // (see Option.ParsePrototype(), and thus it'll prevent Category 785 | // instances from being accidentally used as normal options. 786 | public Category (string description) 787 | : base ("=:Category:= " + description, description) 788 | { 789 | } 790 | 791 | protected override void OnParseComplete (OptionContext c) 792 | { 793 | throw new NotSupportedException ("Category.OnParseComplete should not be invoked."); 794 | } 795 | } 796 | 797 | 798 | public new OptionSet Add (Option option) 799 | { 800 | base.Add (option); 801 | return this; 802 | } 803 | 804 | sealed class ActionOption : Option { 805 | Action action; 806 | 807 | public ActionOption (string prototype, string description, int count, Action action) 808 | : base (prototype, description, count) 809 | { 810 | if (action == null) 811 | throw new ArgumentNullException ("action"); 812 | this.action = action; 813 | } 814 | 815 | protected override void OnParseComplete (OptionContext c) 816 | { 817 | action (c.OptionValues); 818 | } 819 | } 820 | 821 | public OptionSet Add (string prototype, Action action) 822 | { 823 | return Add (prototype, null, action); 824 | } 825 | 826 | public OptionSet Add (string prototype, string description, Action action) 827 | { 828 | if (action == null) 829 | throw new ArgumentNullException ("action"); 830 | Option p = new ActionOption (prototype, description, 1, 831 | delegate (OptionValueCollection v) { action (v [0]); }); 832 | base.Add (p); 833 | return this; 834 | } 835 | 836 | public OptionSet Add (string prototype, OptionAction action) 837 | { 838 | return Add (prototype, null, action); 839 | } 840 | 841 | public OptionSet Add (string prototype, string description, OptionAction action) 842 | { 843 | if (action == null) 844 | throw new ArgumentNullException ("action"); 845 | Option p = new ActionOption (prototype, description, 2, 846 | delegate (OptionValueCollection v) {action (v [0], v [1]);}); 847 | base.Add (p); 848 | return this; 849 | } 850 | 851 | sealed class ActionOption : Option { 852 | Action action; 853 | 854 | public ActionOption (string prototype, string description, Action action) 855 | : base (prototype, description, 1) 856 | { 857 | if (action == null) 858 | throw new ArgumentNullException ("action"); 859 | this.action = action; 860 | } 861 | 862 | protected override void OnParseComplete (OptionContext c) 863 | { 864 | action (Parse (c.OptionValues [0], c)); 865 | } 866 | } 867 | 868 | sealed class ActionOption : Option { 869 | OptionAction action; 870 | 871 | public ActionOption (string prototype, string description, OptionAction action) 872 | : base (prototype, description, 2) 873 | { 874 | if (action == null) 875 | throw new ArgumentNullException ("action"); 876 | this.action = action; 877 | } 878 | 879 | protected override void OnParseComplete (OptionContext c) 880 | { 881 | action ( 882 | Parse (c.OptionValues [0], c), 883 | Parse (c.OptionValues [1], c)); 884 | } 885 | } 886 | 887 | public OptionSet Add (string prototype, Action action) 888 | { 889 | return Add (prototype, null, action); 890 | } 891 | 892 | public OptionSet Add (string prototype, string description, Action action) 893 | { 894 | return Add (new ActionOption (prototype, description, action)); 895 | } 896 | 897 | public OptionSet Add (string prototype, OptionAction action) 898 | { 899 | return Add (prototype, null, action); 900 | } 901 | 902 | public OptionSet Add (string prototype, string description, OptionAction action) 903 | { 904 | return Add (new ActionOption (prototype, description, action)); 905 | } 906 | 907 | public OptionSet Add (ArgumentSource source) 908 | { 909 | if (source == null) 910 | throw new ArgumentNullException ("source"); 911 | sources.Add (source); 912 | return this; 913 | } 914 | 915 | protected virtual OptionContext CreateOptionContext () 916 | { 917 | return new OptionContext (this); 918 | } 919 | 920 | public List Parse (IEnumerable arguments) 921 | { 922 | if (arguments == null) 923 | throw new ArgumentNullException ("arguments"); 924 | OptionContext c = CreateOptionContext (); 925 | c.OptionIndex = -1; 926 | bool process = true; 927 | List unprocessed = new List (); 928 | Option def = Contains ("<>") ? this ["<>"] : null; 929 | ArgumentEnumerator ae = new ArgumentEnumerator (arguments); 930 | foreach (string argument in ae) { 931 | ++c.OptionIndex; 932 | if (argument == "--") { 933 | process = false; 934 | continue; 935 | } 936 | if (!process) { 937 | Unprocessed (unprocessed, def, c, argument); 938 | continue; 939 | } 940 | if (AddSource (ae, argument)) 941 | continue; 942 | if (!Parse (argument, c)) 943 | Unprocessed (unprocessed, def, c, argument); 944 | } 945 | if (c.Option != null) 946 | c.Option.Invoke (c); 947 | return unprocessed; 948 | } 949 | 950 | class ArgumentEnumerator : IEnumerable { 951 | List> sources = new List> (); 952 | 953 | public ArgumentEnumerator (IEnumerable arguments) 954 | { 955 | sources.Add (arguments.GetEnumerator ()); 956 | } 957 | 958 | public void Add (IEnumerable arguments) 959 | { 960 | sources.Add (arguments.GetEnumerator ()); 961 | } 962 | 963 | public IEnumerator GetEnumerator () 964 | { 965 | do { 966 | IEnumerator c = sources [sources.Count-1]; 967 | if (c.MoveNext ()) 968 | yield return c.Current; 969 | else { 970 | c.Dispose (); 971 | sources.RemoveAt (sources.Count-1); 972 | } 973 | } while (sources.Count > 0); 974 | } 975 | 976 | IEnumerator IEnumerable.GetEnumerator () 977 | { 978 | return GetEnumerator (); 979 | } 980 | } 981 | 982 | bool AddSource (ArgumentEnumerator ae, string argument) 983 | { 984 | foreach (ArgumentSource source in sources) { 985 | IEnumerable replacement; 986 | if (!source.GetArguments (argument, out replacement)) 987 | continue; 988 | ae.Add (replacement); 989 | return true; 990 | } 991 | return false; 992 | } 993 | 994 | private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) 995 | { 996 | if (def == null) { 997 | extra.Add (argument); 998 | return false; 999 | } 1000 | c.OptionValues.Add (argument); 1001 | c.Option = def; 1002 | c.Option.Invoke (c); 1003 | return false; 1004 | } 1005 | 1006 | private readonly Regex ValueOption = new Regex ( 1007 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); 1008 | 1009 | protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) 1010 | { 1011 | if (argument == null) 1012 | throw new ArgumentNullException ("argument"); 1013 | 1014 | flag = name = sep = value = null; 1015 | Match m = ValueOption.Match (argument); 1016 | if (!m.Success) { 1017 | return false; 1018 | } 1019 | flag = m.Groups ["flag"].Value; 1020 | name = m.Groups ["name"].Value; 1021 | if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { 1022 | sep = m.Groups ["sep"].Value; 1023 | value = m.Groups ["value"].Value; 1024 | } 1025 | return true; 1026 | } 1027 | 1028 | protected virtual bool Parse (string argument, OptionContext c) 1029 | { 1030 | if (c.Option != null) { 1031 | ParseValue (argument, c); 1032 | return true; 1033 | } 1034 | 1035 | string f, n, s, v; 1036 | if (!GetOptionParts (argument, out f, out n, out s, out v)) 1037 | return false; 1038 | 1039 | Option p; 1040 | if (Contains (n)) { 1041 | p = this [n]; 1042 | c.OptionName = f + n; 1043 | c.Option = p; 1044 | switch (p.OptionValueType) { 1045 | case OptionValueType.None: 1046 | c.OptionValues.Add (n); 1047 | c.Option.Invoke (c); 1048 | break; 1049 | case OptionValueType.Optional: 1050 | case OptionValueType.Required: 1051 | ParseValue (v, c); 1052 | break; 1053 | } 1054 | return true; 1055 | } 1056 | // no match; is it a bool option? 1057 | if (ParseBool (argument, n, c)) 1058 | return true; 1059 | // is it a bundled option? 1060 | if (ParseBundledValue (f, string.Concat (n + s + v), c)) 1061 | return true; 1062 | 1063 | return false; 1064 | } 1065 | 1066 | private void ParseValue (string option, OptionContext c) 1067 | { 1068 | if (option != null) 1069 | foreach (string o in c.Option.ValueSeparators != null 1070 | ? option.Split (c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None) 1071 | : new string[]{option}) { 1072 | c.OptionValues.Add (o); 1073 | } 1074 | if (c.OptionValues.Count == c.Option.MaxValueCount || 1075 | c.Option.OptionValueType == OptionValueType.Optional) 1076 | c.Option.Invoke (c); 1077 | else if (c.OptionValues.Count > c.Option.MaxValueCount) { 1078 | throw new OptionException (localizer (string.Format ( 1079 | "Error: Found {0} option values when expecting {1}.", 1080 | c.OptionValues.Count, c.Option.MaxValueCount)), 1081 | c.OptionName); 1082 | } 1083 | } 1084 | 1085 | private bool ParseBool (string option, string n, OptionContext c) 1086 | { 1087 | Option p; 1088 | string rn; 1089 | if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && 1090 | Contains ((rn = n.Substring (0, n.Length-1)))) { 1091 | p = this [rn]; 1092 | string v = n [n.Length-1] == '+' ? option : null; 1093 | c.OptionName = option; 1094 | c.Option = p; 1095 | c.OptionValues.Add (v); 1096 | p.Invoke (c); 1097 | return true; 1098 | } 1099 | return false; 1100 | } 1101 | 1102 | private bool ParseBundledValue (string f, string n, OptionContext c) 1103 | { 1104 | if (f != "-") 1105 | return false; 1106 | for (int i = 0; i < n.Length; ++i) { 1107 | Option p; 1108 | string opt = f + n [i].ToString (); 1109 | string rn = n [i].ToString (); 1110 | if (!Contains (rn)) { 1111 | if (i == 0) 1112 | return false; 1113 | throw new OptionException (string.Format (localizer ( 1114 | "Cannot bundle unregistered option '{0}'."), opt), opt); 1115 | } 1116 | p = this [rn]; 1117 | switch (p.OptionValueType) { 1118 | case OptionValueType.None: 1119 | Invoke (c, opt, n, p); 1120 | break; 1121 | case OptionValueType.Optional: 1122 | case OptionValueType.Required: { 1123 | string v = n.Substring (i+1); 1124 | c.Option = p; 1125 | c.OptionName = opt; 1126 | ParseValue (v.Length != 0 ? v : null, c); 1127 | return true; 1128 | } 1129 | default: 1130 | throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); 1131 | } 1132 | } 1133 | return true; 1134 | } 1135 | 1136 | private static void Invoke (OptionContext c, string name, string value, Option option) 1137 | { 1138 | c.OptionName = name; 1139 | c.Option = option; 1140 | c.OptionValues.Add (value); 1141 | option.Invoke (c); 1142 | } 1143 | 1144 | private const int OptionWidth = 29; 1145 | private const int Description_FirstWidth = 80 - OptionWidth; 1146 | private const int Description_RemWidth = 80 - OptionWidth - 2; 1147 | 1148 | public void WriteOptionDescriptions (TextWriter o) 1149 | { 1150 | foreach (Option p in this) { 1151 | int written = 0; 1152 | 1153 | Category c = p as Category; 1154 | if (c != null) { 1155 | WriteDescription (o, p.Description, "", 80, 80); 1156 | continue; 1157 | } 1158 | 1159 | if (!WriteOptionPrototype (o, p, ref written)) 1160 | continue; 1161 | 1162 | if (written < OptionWidth) 1163 | o.Write (new string (' ', OptionWidth - written)); 1164 | else { 1165 | o.WriteLine (); 1166 | o.Write (new string (' ', OptionWidth)); 1167 | } 1168 | 1169 | WriteDescription (o, p.Description, new string (' ', OptionWidth+2), 1170 | Description_FirstWidth, Description_RemWidth); 1171 | } 1172 | 1173 | foreach (ArgumentSource s in sources) { 1174 | string[] names = s.GetNames (); 1175 | if (names == null || names.Length == 0) 1176 | continue; 1177 | 1178 | int written = 0; 1179 | 1180 | Write (o, ref written, " "); 1181 | Write (o, ref written, names [0]); 1182 | for (int i = 1; i < names.Length; ++i) { 1183 | Write (o, ref written, ", "); 1184 | Write (o, ref written, names [i]); 1185 | } 1186 | 1187 | if (written < OptionWidth) 1188 | o.Write (new string (' ', OptionWidth - written)); 1189 | else { 1190 | o.WriteLine (); 1191 | o.Write (new string (' ', OptionWidth)); 1192 | } 1193 | 1194 | WriteDescription (o, s.Description, new string (' ', OptionWidth+2), 1195 | Description_FirstWidth, Description_RemWidth); 1196 | } 1197 | } 1198 | 1199 | void WriteDescription (TextWriter o, string value, string prefix, int firstWidth, int remWidth) 1200 | { 1201 | bool indent = false; 1202 | foreach (string line in GetLines (localizer (GetDescription (value)), firstWidth, remWidth)) { 1203 | if (indent) 1204 | o.Write (prefix); 1205 | o.WriteLine (line); 1206 | indent = true; 1207 | } 1208 | } 1209 | 1210 | bool WriteOptionPrototype (TextWriter o, Option p, ref int written) 1211 | { 1212 | string[] names = p.Names; 1213 | 1214 | int i = GetNextOptionIndex (names, 0); 1215 | if (i == names.Length) 1216 | return false; 1217 | 1218 | if (names [i].Length == 1) { 1219 | Write (o, ref written, " -"); 1220 | Write (o, ref written, names [0]); 1221 | } 1222 | else { 1223 | Write (o, ref written, " --"); 1224 | Write (o, ref written, names [0]); 1225 | } 1226 | 1227 | for ( i = GetNextOptionIndex (names, i+1); 1228 | i < names.Length; i = GetNextOptionIndex (names, i+1)) { 1229 | Write (o, ref written, ", "); 1230 | Write (o, ref written, names [i].Length == 1 ? "-" : "--"); 1231 | Write (o, ref written, names [i]); 1232 | } 1233 | 1234 | if (p.OptionValueType == OptionValueType.Optional || 1235 | p.OptionValueType == OptionValueType.Required) { 1236 | if (p.OptionValueType == OptionValueType.Optional) { 1237 | Write (o, ref written, localizer ("[")); 1238 | } 1239 | Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); 1240 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 1241 | ? p.ValueSeparators [0] 1242 | : " "; 1243 | for (int c = 1; c < p.MaxValueCount; ++c) { 1244 | Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); 1245 | } 1246 | if (p.OptionValueType == OptionValueType.Optional) { 1247 | Write (o, ref written, localizer ("]")); 1248 | } 1249 | } 1250 | return true; 1251 | } 1252 | 1253 | static int GetNextOptionIndex (string[] names, int i) 1254 | { 1255 | while (i < names.Length && names [i] == "<>") { 1256 | ++i; 1257 | } 1258 | return i; 1259 | } 1260 | 1261 | static void Write (TextWriter o, ref int n, string s) 1262 | { 1263 | n += s.Length; 1264 | o.Write (s); 1265 | } 1266 | 1267 | private static string GetArgumentName (int index, int maxIndex, string description) 1268 | { 1269 | if (description == null) 1270 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1271 | string[] nameStart; 1272 | if (maxIndex == 1) 1273 | nameStart = new string[]{"{0:", "{"}; 1274 | else 1275 | nameStart = new string[]{"{" + index + ":"}; 1276 | for (int i = 0; i < nameStart.Length; ++i) { 1277 | int start, j = 0; 1278 | do { 1279 | start = description.IndexOf (nameStart [i], j); 1280 | } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); 1281 | if (start == -1) 1282 | continue; 1283 | int end = description.IndexOf ("}", start); 1284 | if (end == -1) 1285 | continue; 1286 | return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); 1287 | } 1288 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1289 | } 1290 | 1291 | private static string GetDescription (string description) 1292 | { 1293 | if (description == null) 1294 | return string.Empty; 1295 | StringBuilder sb = new StringBuilder (description.Length); 1296 | int start = -1; 1297 | for (int i = 0; i < description.Length; ++i) { 1298 | switch (description [i]) { 1299 | case '{': 1300 | if (i == start) { 1301 | sb.Append ('{'); 1302 | start = -1; 1303 | } 1304 | else if (start < 0) 1305 | start = i + 1; 1306 | break; 1307 | case '}': 1308 | if (start < 0) { 1309 | if ((i+1) == description.Length || description [i+1] != '}') 1310 | throw new InvalidOperationException ("Invalid option description: " + description); 1311 | ++i; 1312 | sb.Append ("}"); 1313 | } 1314 | else { 1315 | sb.Append (description.Substring (start, i - start)); 1316 | start = -1; 1317 | } 1318 | break; 1319 | case ':': 1320 | if (start < 0) 1321 | goto default; 1322 | start = i + 1; 1323 | break; 1324 | default: 1325 | if (start < 0) 1326 | sb.Append (description [i]); 1327 | break; 1328 | } 1329 | } 1330 | return sb.ToString (); 1331 | } 1332 | 1333 | private static IEnumerable GetLines (string description, int firstWidth, int remWidth) 1334 | { 1335 | return StringCoda.WrappedLines (description, firstWidth, remWidth); 1336 | } 1337 | } 1338 | } 1339 | 1340 | --------------------------------------------------------------------------------