public IEnumerable <T> CheckForCollisions <T> (IEnumerable <T> collection, string type) where T : IMemberDefinition { void FindCollisions(HashSet <string> names, string kind) { var collisions = collection.Where(item => names.Contains(item.Name)); foreach (var collision in collisions) { SequencePoint point = ErrorEmitter.GetSequencePointFromMemberDefinition(collision); ErrorEmitter.Error(ErrorCode.CollidingTypeName, $"All Unreal types live in same namespace. {type} '{collision.Name}' conflicts with existing native {kind}", point); } collection = collection.Except(collisions); } FindCollisions(Classes, "class"); FindCollisions(Structs, "struct"); FindCollisions(Enums, "enum"); return(collection); }
public InvalidUnrealFunctionException(MethodDefinition method, string message, Exception innerException = null) : base(String.Format("Method '{0}' is not a valid Unreal function: {1}", method.Name, message), innerException, ErrorEmitter.GetSequencePointFromMemberDefinition(method)) { }
public InvalidUnrealPropertyException(IMemberDefinition property, string message) : this(property.FullName, ErrorEmitter.GetSequencePointFromMemberDefinition(property), message) { }
public InvalidUnrealEnumException(TypeDefinition enom, string message) : base(String.Format("Enum '{0}' is not a valid Unreal enum: {1}", enom.FullName, message), ErrorEmitter.GetSequencePointFromMemberDefinition(enom)) { }
public InvalidUnrealStructException(TypeDefinition strukt, string message) : base(String.Format("Struct '{0}' is not a valid Unreal struct: {1}", strukt.FullName, message), ErrorEmitter.GetSequencePointFromMemberDefinition(strukt)) { }
public InvalidUnrealClassException(TypeDefinition klass, string message) : this(klass.FullName, ErrorEmitter.GetSequencePointFromMemberDefinition(klass), message) { }
public ConstructorNotFoundException(TypeDefinition type, string message) : base(message, ErrorEmitter.GetSequencePointFromMemberDefinition(type)) { }
public InternalRewriteException(TypeDefinition type, string message) : base($"Internal error rewriting type {type.FullName}: {message}", ErrorEmitter.GetSequencePointFromMemberDefinition(type)) { }
public UnsupportedPropertyInitializerException(PropertyDefinition property, SequencePoint seq) : base($"Property initializers are not supported for properties of type {property.PropertyType.FullName}", seq ?? ErrorEmitter.GetSequencePointFromMemberDefinition(property)) { }
public UnableToFixPropertyBackingReferenceException(MethodDefinition constructor, PropertyDefinition property, OpCode opCode) : base($"The type {constructor.DeclaringType.FullName}'s constructor references the property {property.Name} using an unsupported IL pattern", ErrorEmitter.GetSequencePointFromMemberDefinition(constructor)) { }
public InvalidConstructorException(MethodDefinition constructor, string message) : base(message, ErrorEmitter.GetSequencePointFromMemberDefinition(constructor)) { }
public NotDerivableClassException(TypeDefinition klass, TypeDefinition superKlass) : base(String.Format("Class '{0}' is invalid because '{1}' may not be derived from in managed code.", klass.FullName, superKlass.FullName), ErrorEmitter.GetSequencePointFromMemberDefinition(klass)) { }
public InvalidEnumMemberException(TypeDefinition enom, FieldReference field, string message, Exception innerException = null) : base(String.Format("Enum '{0}' has invalid field '{1}': {2}", enom.Name, field.Name, message), innerException, ErrorEmitter.GetSequencePointFromMemberDefinition(enom)) { }
static void ProcessAssembly(AssemblyDefinition bindingsAssembly, string assemblyPath, string outputPath, BaseAssemblyResolver resolver, string [] knownPaths, NativeTypeManifest manifest, bool verify) { string metadataFileName = Path.ChangeExtension(outputPath, "json"); var readerParams = new ReaderParameters { AssemblyResolver = resolver, ReadSymbols = true, SymbolReaderProvider = new PortablePdbReaderProvider(), ReadingMode = ReadingMode.Deferred }; var assembly = AssemblyDefinition.ReadAssembly(assemblyPath, readerParams); var metadata = new AssemblyMetadata { AssemblyName = assembly.Name.Name, AssemblyPath = assemblyPath, References = (from module in assembly.Modules from reference in module.AssemblyReferences select AssemblyReferenceMetadata.ResolveReference(reference, resolver, assembly.Name.Name, knownPaths)).ToArray() }; /////////////////////////////////////////////////////////////////// //Enums var unrealEnums = (from module in assembly.Modules from type in module.Types where type.IsEnum && IsUnrealEnum(type) select type); unrealEnums = manifest.CheckForCollisions(unrealEnums, "Enum"); //Generate metadata bool errorGeneratingEnumMetadata = false; var enumMetadata = unrealEnums.ToDictionaryErrorEmit(type => type, type => new EnumMetadata(type), out errorGeneratingEnumMetadata); metadata.Enums = enumMetadata.Values.ToArray(); /////////////////////////////////////////////////////////////////// //Structs // Capture the current struct list in an array, as IL rewriting for UStructs may add array marshaler types. var unrealStructs = (from module in assembly.Modules from type in module.Types where type.IsValueType && IsUnrealStruct(type) select type); unrealStructs = manifest.CheckForCollisions(unrealStructs, "Struct"); // We need to create struct metadata in the correct order to ensure that blittable structs have // their UStruct attributes updated before other referencing structs use them to create UnrealTypes. var structStack = new Stack <TypeDefinition> (); var pushedStructs = new HashSet <TypeDefinition> (); var structHandlingOrder = new List <TypeDefinition> (); var structMetadata = new Dictionary <TypeDefinition, StructMetadata> (); bool errorsGeneratingStructMetadata = false; foreach (var unrealStruct in unrealStructs) { if (!pushedStructs.Contains(unrealStruct)) { structStack.Push(unrealStruct); pushedStructs.Add(unrealStruct); PushReferencedStructsFromAssembly(assembly, unrealStruct, structStack, pushedStructs); while (structStack.Count > 0) { var currentStruct = structStack.Pop(); try { if (structMetadata.ContainsKey(currentStruct)) { throw new InternalRewriteException(currentStruct, "Attempted to create struct metadata twice"); } var currentMetadata = new StructMetadata(currentStruct); structHandlingOrder.Add(currentStruct); structMetadata.Add(currentStruct, currentMetadata); } catch (MonoAssemblyProcessError error) { errorsGeneratingStructMetadata = true; ErrorEmitter.Error(error); } } } } metadata.Structs = structMetadata.Values.ToArray(); // Rewrite as a separate pass. For substructs, we need access to metadata.Structs to propagate hashes. foreach (var currentStruct in structHandlingOrder) { StructMetadata currentMetadata = structMetadata [currentStruct]; RewriteUnrealStruct(metadata, assembly, currentStruct, currentMetadata, bindingsAssembly); } /////////////////////////////////////////////////////////////////// //Classes bool unrealClassError; // get all the unreal classes (we don't care if they are exported) var unrealClasses = assembly.Modules.SelectMany(x => x.Types).SelectWhereErrorEmit(type => type.IsClass && type.BaseType != null && IsUnrealClass(type), type => type, out unrealClassError); unrealClasses = manifest.CheckForCollisions(unrealClasses, "Class"); bool errorGeneratingMetadata; var unrealClassDictionary = unrealClasses.ToDictionaryErrorEmit(type => type, type => ClassMetadata.Create(type, GetUnrealClass(type)), out errorGeneratingMetadata); if (errorGeneratingMetadata || errorsGeneratingStructMetadata || errorGeneratingEnumMetadata) { throw new MetaDataGenerationException(); } metadata.Classes = unrealClassDictionary.Values.ToArray(); if (unrealClassDictionary.Count > 0) { var rewriteStack = new Stack <KeyValuePair <TypeDefinition, ClassMetadata> > (); var rewrittenClasses = new HashSet <TypeDefinition> (); // make sure we write base classes before derived ones foreach (var pair in unrealClassDictionary) { var currentPair = pair; bool needRewrite = true; while (needRewrite) { if (!rewrittenClasses.Contains(currentPair.Key)) { rewriteStack.Push(currentPair); var baseType = currentPair.Key.BaseType.Resolve(); if (unrealClassDictionary.ContainsKey(baseType)) { ClassMetadata baseClassMetadata = unrealClassDictionary [baseType]; currentPair = new KeyValuePair <TypeDefinition, ClassMetadata> (baseType, baseClassMetadata); } else { needRewrite = false; } } else { needRewrite = false; } } while (rewriteStack.Count > 0) { currentPair = rewriteStack.Pop(); if (rewrittenClasses.Contains(currentPair.Key)) { throw new InternalRewriteException(currentPair.Key, "Attempted to rewrite class twice"); } var baseType = currentPair.Key.BaseType; if (baseType != null && unrealClassDictionary.ContainsKey(baseType.Resolve())) { ClassMetadata superClassMetadata = unrealClassDictionary [baseType.Resolve()]; currentPair.Value.SetSuperClassHash(superClassMetadata); } RewriteUnrealClass(metadata, assembly, currentPair.Key, currentPair.Value, bindingsAssembly); rewrittenClasses.Add(currentPair.Key); } } var writer = new WriterParameters { WriteSymbols = true, SymbolWriterProvider = new PortablePdbWriterProvider() }; assembly.Write(outputPath, writer); VerifyAssembly(outputPath, knownPaths, verify); //for some reason msbuild uses creation time or incrementral builds, not mtime //but cecil preserves the creation time from the original so we have to reset it File.SetCreationTimeUtc(outputPath, DateTime.UtcNow); string metadataContents = fastJSON.JSON.ToNiceJSON(metadata, new fastJSON.JSONParameters { UseExtensions = false }); File.WriteAllText(metadataFileName, metadataContents); } else { File.Delete(metadataFileName); } }
static bool ProcessAssemblyInPlace(AssemblyDefinition bindingsAssembly, string assemblyPath, BaseAssemblyResolver resolver, string [] knownPaths, NativeTypeManifest manifest, bool verify) { string backupFile = Path.ChangeExtension(assemblyPath, ".bak.dll"); string pdbFile = Path.ChangeExtension(assemblyPath, ".pdb"); string backupPdbFile = Path.ChangeExtension(pdbFile, ".bak.pdb"); string metadataFileName = Path.ChangeExtension(assemblyPath, "json"); // before we rewrite, copy the original assembly to a backup location // if we're debugging we might be using backup files directly, don't do the copy if (!assemblyPath.Contains(".bak")) { File.Copy(assemblyPath, backupFile, true); if (File.Exists(pdbFile)) { File.Copy(pdbFile, backupPdbFile, true); } } try { ProcessAssembly(bindingsAssembly, assemblyPath, assemblyPath, resolver, knownPaths, manifest, verify); // if we got this far without an exception, safe to delete the bak files if (File.Exists(backupFile)) { File.Delete(backupFile); } if (File.Exists(backupPdbFile)) { File.Delete(backupPdbFile); } } catch (MonoAssemblyProcessError error) { ErrorEmitter.Error(error); // if we're debugging, we may be operating on the backup files if (!assemblyPath.Contains(".bak")) { // delete originals so UE4 won't think the assembly is valid if (File.Exists(assemblyPath)) { File.Delete(assemblyPath); } if (File.Exists(pdbFile)) { File.Delete(pdbFile); } if (File.Exists(metadataFileName)) { File.Delete(metadataFileName); } } return(false); } catch (Exception e) { Console.Error.WriteLine("Exception processing {0}", assemblyPath); Console.Error.WriteLine(e.Message); Console.Error.WriteLine(e.StackTrace); // if we're debugging, we may be operating on the backup files if (!assemblyPath.Contains(".bak")) { // delete originals so UE4 won't think the assembly is valid if (File.Exists(assemblyPath)) { File.Delete(assemblyPath); } if (File.Exists(pdbFile)) { File.Delete(pdbFile); } if (File.Exists(metadataFileName)) { File.Delete(metadataFileName); } } return(false); } return(true); }
static int Main(string [] args) { var assemblyPaths = new List <string> (); string baseUnrealBindingsNamespace = "UnrealEngine"; bool showHelp = false; bool verify = false; string outputDir = null; var options = new OptionSet() { { "p|path=", "Additional search paths for assemblies", v => assemblyPaths.Add(StripQuotes(v)) }, { "n|namespace=", "Base unreal bindings namespace", v => baseUnrealBindingsNamespace = v }, { "o|output=", "Output directory", v => outputDir = v }, { "h|help", "Show this message and exit", v => showHelp = v != null }, { "verify", "Verify processed assemblies", v => verify = v != null } }; List <string> extra; try { extra = options.Parse(args); } catch (OptionException e) { Console.Error.Write("MonoAssemblyProcess: "); Console.Error.WriteLine(e.Message); Console.Error.WriteLine("Try 'MonoAssemblyProcess --help' for more information"); return(1); } if (showHelp) { ShowHelp(options); return(0); } if (extra.Count == 0) { Console.Error.WriteLine("Need at least one assembly to process!"); Console.Error.WriteLine("Try 'MonoAssemblyProcess --help' for more information"); return(2); } NativeTypeManifest manifest = new NativeTypeManifest(); DefaultAssemblyResolver resolver = new DefaultAssemblyResolver(); foreach (var lookupPath in assemblyPaths) { if (Directory.Exists(lookupPath)) { string nativeClassManifest = Path.Combine(lookupPath, "AllNativeClasses.manifest"); if (File.Exists(nativeClassManifest)) { manifest = new NativeTypeManifest(nativeClassManifest); } resolver.AddSearchDirectory(lookupPath); } else { Console.Error.WriteLine("Warning: Assembly resolve path {0} does not exist, skipping", lookupPath); } } string [] knownPaths = assemblyPaths.ToArray(); BaseUnrealNamespace = baseUnrealBindingsNamespace; BindingsNamespace = baseUnrealBindingsNamespace + "." + BindingsSubnamespace; AssemblyDefinition bindingsAssembly = resolver.Resolve(new AssemblyNameReference(BindingsNamespace, new Version(0, 0, 0, 0))); if (null == bindingsAssembly) { Console.Error.WriteLine("Could not find bindings assembly: " + BindingsNamespace); return(3); } foreach (var quotedAssemblyPath in extra) { var assemblyPath = StripQuotes(quotedAssemblyPath); if (outputDir == null) { if (!ProcessAssemblyInPlace(bindingsAssembly, assemblyPath, resolver, knownPaths, manifest, verify)) { return(4); } } else { var outputPath = Path.Combine(outputDir, Path.GetFileName(assemblyPath)); string nativeClassManifest = Path.Combine(outputPath, "AllNativeClasses.manifest"); if (File.Exists(nativeClassManifest)) { manifest = new NativeTypeManifest(nativeClassManifest); } Directory.CreateDirectory(outputDir); try { ProcessAssembly(bindingsAssembly, assemblyPath, outputPath, resolver, knownPaths, manifest, verify); } catch (MonoAssemblyProcessError error) { ErrorEmitter.Error(error); } catch (Exception ex) { Console.Error.WriteLine("Exception processing {0}", assemblyPath); Console.Error.WriteLine(ex.Message); Console.Error.WriteLine(ex.StackTrace); return(4); } } } return(0); }