using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using HarmonyLib; using Newtonsoft.Json; using UnityEditor; using UnityEngine; using VRC.SDKBase; using BlackStartX.GestureManager.Editor.Modules.Vrc3; using BlackStartX.GestureManager.Editor.Modules.Vrc3.Params; [InitializeOnLoad] internal static class GestureManagerExporter { private static bool excludeAnimatorParameters = true; private static bool sortByDescriptor = true; static GestureManagerExporter() { var harmony = new Harmony("gay.lilyy.GestureManagerExporter"); harmony.PatchAll(); } [HarmonyPatch] private static class PatchParametersLayout { static MethodBase TargetMethod() { var nestedClass = typeof(ModuleVrc3).Assembly .GetType("BlackStartX.GestureManager.Editor.Modules.Vrc3.Vrc3Debug.Avatar.Vrc3AvatarDebugWindow+Static"); return nestedClass?.GetMethod("ParametersLayout", BindingFlags.NonPublic | BindingFlags.Static); } static void Postfix(ModuleVrc3 module, float width) { excludeAnimatorParameters = GUILayout.Toggle(excludeAnimatorParameters, "Exclude Animator-Only Parameters", GUILayout.Width(width)); sortByDescriptor = GUILayout.Toggle(sortByDescriptor, "Sort by Parameters File", GUILayout.Width(width)); if (!GUILayout.Button("Export Parameters to JSON", GUILayout.Width(width))) return; var path = EditorUtility.SaveFilePanel( "Export Parameters", "Assets", "avatar_parameters.json", "json" ); if (string.IsNullOrEmpty(path)) return; var parameters = new Dictionary(); var avatarDescriptor = module.AvatarDescriptor; foreach (var param in module.Params) { if (excludeAnimatorParameters) { if (avatarDescriptor.expressionParameters.FindParameter(param.Key) == null) { continue; } } object value = param.Value.Type switch { AnimatorControllerParameterType.Float => param.Value.FloatValue(), AnimatorControllerParameterType.Int => param.Value.IntValue(), AnimatorControllerParameterType.Bool => param.Value.BoolValue(), AnimatorControllerParameterType.Trigger => param.Value.BoolValue(), _ => throw new ArgumentOutOfRangeException(nameof(param.Value.Type), param.Value.Type, null) }; parameters[param.Key] = new { type = param.Value.TypeText, value }; } if (sortByDescriptor) { var orderedParams = new Dictionary(); var paramNamesInDescriptor = avatarDescriptor.expressionParameters.parameters .Select(p => p.name) .Where(name => parameters.ContainsKey(name)); foreach (var name in paramNamesInDescriptor) orderedParams[name] = parameters[name]; var unmatchedParams = parameters.Keys .Where(k => !orderedParams.ContainsKey(k)) .OrderBy(k => k); foreach (var name in unmatchedParams) orderedParams[name] = parameters[name]; parameters = orderedParams; } var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, FloatFormatHandling = FloatFormatHandling.String, // We'll format floats ourselves Converters = new List { new Float2DecimalConverter() } }; var json = JsonConvert.SerializeObject(parameters, settings); File.WriteAllText(path, json); EditorUtility.DisplayDialog("Export Complete", $"{parameters.Count} Parameters have been exported successfully!", "OK"); } } } class Float2DecimalConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(float) || objectType == typeof(double); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value is float f) writer.WriteRawValue(f.ToString("0.00")); else if (value is double d) writer.WriteRawValue(d.ToString("0.00")); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException(); }