using Dalamud.Logging; using HSUI.Interface.EnemyList; using HSUI.Interface.GeneralElements; using HSUI.Interface.Party; using HSUI.Interface.StatusEffects; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; namespace HSUI.Config { public abstract class PluginConfigObjectConverter : JsonConverter { protected Dictionary FieldConvertersMap = new Dictionary(); public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var genericMethod = GetType().GetMethod("ConvertJson"); var method = genericMethod?.MakeGenericMethod(objectType); return method?.Invoke(this, new object[] { reader, serializer }); } public T? ConvertJson(JsonReader reader, JsonSerializer serializer) where T : PluginConfigObject { Type type = typeof(T); T? config = null; try { ConstructorInfo? constructor = type.GetConstructor(new Type[] { }); if (constructor != null) { config = (T?)Activator.CreateInstance(); } else { config = (T?)ConfigurationManager.GetDefaultConfigObjectForType(type); } // last resource, hackily create an instance without calling the constructor if (config == null) { #pragma warning disable SYSLIB0050 // Type or member is obsolete config = (T)FormatterServices.GetUninitializedObject(type); #pragma warning restore SYSLIB0050 // Type or member is obsolete } } catch (Exception e) { Plugin.Logger.Error($"Error creating a {type.Name}: " + e.Message); } if (config == null) { return null; } try { JObject? jsonObject = (JObject?)serializer.Deserialize(reader); if (jsonObject == null) { return null; } Dictionary ValuesMap = new Dictionary(); // get values from json foreach (JProperty property in jsonObject.Properties()) { string propertyName = property.Name; object? value = null; // convert values if needed if (FieldConvertersMap.TryGetValue(propertyName, out PluginConfigObjectFieldConverter? fieldConverter) && fieldConverter != null) { (propertyName, value) = fieldConverter.Convert(property.Value); } // read value from json else { FieldInfo? field = type.GetField(propertyName); if (field != null) { value = property.Value.ToObject(field.FieldType); } } if (value != null) { ValuesMap.Add(propertyName, value); } } // apply values foreach (string key in ValuesMap.Keys) { string[] fields = key.Split(".", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); object? currentObject = config; object value = ValuesMap[key]; for (int i = 0; i < fields.Length; i++) { FieldInfo? field = currentObject?.GetType().GetField(fields[i]); if (field == null) { break; } if (i == fields.Length - 1) { try { field.SetValue(currentObject, value); } catch { } } else { currentObject = field.GetValue(currentObject); } } } } catch (Exception e) { Plugin.Logger.Error($"Error deserializing {type.Name}: " + e.Message); } return config; } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { if (value == null) { return; } JObject jsonObject = new JObject(); Type type = value.GetType(); jsonObject.Add("$type", type.FullName + ", HSUI"); FieldInfo[] fields = type.GetFields(); foreach (FieldInfo field in fields) { if (field.GetCustomAttribute() != null) { continue; } object? fieldValue = field.GetValue(value); if (fieldValue != null) { jsonObject.Add(field.Name, JToken.FromObject(fieldValue, serializer)); } } jsonObject.WriteTo(writer); } } #region contract resolver public class PluginConfigObjectsContractResolver : DefaultContractResolver { private static Dictionary ConvertersMap = new Dictionary() { [typeof(UnitFrameConfig)] = typeof(ColorByHealthFieldsConverter), [typeof(PlayerUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter), [typeof(TargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter), [typeof(TargetOfTargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter), [typeof(FocusTargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter), [typeof(PartyFramesColorsConfig)] = typeof(ColorByHealthFieldsConverter), [typeof(PartyFramesRoleIconConfig)] = typeof(PartyFramesIconsConverter), [typeof(PartyFramesLeaderIconConfig)] = typeof(PartyFramesIconsConverter), [typeof(PartyFramesRaiseTrackerConfig)] = typeof(PartyFramesTrackerConfigConverter), [typeof(PartyFramesInvulnTrackerConfig)] = typeof(PartyFramesTrackerConfigConverter), [typeof(PartyFramesManaBarConfig)] = typeof(PartyFramesManaBarConfigConverter), [typeof(StatusEffectsBlacklistConfig)] = typeof(StatusEffectsBlacklistConfigConverter), [typeof(HUDOptionsConfig)] = typeof(HUDOptionsConfigConverter), [typeof(CastbarConfig)] = typeof(CastbarConfigConverter), [typeof(UnitFrameCastbarConfig)] = typeof(CastbarConfigConverter), [typeof(PlayerCastbarConfig)] = typeof(CastbarConfigConverter), [typeof(TargetCastbarConfig)] = typeof(CastbarConfigConverter), [typeof(TargetOfTargetCastbarConfig)] = typeof(CastbarConfigConverter), [typeof(FocusTargetCastbarConfig)] = typeof(CastbarConfigConverter), [typeof(PartyFramesCastbarConfig)] = typeof(CastbarConfigConverter), [typeof(EnemyListCastbarConfig)] = typeof(CastbarConfigConverter), }; protected override JsonObjectContract CreateObjectContract(Type objectType) { JsonObjectContract contract = base.CreateObjectContract(objectType); if (ConvertersMap.TryGetValue(objectType, out Type? converterType) && converterType != null) { contract.Converter = (JsonConverter?)Activator.CreateInstance(converterType); } return contract; } } #endregion #region field converters public abstract class PluginConfigObjectFieldConverter { public readonly string NewFieldPath; public PluginConfigObjectFieldConverter(string newFieldPath) { NewFieldPath = newFieldPath; } public abstract (string, object) Convert(JToken token); } public class NewTypeFieldConverter : PluginConfigObjectFieldConverter where TOld : struct where TNew : struct { private TNew DefaultValue; private Func Func; public NewTypeFieldConverter(string newFieldPath, TNew defaultValue, Func func) : base(newFieldPath) { DefaultValue = defaultValue; Func = func; } public override (string, object) Convert(JToken token) { TNew result = DefaultValue; TOld? oldValue = token.ToObject(); if (oldValue.HasValue) { result = Func(oldValue.Value); } return (NewFieldPath, result); } } public class SameTypeFieldConverter : NewTypeFieldConverter where T : struct { public SameTypeFieldConverter(string newFieldPath, T defaultValue) : base(newFieldPath, defaultValue, (oldValue) => { return oldValue; }) { } } public class NewClassFieldConverter : PluginConfigObjectFieldConverter where TOld : class where TNew : class { private TNew DefaultValue; private Func Func; public NewClassFieldConverter(string newFieldPath, TNew defaultValue, Func func) : base(newFieldPath) { DefaultValue = defaultValue; Func = func; } public override (string, object) Convert(JToken token) { TNew result = DefaultValue; TOld? oldValue = token.ToObject(); if (oldValue != null) { result = Func(oldValue); } return (NewFieldPath, result); } } public class SameClassFieldConverter : NewClassFieldConverter where T : class { public SameClassFieldConverter(string newFieldPath, T defaultValue) : base(newFieldPath, defaultValue, (oldValue) => { return oldValue; }) { } } public class TypeToClassFieldConverter : PluginConfigObjectFieldConverter where TOld : struct where TNew : class { private TNew DefaultValue; private Func Func; public TypeToClassFieldConverter(string newFieldPath, TNew defaultValue, Func func) : base(newFieldPath) { DefaultValue = defaultValue; Func = func; } public override (string, object) Convert(JToken token) { TNew result = DefaultValue; TOld? oldValue = token.ToObject(); if (oldValue.HasValue) { result = Func(oldValue.Value); } return (NewFieldPath, result); } } #endregion }