Editor/TypeLibraryFixes/TypeLibraryExtensions.cs
using Sandbox;
using Sandbox.Internal;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
namespace ExtendedEditor.TypeLibraryFixes;
public static class TypeLibraryExtensions
{
public static SerializedProperty CreatePropertyFixed<T>(this TypeLibrary typeLibrary, string title,
Func<T> get, Action<T> set, IEnumerable<Attribute>? attributes = null, SerializedObject? parent = null)
{
return new ActionBasedSerializedPropertyFixed(typeof(T), title, title, string.Empty,
() => get()!, v => { if(v is T t) set(t); }, attributes, parent)
{
PropertyToObject = TypeSerializedPropertyFixed.PropertyToObjectFixed
};
}
public static void CreateObjectValue(this SerializedProperty property)
{
var value = property.GetValue<object>();
if(value != null)
return;
var type = property.PropertyType;
if(property.TryGetAttribute<TypeHintAttribute>(out var typeHint))
type = typeHint.HintedType;
if(!type.IsValueType)
return;
property.SetValue(property.GetDefault());
}
extension(SerializedProperty)
{
public static T? ValueToTypeFixed<T>(T? value, T defaultValue = default!) =>
(T)ValueToTypeFixed(typeof(T), value, defaultValue)!;
public static object? ValueToTypeFixed(Type type, object? value, object? defaultValue = default)
{
try
{
if(value == null)
return defaultValue;
if(value.GetType().IsAssignableTo(type))
return value;
if(type.IsNullableValueType())
return ValueToTypeFixed(type.GetGenericArguments()[0], value, defaultValue);
if(type == typeof(string))
return FormattableString.Invariant($"{value}");
if(value.GetType() == typeof(string))
return JsonSerializer.Deserialize((string)value, type)!;
if(type.IsEnum && value is IConvertible)
{
try
{
return Enum.ToObject(type, Convert.ToInt64(value));
}
catch
{
return defaultValue;
}
}
object obj2 = Convert.ChangeType(value, type);
if(obj2 != null)
return obj2;
return JsonSerializer.SerializeToElement(value).Deserialize(type)!;
}
catch
{
return defaultValue;
}
}
}
private static readonly Assembly _reflectionAssembly = Assembly.Load("Sandbox.Reflection");
private static readonly Assembly _systemAssembly = Assembly.Load("Sandbox.System");
private static readonly Type _legacyTypeSerializedObjectType = _reflectionAssembly.GetType("Sandbox.Internal.TypeSerializedObject")!;
private static readonly FieldInfo _legacyTypeSerializedObjectDesriptionField =
_legacyTypeSerializedObjectType?.GetField("TypeDescription", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
private static readonly MethodInfo _legacyTypeSerializedObjectGetTargetObjectMethod =
_legacyTypeSerializedObjectType?.GetMethod("GetTargetObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, [])!;
private static readonly FieldInfo _multiSerializedObjectChildrenField =
typeof(MultiSerializedObject).GetField("children", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
public static SerializedObject Fix(this SerializedObject obj)
{
if(obj is TypeSerializedObjectFixed)
return obj;
if(obj is SerializedCollection collection)
{
collection.PropertyToObject = TypeSerializedPropertyFixed.PropertyToObjectFixed;
//TODO test
return collection;
}
if(obj is MultiSerializedObject multiSerializedObject)
{
List<SerializedObject> children = (List<SerializedObject>)_multiSerializedObjectChildrenField.GetValue(multiSerializedObject)!;
var childrenClone = children.ToArray();
children.Clear();
foreach(var child in childrenClone)
{
multiSerializedObject.Add(child.Fix());
}
multiSerializedObject.Rebuild();
return multiSerializedObject;
}
if(obj.GetType() == _legacyTypeSerializedObjectType)
{
TypeDescription typeDescription = (TypeDescription)_legacyTypeSerializedObjectDesriptionField.GetValue(obj)!;
object target = _legacyTypeSerializedObjectGetTargetObjectMethod.Invoke(obj, null)!;
var type = typeDescription.TargetType;
//TODO fix generic types
var result = new TypeSerializedObjectFixed(target, type, obj.ParentProperty);
result.OnPropertyChanged += obj.NoteChanged;
return result;
}
throw new NotImplementedException($"Can't fix {obj.GetType()}");
}
private static readonly Type _legacyTypeSerializedPropertyType = _reflectionAssembly.GetType("Sandbox.Internal.TypeSerializedProperty")!;
private static readonly FieldInfo _legacyTypeSerializedPropertyDescriptionField =
_legacyTypeSerializedPropertyType?.GetField("prop", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
private static readonly Type _legacyTypeSerializedFieldType = _reflectionAssembly.GetType("Sandbox.Internal.TypeSerializedField")!;
private static readonly FieldInfo _legacyTypeSerializedFieldDesriptionField =
_legacyTypeSerializedFieldType?.GetField("field", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
private static readonly Type _legacyActionBasedSerializedPropertyType = _systemAssembly.GetType("Sandbox.ActionBasedSerializedProperty`1")!;
private static bool IsFixed(this SerializedProperty property, HashSet<SerializedProperty>? tested = null)
{
if(tested is not null && tested.Contains(property))
return true;
if(property is TypeSerializedPropertyFixed)
return true;
if(property is ActionBasedSerializedPropertyFixed)
return true;
if(property is SerializedPropertyProxy proxy)
{
if(proxy.Fixed.HasValue)
return proxy.Fixed.Value;
tested ??= [];
tested.Add(proxy);
var result = proxy.Target.IsFixed(tested);
proxy.Fixed = result;
return result;
}
if(property.Parent is SerializedCollection)
return true; // I hope we don't need to fix this
var propertyType = property.GetType();
if(propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == _legacyActionBasedSerializedPropertyType)
return true;
return false;
}
public static SerializedProperty Fix(this SerializedProperty property)
{
if(property.IsFixed())
return property;
if(property is SerializedPropertyProxy proxy)
{
if(proxy.Fixed == true)
return proxy;
return new SerializedPropertyProxy(proxy.Target.Fix(), proxy.PropertyType, proxy.GetAttributes())
{
Fixed = true,
};
}
var propertyType = property.GetType();
if(propertyType == _legacyTypeSerializedPropertyType)
{
var parentFixed = (TypeSerializedObjectFixed)property.Parent!.Fix();
PropertyDescription propertyDescription = (PropertyDescription)_legacyTypeSerializedPropertyDescriptionField.GetValue(property)!;
var prop = propertyDescription.DeclaringType.TargetType.GetProperty(propertyDescription.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
return new TypeSerializedPropertyFixed(parentFixed, prop);
}
if(propertyType == _legacyTypeSerializedFieldType)
{
var parentFixed = (TypeSerializedObjectFixed)property.Parent!.Fix();
FieldDescription fieldDescription = (FieldDescription)_legacyTypeSerializedFieldDesriptionField.GetValue(property)!;
var fiel = fieldDescription.DeclaringType.TargetType.GetField(fieldDescription.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
return new TypeSerializedPropertyFixed(parentFixed, fiel);
}
Log.Warning($"Can't fix {propertyType}");
return property;
}
}