diff --git a/Penumbra/Meta/Manipulations/EqpManipulation.cs b/Penumbra/Meta/Manipulations/EqpManipulation.cs index 430dbb9f..b87d0d7a 100644 --- a/Penumbra/Meta/Manipulations/EqpManipulation.cs +++ b/Penumbra/Meta/Manipulations/EqpManipulation.cs @@ -6,14 +6,18 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; using Penumbra.Meta.Files; +using Penumbra.Util; namespace Penumbra.Meta.Manipulations; [StructLayout( LayoutKind.Sequential, Pack = 1 )] public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation > { - public readonly EqpEntry Entry; - public readonly ushort SetId; + [JsonConverter( typeof( ForceNumericFlagEnumConverter ) )] + public readonly EqpEntry Entry; + + public readonly ushort SetId; + [JsonConverter( typeof( StringEnumConverter ) )] public readonly EquipSlot Slot; diff --git a/Penumbra/Util/FixedUlongStringEnumConverter.cs b/Penumbra/Util/FixedUlongStringEnumConverter.cs new file mode 100644 index 00000000..1ab68e4e --- /dev/null +++ b/Penumbra/Util/FixedUlongStringEnumConverter.cs @@ -0,0 +1,92 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Penumbra.Util; + +// Json.Net has a bug +// ulong enums can not be correctly deserialized if they exceed long.MaxValue. +// These converters fix this, taken from https://stackoverflow.com/questions/61740964/json-net-unable-to-deserialize-ulong-flag-type-enum/ +public class ForceNumericFlagEnumConverter : FixedUlongStringEnumConverter +{ + private static bool HasFlagsAttribute( Type? objectType ) + => objectType != null && Attribute.IsDefined( Nullable.GetUnderlyingType( objectType ) ?? objectType, typeof( System.FlagsAttribute ) ); + + public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer ) + { + var enumType = value?.GetType(); + if( HasFlagsAttribute( enumType ) ) + { + var underlyingType = Enum.GetUnderlyingType( enumType! ); + var underlyingValue = Convert.ChangeType( value, underlyingType ); + writer.WriteValue( underlyingValue ); + } + else + { + base.WriteJson( writer, value, serializer ); + } + } +} + +public class FixedUlongStringEnumConverter : StringEnumConverter +{ + public override object? ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer ) + { + if( reader.MoveToContentAndAssert().TokenType != JsonToken.Integer || reader.ValueType != typeof( System.Numerics.BigInteger ) ) + { + return base.ReadJson( reader, objectType, existingValue, serializer ); + } + + // Todo: throw an exception if !this.AllowIntegerValues + // https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Converters_StringEnumConverter_AllowIntegerValues.htm + var enumType = Nullable.GetUnderlyingType( objectType ) ?? objectType; + if( Enum.GetUnderlyingType( enumType ) == typeof( ulong ) ) + { + var bigInteger = ( System.Numerics.BigInteger )reader.Value!; + if( bigInteger >= ulong.MinValue && bigInteger <= ulong.MaxValue ) + { + return Enum.ToObject( enumType, checked( ( ulong )bigInteger ) ); + } + } + + return base.ReadJson( reader, objectType, existingValue, serializer ); + } +} + +public static partial class JsonExtensions +{ + public static JsonReader MoveToContentAndAssert( this JsonReader reader ) + { + if( reader == null ) + { + throw new ArgumentNullException(); + } + + if( reader.TokenType == JsonToken.None ) // Skip past beginning of stream. + { + reader.ReadAndAssert(); + } + + while( reader.TokenType == JsonToken.Comment ) // Skip past comments. + { + reader.ReadAndAssert(); + } + + return reader; + } + + public static JsonReader ReadAndAssert( this JsonReader reader ) + { + if( reader == null ) + { + throw new ArgumentNullException(); + } + + if( !reader.Read() ) + { + throw new JsonReaderException( "Unexpected end of JSON stream." ); + } + + return reader; + } +} \ No newline at end of file