using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using UAssetAPI.FieldTypes; using UAssetAPI.PropertyTypes.Objects; using UAssetAPI.PropertyTypes.Structs; using UAssetAPI.UnrealTypes; namespace UAssetAPI { /// /// An entry in the property type registry. Contains the class Type used for standard and struct property serialization. /// internal class RegistryEntry { internal Type PropertyType; internal bool HasCustomStructSerialization; public RegistryEntry() { } } /// /// The main serializer for most property types in UAssetAPI. /// public static class MainSerializer { #if DEBUG private static PropertyData lastType; #endif private static IDictionary _propertyTypeRegistry; /// /// The property type registry. Maps serialized property names to their types. /// internal static IDictionary PropertyTypeRegistry { get { InitializePropertyTypeRegistry(); return _propertyTypeRegistry; } set => _propertyTypeRegistry = value; // I hope you know what you're doing! } private static IEnumerable GetDependentAssemblies(Assembly analyzedAssembly) { return AppDomain.CurrentDomain.GetAssemblies().Where(a => GetNamesOfAssembliesReferencedBy(a).Contains(analyzedAssembly.FullName)); } public static IEnumerable GetNamesOfAssembliesReferencedBy(Assembly assembly) { return assembly.GetReferencedAssemblies().Select(assemblyName => assemblyName.FullName); } private static Type registryParentDataType = typeof(PropertyData); /// /// Initializes the property type registry. /// private static void InitializePropertyTypeRegistry() { if (_propertyTypeRegistry != null) return; _propertyTypeRegistry = new Dictionary(); Assembly[] allDependentAssemblies = GetDependentAssemblies(registryParentDataType.Assembly).ToArray(); Assembly[] allAssemblies = new Assembly[allDependentAssemblies.Length + 1]; allAssemblies[0] = registryParentDataType.Assembly; Array.Copy(allDependentAssemblies, 0, allAssemblies, 1, allDependentAssemblies.Length); for (int i = 0; i < allAssemblies.Length; i++) { Type[] allPropertyDataTypes = allAssemblies[i].GetTypes().Where(t => t.IsSubclassOf(registryParentDataType)).ToArray(); for (int j = 0; j < allPropertyDataTypes.Length; j++) { Type currentPropertyDataType = allPropertyDataTypes[j]; if (currentPropertyDataType == null || currentPropertyDataType.ContainsGenericParameters) continue; var testInstance = Activator.CreateInstance(currentPropertyDataType); FString returnedPropType = currentPropertyDataType.GetProperty("PropertyType")?.GetValue(testInstance, null) as FString; if (returnedPropType == null) continue; bool? returnedHasCustomStructSerialization = currentPropertyDataType.GetProperty("HasCustomStructSerialization")?.GetValue(testInstance, null) as bool?; if (returnedHasCustomStructSerialization == null) continue; bool? returnedShouldBeRegistered = currentPropertyDataType.GetProperty("ShouldBeRegistered")?.GetValue(testInstance, null) as bool?; if (returnedShouldBeRegistered == null) continue; if ((bool)returnedShouldBeRegistered) { RegistryEntry res = new RegistryEntry(); res.PropertyType = currentPropertyDataType; res.HasCustomStructSerialization = (bool)returnedHasCustomStructSerialization; _propertyTypeRegistry[returnedPropType.Value] = res; } } } // Fetch the current git commit while we're here UAPUtils.CurrentCommit = string.Empty; } /// /// Initializes the correct PropertyData class based off of serialized name, type, etc. /// /// The serialized type of this property. /// The serialized name of this property. /// The UAsset which this property is contained within. /// The BinaryReader to read from. If left unspecified, you must call the method manually. /// The length of this property on disk in bytes. /// The duplication index of this property. /// Does this property serialize its header in the current context? /// A new PropertyData instance based off of the passed parameters. public static PropertyData TypeToClass(FName type, FName name, UAsset asset, AssetBinaryReader reader = null, int leng = 0, int duplicationIndex = 0, bool includeHeader = true) { long startingOffset = 0; if (reader != null) startingOffset = reader.BaseStream.Position; if (type.Value.Value == "None") return null; PropertyData data = null; if (PropertyTypeRegistry.ContainsKey(type.Value.Value)) { data = (PropertyData)Activator.CreateInstance(PropertyTypeRegistry[type.Value.Value].PropertyType, name); } else { #if DEBUG Debug.WriteLine("-----------"); Debug.WriteLine("Parsing unknown type " + type.ToString()); Debug.WriteLine("Length: " + leng); if (reader != null) Debug.WriteLine("Pos: " + reader.BaseStream.Position); Debug.WriteLine("Last type: " + lastType.PropertyType?.ToString()); if (lastType is ArrayPropertyData) Debug.WriteLine("Last array's type was " + ((ArrayPropertyData)lastType).ArrayType?.ToString()); if (lastType is StructPropertyData) Debug.WriteLine("Last struct's type was " + ((StructPropertyData)lastType).StructType?.ToString()); if (lastType is MapPropertyData lastTypeMap) { if (lastTypeMap.Value.Count == 0) { Debug.WriteLine("Last map's key type was " + lastTypeMap.KeyType?.ToString()); Debug.WriteLine("Last map's value type was " + lastTypeMap.ValueType?.ToString()); } else { Debug.WriteLine("Last map's key type was " + lastTypeMap.Value.Keys.ElementAt(0).PropertyType?.ToString()); Debug.WriteLine("Last map's value type was " + lastTypeMap.Value[0].PropertyType?.ToString()); } } Debug.WriteLine("-----------"); #endif if (leng > 0) { data = new UnknownPropertyData(name); ((UnknownPropertyData)data).SetSerializingPropertyType(type.Value); } else { if (reader == null) throw new FormatException("Unknown property type: " + type.ToString() + " (on " + name.ToString() + ")"); throw new FormatException("Unknown property type: " + type.ToString() + " (on " + name.ToString() + " at " + reader.BaseStream.Position + ")"); } } #if DEBUG lastType = data; #endif data.DuplicationIndex = duplicationIndex; if (reader != null) { data.Read(reader, includeHeader, leng); if (data.Offset == 0) data.Offset = startingOffset; // fallback } return data; } /// /// Reads a property into memory. /// /// The BinaryReader to read from. The underlying stream should be at the position of the property to be read. /// Does this property serialize its header in the current context? /// The property read from disk. public static PropertyData Read(AssetBinaryReader reader, bool includeHeader) { long startingOffset = reader.BaseStream.Position; FName name = reader.ReadFName(); if (name.Value.Value == "None") return null; FName type = reader.ReadFName(); int leng = reader.ReadInt32(); int duplicationIndex = reader.ReadInt32(); PropertyData result = TypeToClass(type, name, reader.Asset, reader, leng, duplicationIndex, includeHeader); result.Offset = startingOffset; return result; } private static readonly Regex allNonLetters = new Regex("[^a-zA-Z]", RegexOptions.Compiled); /// /// Reads an FProperty into memory. Primarily used as a part of serialization. /// /// The BinaryReader to read from. The underlying stream should be at the position of the FProperty to be read. /// The FProperty read from disk. public static FProperty ReadFProperty(AssetBinaryReader reader) { FName serializedType = reader.ReadFName(); Type requestedType = Type.GetType("UAssetAPI.FieldTypes.F" + allNonLetters.Replace(serializedType.Value.Value, string.Empty)); if (requestedType == null) requestedType = typeof(FGenericProperty); var res = (FProperty)Activator.CreateInstance(requestedType); res.SerializedType = serializedType; res.Read(reader); return res; } /// /// Serializes an FProperty from memory. /// /// The FProperty to serialize. /// The BinaryWriter to serialize the FProperty to. public static void WriteFProperty(FProperty prop, AssetBinaryWriter writer) { writer.Write(prop.SerializedType); prop.Write(writer); } /// /// Reads a UProperty into memory. Primarily used as a part of serialization. /// /// The BinaryReader to read from. The underlying stream should be at the position of the UProperty to be read. /// The type of UProperty to be read. /// The FProperty read from disk. public static UProperty ReadUProperty(AssetBinaryReader reader, FName serializedType) { return ReadUProperty(reader, Type.GetType("UAssetAPI.FieldTypes.U" + allNonLetters.Replace(serializedType.Value.Value, string.Empty))); } /// /// Reads a UProperty into memory. Primarily used as a part of serialization. /// /// The BinaryReader to read from. The underlying stream should be at the position of the UProperty to be read. /// The type of UProperty to be read. /// The FProperty read from disk. public static UProperty ReadUProperty(AssetBinaryReader reader, Type requestedType) { if (requestedType == null) requestedType = typeof(UGenericProperty); var res = (UProperty)Activator.CreateInstance(requestedType); res.Read(reader); return res; } /// /// Reads a UProperty into memory. Primarily used as a part of serialization. /// /// The BinaryReader to read from. The underlying stream should be at the position of the UProperty to be read. /// The FProperty read from disk. public static T ReadUProperty(AssetBinaryReader reader) where T : UProperty { var res = (UProperty)Activator.CreateInstance(typeof(T)); res.Read(reader); return (T)res; } /// /// Serializes a UProperty from memory. /// /// The UProperty to serialize. /// The BinaryWriter to serialize the UProperty to. public static void WriteUProperty(UProperty prop, AssetBinaryWriter writer) { prop.Write(writer); } /// /// Serializes a property from memory. /// /// The property to serialize. /// The BinaryWriter to serialize the property to. /// Does this property serialize its header in the current context? /// The serial offset where the length of the property is stored. public static int Write(PropertyData property, AssetBinaryWriter writer, bool includeHeader) { if (property == null) return 0; property.Offset = writer.BaseStream.Position; writer.Write(property.Name); if (property is UnknownPropertyData unknownProp) { writer.Write(new FName(writer.Asset, unknownProp.SerializingPropertyType)); } else { writer.Write(new FName(writer.Asset, property.PropertyType)); } int oldLoc = (int)writer.BaseStream.Position; writer.Write((int)0); // initial length writer.Write(property.DuplicationIndex); int realLength = property.Write(writer, includeHeader); int newLoc = (int)writer.BaseStream.Position; writer.Seek(oldLoc, SeekOrigin.Begin); writer.Write(realLength); writer.Seek(newLoc, SeekOrigin.Begin); return oldLoc; } } }