1
0
mirror of https://github.com/Raymonf/whack.git synced 2024-11-14 21:07:36 +01:00
whack/WTT/UAssetAPI/MainSerializer.cs

314 lines
15 KiB
C#
Raw Normal View History

2022-09-29 00:37:28 +02:00
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
{
/// <summary>
/// An entry in the property type registry. Contains the class Type used for standard and struct property serialization.
/// </summary>
internal class RegistryEntry
{
internal Type PropertyType;
internal bool HasCustomStructSerialization;
public RegistryEntry()
{
}
}
/// <summary>
/// The main serializer for most property types in UAssetAPI.
/// </summary>
public static class MainSerializer
{
#if DEBUG
private static PropertyData lastType;
#endif
private static IDictionary<string, RegistryEntry> _propertyTypeRegistry;
/// <summary>
/// The property type registry. Maps serialized property names to their types.
/// </summary>
internal static IDictionary<string, RegistryEntry> PropertyTypeRegistry
{
get
{
InitializePropertyTypeRegistry();
return _propertyTypeRegistry;
}
set => _propertyTypeRegistry = value; // I hope you know what you're doing!
}
private static IEnumerable<Assembly> GetDependentAssemblies(Assembly analyzedAssembly)
{
return AppDomain.CurrentDomain.GetAssemblies().Where(a => GetNamesOfAssembliesReferencedBy(a).Contains(analyzedAssembly.FullName));
}
public static IEnumerable<string> GetNamesOfAssembliesReferencedBy(Assembly assembly)
{
return assembly.GetReferencedAssemblies().Select(assemblyName => assemblyName.FullName);
}
private static Type registryParentDataType = typeof(PropertyData);
/// <summary>
/// Initializes the property type registry.
/// </summary>
private static void InitializePropertyTypeRegistry()
{
if (_propertyTypeRegistry != null) return;
_propertyTypeRegistry = new Dictionary<string, RegistryEntry>();
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;
}
/// <summary>
/// Initializes the correct PropertyData class based off of serialized name, type, etc.
/// </summary>
/// <param name="type">The serialized type of this property.</param>
/// <param name="name">The serialized name of this property.</param>
/// <param name="asset">The UAsset which this property is contained within.</param>
/// <param name="reader">The BinaryReader to read from. If left unspecified, you must call the <see cref="PropertyData.Read(AssetBinaryReader, bool, long, long)"/> method manually.</param>
/// <param name="leng">The length of this property on disk in bytes.</param>
/// <param name="duplicationIndex">The duplication index of this property.</param>
/// <param name="includeHeader">Does this property serialize its header in the current context?</param>
/// <returns>A new PropertyData instance based off of the passed parameters.</returns>
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;
}
/// <summary>
/// Reads a property into memory.
/// </summary>
/// <param name="reader">The BinaryReader to read from. The underlying stream should be at the position of the property to be read.</param>
/// <param name="includeHeader">Does this property serialize its header in the current context?</param>
/// <returns>The property read from disk.</returns>
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);
/// <summary>
/// Reads an FProperty into memory. Primarily used as a part of <see cref="StructExport"/> serialization.
/// </summary>
/// <param name="reader">The BinaryReader to read from. The underlying stream should be at the position of the FProperty to be read.</param>
/// <returns>The FProperty read from disk.</returns>
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;
}
/// <summary>
/// Serializes an FProperty from memory.
/// </summary>
/// <param name="prop">The FProperty to serialize.</param>
/// <param name="writer">The BinaryWriter to serialize the FProperty to.</param>
public static void WriteFProperty(FProperty prop, AssetBinaryWriter writer)
{
writer.Write(prop.SerializedType);
prop.Write(writer);
}
/// <summary>
/// Reads a UProperty into memory. Primarily used as a part of <see cref="PropertyExport"/> serialization.
/// </summary>
/// <param name="reader">The BinaryReader to read from. The underlying stream should be at the position of the UProperty to be read.</param>
/// <param name="serializedType">The type of UProperty to be read.</param>
/// <returns>The FProperty read from disk.</returns>
public static UProperty ReadUProperty(AssetBinaryReader reader, FName serializedType)
{
return ReadUProperty(reader, Type.GetType("UAssetAPI.FieldTypes.U" + allNonLetters.Replace(serializedType.Value.Value, string.Empty)));
}
/// <summary>
/// Reads a UProperty into memory. Primarily used as a part of <see cref="PropertyExport"/> serialization.
/// </summary>
/// <param name="reader">The BinaryReader to read from. The underlying stream should be at the position of the UProperty to be read.</param>
/// <param name="requestedType">The type of UProperty to be read.</param>
/// <returns>The FProperty read from disk.</returns>
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;
}
/// <summary>
/// Reads a UProperty into memory. Primarily used as a part of <see cref="PropertyExport"/> serialization.
/// </summary>
/// <param name="reader">The BinaryReader to read from. The underlying stream should be at the position of the UProperty to be read.</param>
/// <returns>The FProperty read from disk.</returns>
public static T ReadUProperty<T>(AssetBinaryReader reader) where T : UProperty
{
var res = (UProperty)Activator.CreateInstance(typeof(T));
res.Read(reader);
return (T)res;
}
/// <summary>
/// Serializes a UProperty from memory.
/// </summary>
/// <param name="prop">The UProperty to serialize.</param>
/// <param name="writer">The BinaryWriter to serialize the UProperty to.</param>
public static void WriteUProperty(UProperty prop, AssetBinaryWriter writer)
{
prop.Write(writer);
}
/// <summary>
/// Serializes a property from memory.
/// </summary>
/// <param name="property">The property to serialize.</param>
/// <param name="writer">The BinaryWriter to serialize the property to.</param>
/// <param name="includeHeader">Does this property serialize its header in the current context?</param>
/// <returns>The serial offset where the length of the property is stored.</returns>
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;
}
}
}