diff options
Diffstat (limited to 'VRCSDK3Worlds/Assets/VRCSDK/SDK3')
59 files changed, 2906 insertions, 0 deletions
diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor.meta new file mode 100644 index 00000000..da574c55 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2251c772f96ba6542a69c961516313b6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3.meta new file mode 100644 index 00000000..5a73577c --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e9bf3b130aafe3c43a4a2de9f6168b76 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCDestructibleUdonEditor.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCDestructibleUdonEditor.cs new file mode 100644 index 00000000..d4c75bf1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCDestructibleUdonEditor.cs @@ -0,0 +1,59 @@ +/* +#if VRC_SDK_VRCSDK3 + +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using System; + +[CustomEditor(typeof(VRC.SDK3.Components.VRCDestructibleUdon))] +public class VRCDestructibleUdonEditor : Editor +{ + VRC.SDK3.Components.VRCDestructibleUdon myTarget; + + void OnEnable() + { + if (myTarget == null) + myTarget = (VRC.SDK3.Components.VRCDestructibleUdon)target; + } + + string[] UdonMethods = null; + string[] UdonVariables = null; + + public override void OnInspectorGUI() + { + var udon = myTarget.GetComponent<VRC.Udon.UdonBehaviour>(); + if (udon != null) + { + #if VRC_CLIENT + myTarget.UdonMethodApplyDamage = EditorGUILayout.TextField("On Apply Damage", myTarget.UdonMethodApplyDamage); + myTarget.UdonMethodApplyHealing= EditorGUILayout.TextField("On Apply Healing", myTarget.UdonMethodApplyHealing); + myTarget.UdonVariableCurrentHealth= EditorGUILayout.TextField("Current Health Variable", myTarget.UdonVariableCurrentHealth); + myTarget.UdonVariableMaxHealth = EditorGUILayout.TextField("On Max Health Variable", myTarget.UdonVariableMaxHealth); + #else + List<string> methods = new List<string>(udon.GetPrograms()); + methods.Insert(0, "-none-"); + myTarget.UdonMethodApplyDamage = DrawUdonProgramPicker("On Apply Damage", myTarget.UdonMethodApplyDamage, methods); + myTarget.UdonMethodApplyHealing = DrawUdonProgramPicker("On Apply Healing", myTarget.UdonMethodApplyHealing, methods); + List<string> variables = new List<string>(udon.publicVariables.VariableSymbols); + variables.Insert(0, "-none-"); + myTarget.UdonVariableCurrentHealth = DrawUdonProgramPicker("Current Health Variable", myTarget.UdonVariableCurrentHealth, variables); + myTarget.UdonVariableMaxHealth = DrawUdonProgramPicker("Max Health Variable", myTarget.UdonVariableMaxHealth, variables); + #endif + } + } + + string DrawUdonProgramPicker(string title, string current, List<string> choices) + { + int index = choices.IndexOf(current); + if (index == -1) + index = 0; + int value = EditorGUILayout.Popup(title, index, choices.ToArray()); + if (value != 0) + return choices[value]; + return current; + } +} +#endif +*/
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCDestructibleUdonEditor.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCDestructibleUdonEditor.cs.meta new file mode 100644 index 00000000..79c111bf --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCDestructibleUdonEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d57b23c04034119448f23c5fdbc57662 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCMidiEditor.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCMidiEditor.cs new file mode 100644 index 00000000..454d8dce --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCMidiEditor.cs @@ -0,0 +1,25 @@ +using UnityEditor; +using UnityEngine; +using VRC.SDK3.Midi; +using VRC.SDKBase.Midi; +#if (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN) && !UNITY_ANDROID +namespace VRC.SDK3.Editor +{ + [CustomEditor(typeof(VRCMidiListener))] + public class VRCMidiListenerEditor : UnityEditor.Editor + { +#if UNITY_STANDALONE_WIN + [RuntimeInitializeOnLoadMethod] + public static void InitializeMidi() + { + VRCMidiHandler.OnLog = (message) => Debug.Log(message); + VRCMidiHandler.Initialize = () => + { + return VRCMidiHandler.OpenMidiInput<VRCPortMidiInput>( + EditorPrefs.GetString(VRCMidiWindow.DEVICE_NAME_STRING)); + }; + } +#endif + } +} +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCMidiEditor.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCMidiEditor.cs.meta new file mode 100644 index 00000000..723ff8e5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCMidiEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5b1017097f3d46cd949892e0fce3ece9 +timeCreated: 1612853300
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCPlayerStationEditor3.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCPlayerStationEditor3.cs new file mode 100644 index 00000000..55f307c4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCPlayerStationEditor3.cs @@ -0,0 +1,33 @@ +#if VRC_SDK_VRCSDK3 + +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using System; + +[CustomEditor(typeof(VRC.SDK3.Components.VRCStation))] +public class VRCPlayerStationEditor3 : Editor +{ + VRC.SDK3.Components.VRCStation myTarget; + + void OnEnable() + { + if(myTarget == null) + myTarget = (VRC.SDK3.Components.VRCStation)target; + } + + public override void OnInspectorGUI() + { + myTarget.PlayerMobility = (VRC.SDKBase.VRCStation.Mobility)EditorGUILayout.EnumPopup("Player Mobility", myTarget.PlayerMobility); + myTarget.canUseStationFromStation = EditorGUILayout.Toggle("Can Use Station From Station", myTarget.canUseStationFromStation); + myTarget.animatorController = (RuntimeAnimatorController) EditorGUILayout.ObjectField("Animator Controller", myTarget.animatorController, typeof(RuntimeAnimatorController), false ); + myTarget.disableStationExit = EditorGUILayout.Toggle("Disable Station Exit", myTarget.disableStationExit ); + myTarget.seated = EditorGUILayout.Toggle("Seated", myTarget.seated); + myTarget.stationEnterPlayerLocation = (Transform)EditorGUILayout.ObjectField("Player Enter Location", myTarget.stationEnterPlayerLocation, typeof(Transform), true); + myTarget.stationExitPlayerLocation = (Transform)EditorGUILayout.ObjectField("Player Exit Location", myTarget.stationExitPlayerLocation, typeof(Transform), true); + myTarget.controlsObject = (VRC.SDKBase.VRC_ObjectApi)EditorGUILayout.ObjectField("API Object", myTarget.controlsObject, typeof(VRC.SDKBase.VRC_ObjectApi), false); + } + +} +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCPlayerStationEditor3.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCPlayerStationEditor3.cs.meta new file mode 100644 index 00000000..956b47c0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCPlayerStationEditor3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8901d07a685ca424492a3cabff506184 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCSceneDescriptorEditor3.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCSceneDescriptorEditor3.cs new file mode 100644 index 00000000..7eee02e3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCSceneDescriptorEditor3.cs @@ -0,0 +1,27 @@ +#if VRC_SDK_VRCSDK3 + +using UnityEditor; + +[CustomEditor (typeof(VRC.SDK3.Components.VRCSceneDescriptor))] +public class VRCSceneDescriptorEditor3 : Editor +{ + VRC.SDK3.Components.VRCSceneDescriptor sceneDescriptor; + VRC.Core.PipelineManager pipelineManager; + + public override void OnInspectorGUI() + { + if(sceneDescriptor == null) + sceneDescriptor = (VRC.SDK3.Components.VRCSceneDescriptor)target; + + if(pipelineManager == null) + { + pipelineManager = sceneDescriptor.GetComponent<VRC.Core.PipelineManager>(); + if(pipelineManager == null) + sceneDescriptor.gameObject.AddComponent<VRC.Core.PipelineManager>(); + } + + DrawDefaultInspector(); + } +} + +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCSceneDescriptorEditor3.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCSceneDescriptorEditor3.cs.meta new file mode 100644 index 00000000..df3e2505 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRCSceneDescriptorEditor3.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4b2b9ac625bc5b04c887ff9ee9b5fdbe +timeCreated: 1450463561 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_PickupEditor3.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_PickupEditor3.cs new file mode 100644 index 00000000..8954647d --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_PickupEditor3.cs @@ -0,0 +1,71 @@ +#if VRC_SDK_VRCSDK3 && UNITY_EDITOR + +using UnityEditor; +using UnityEngine; + +namespace VRC.SDK3 +{ + [CustomEditor(typeof(VRC.SDK3.Components.VRCPickup))] + public class VRC_PickupEditor3 : UnityEditor.Editor + { + private void InspectorField(string propertyName, string humanName) + { + SerializedProperty propertyField = serializedObject.FindProperty(propertyName); + EditorGUILayout.PropertyField(propertyField, new GUIContent(humanName), true); + } + + private SerializedProperty momentumTransferMethodProperty; + private SerializedProperty disallowTheftProperty; + private SerializedProperty exactGunProperty; + private SerializedProperty exactGripProperty; + private SerializedProperty allowManipulationWhenEquippedProperty; + private SerializedProperty orientationProperty; + private SerializedProperty autoHoldProperty; + private SerializedProperty interactionTextProperty; + private SerializedProperty useTextProperty; + private SerializedProperty throwVelocityBoostMinSpeedProperty; + private SerializedProperty throwVelocityBoostScaleProperty; + private SerializedProperty pickupableProperty; + private SerializedProperty proximityProperty; + + public override void OnInspectorGUI() + { + momentumTransferMethodProperty = serializedObject.FindProperty("MomentumTransferMethod"); + disallowTheftProperty = serializedObject.FindProperty("DisallowTheft"); + exactGunProperty = serializedObject.FindProperty("ExactGun"); + exactGripProperty = serializedObject.FindProperty("ExactGrip"); + allowManipulationWhenEquippedProperty = serializedObject.FindProperty("allowManipulationWhenEquipped"); + orientationProperty = serializedObject.FindProperty("orientation"); + autoHoldProperty = serializedObject.FindProperty("AutoHold"); + interactionTextProperty = serializedObject.FindProperty("InteractionText"); + useTextProperty = serializedObject.FindProperty("UseText"); + throwVelocityBoostMinSpeedProperty = serializedObject.FindProperty("ThrowVelocityBoostMinSpeed"); + throwVelocityBoostScaleProperty = serializedObject.FindProperty("ThrowVelocityBoostScale"); + pickupableProperty = serializedObject.FindProperty("pickupable"); + proximityProperty = serializedObject.FindProperty("proximity"); + + EditorGUILayout.BeginVertical(GUILayout.MaxWidth(EditorGUIUtility.currentViewWidth - 30)); + + EditorGUILayout.PropertyField(momentumTransferMethodProperty, new GUIContent("Momentum Transfer Method")); + EditorGUILayout.PropertyField(disallowTheftProperty, new GUIContent("Disallow Theft")); + EditorGUILayout.PropertyField(exactGunProperty, new GUIContent("Exact Gun")); + EditorGUILayout.PropertyField(exactGripProperty, new GUIContent("Exact Grip")); + EditorGUILayout.PropertyField(allowManipulationWhenEquippedProperty, new GUIContent("Allow Manipulation When Equipped")); + EditorGUILayout.PropertyField(orientationProperty, new GUIContent("Orientation")); + EditorGUILayout.PropertyField(autoHoldProperty, new GUIContent("AutoHold", "If the pickup is supposed to be aligned to the hand (i.e. orientation field is set to Gun or Grip), auto-detect means that it will be Equipped(not dropped when they release trigger), otherwise just hold as a normal pickup.")); + EditorGUILayout.PropertyField(interactionTextProperty, new GUIContent("Interaction Text","Text displayed when user hovers over the pickup.")); + if (autoHoldProperty.enumValueIndex != (int)VRC.SDK3.Components.VRCPickup.AutoHoldMode.No) + EditorGUILayout.PropertyField(useTextProperty, new GUIContent("Use Text", "Text to display describing action for clicking button, when this pickup is already being held.")); + EditorGUILayout.PropertyField(throwVelocityBoostMinSpeedProperty, new GUIContent("Throw Velocity Boost Min Speed")); + EditorGUILayout.PropertyField(throwVelocityBoostScaleProperty, new GUIContent("Throw Velocity Boost Scale")); + EditorGUILayout.PropertyField(pickupableProperty, new GUIContent("Pickupable")); + EditorGUILayout.PropertyField(proximityProperty, new GUIContent("Proximity")); + + EditorGUILayout.EndVertical(); + + serializedObject.ApplyModifiedProperties(); + } + + } +} +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_PickupEditor3.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_PickupEditor3.cs.meta new file mode 100644 index 00000000..d0eb02dc --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_PickupEditor3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8cc4c1876b26174fbaeb062178a6bda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_SpatialAudioSourceEditor3.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_SpatialAudioSourceEditor3.cs new file mode 100644 index 00000000..0a5eec56 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_SpatialAudioSourceEditor3.cs @@ -0,0 +1,58 @@ +#if VRC_SDK_VRCSDK3 +#if UNITY_EDITOR + +using UnityEngine; +using UnityEditor; + +namespace VRC.SDK3.Editor +{ + [CustomEditor(typeof(VRC.SDK3.Components.VRCSpatialAudioSource))] + public class VRC_SpatialAudioSourceEditor3 : UnityEditor.Editor + { + private bool showAdvancedOptions = false; + private SerializedProperty gainProperty; + private SerializedProperty nearProperty; + private SerializedProperty farProperty; + private SerializedProperty volRadiusProperty; + private SerializedProperty enableSpatialProperty; + private SerializedProperty useCurveProperty; + + public override void OnInspectorGUI() + { + gainProperty = serializedObject.FindProperty("Gain"); + nearProperty = serializedObject.FindProperty("Near"); + farProperty = serializedObject.FindProperty("Far"); + volRadiusProperty = serializedObject.FindProperty("VolumetricRadius"); + enableSpatialProperty = serializedObject.FindProperty("EnableSpatialization"); + useCurveProperty = serializedObject.FindProperty("UseAudioSourceVolumeCurve"); + + serializedObject.Update(); + + VRC.SDKBase.VRC_SpatialAudioSource target = serializedObject.targetObject as VRC.SDKBase.VRC_SpatialAudioSource; + AudioSource source = target.GetComponent<AudioSource>(); + + EditorGUILayout.BeginVertical(); + + EditorGUILayout.PropertyField(gainProperty, new GUIContent("Gain")); + EditorGUILayout.PropertyField(farProperty, new GUIContent("Far")); + showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options"); + bool enableSp = enableSpatialProperty.boolValue; + if (showAdvancedOptions) + { + EditorGUILayout.PropertyField(nearProperty, new GUIContent("Near")); + EditorGUILayout.PropertyField(volRadiusProperty, new GUIContent("Volumetric Radius")); + EditorGUILayout.PropertyField(enableSpatialProperty, new GUIContent("Enable Spatialization")); + if (enableSp) + EditorGUILayout.PropertyField(useCurveProperty, new GUIContent("Use AudioSource Volume Curve")); + } + + EditorGUILayout.EndVertical(); + + if (source != null) + source.spatialize = enableSp; + serializedObject.ApplyModifiedProperties(); + } + } +} +#endif +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_SpatialAudioSourceEditor3.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_SpatialAudioSourceEditor3.cs.meta new file mode 100644 index 00000000..75ef7b25 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/Components3/VRC_SpatialAudioSourceEditor3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f8f999a8e1ebee4588f94a8a618d7c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCMidiWindow.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCMidiWindow.cs new file mode 100644 index 00000000..c2924084 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCMidiWindow.cs @@ -0,0 +1,99 @@ +#if UNITY_2019_3_OR_NEWER +using UnityEditor.UIElements; +using UnityEngine.UIElements; +using UnityEngine.UIElements.StyleSheets; +#else +using UnityEditor.Experimental.UIElements; +using UnityEngine.Experimental.UIElements; +using UnityEngine.Experimental.UIElements.StyleEnums; +#endif +using System.Linq; +using UnityEditor; +using UnityEngine; +using VRC.SDKBase.Midi; + +namespace VRC.SDK3.Midi +{ +#if UNITY_STANDALONE_WIN + public class VRCMidiWindow : EditorWindow + { + private VisualElement _rootView; + private TextField _deviceNameField; + + public const string DEVICE_NAME_STRING = "VRC.SDK3.Midi.Device"; +#if (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN) && !UNITY_ANDROID + [MenuItem("VRChat SDK/Utilities/Midi")] + private static void ShowWindow() + { + VRCMidiWindow foo = CreateInstance(typeof(VRCMidiWindow)) as VRCMidiWindow; + // ReSharper disable once PossibleNullReferenceException + foo.titleContent = new GUIContent("Midi"); + foo.maxSize = new Vector2(256, 80); + foo.ShowUtility(); + } + + private void OnEnable() + { +#if UNITY_2019_3_OR_NEWER + _rootView = rootVisualElement; +#else + _rootView = this.GetRootVisualContainer(); +#endif + _rootView.Add(new Label("Midi Settings") + { + style = + { + fontSize = 18, + marginTop = 10, + marginBottom = 10, + } + }); + + // Container for Device Name label and field + VisualElement deviceNameContainer = new VisualElement() {style = {flexDirection = FlexDirection.Row}}; + _rootView.Add(deviceNameContainer); + + // Label for Field + deviceNameContainer.Add(new Label("Device Name")); + + // Input Name Field + _deviceNameField = new TextField() + { + isDelayed = true, + value = EditorPrefs.GetString(DEVICE_NAME_STRING), + style = { flexGrow = 1 }, + }; +#if UNITY_2019_3_OR_NEWER + _deviceNameField.RegisterValueChangedCallback( +#else + _deviceNameField.OnValueChanged( +#endif + evt => EditorPrefs.SetString(DEVICE_NAME_STRING, evt.newValue)); + + // Get available device names + VRCPortMidiInput midi = new VRCPortMidiInput(); + var deviceNames = midi.GetDeviceNames().ToList(); + + // Add blank device name to use if specified device not found + deviceNames.Insert(0, ""); + string currentDeviceValue = EditorPrefs.GetString(DEVICE_NAME_STRING); + string defaultValue = deviceNames.Contains(currentDeviceValue) ? currentDeviceValue : ""; + + // Create and add device popup + var deviceNamePopupField = new PopupField<string>(deviceNames, defaultValue) + { + style = {flexGrow = 1}, + name = "midiDevicePopUp", + }; +#if UNITY_2019_3_OR_NEWER + deviceNamePopupField.RegisterValueChangedCallback( +#else + deviceNamePopupField.OnValueChanged( +#endif + evt => EditorPrefs.SetString(DEVICE_NAME_STRING, evt.newValue)); + deviceNameContainer.Add(deviceNamePopupField); + } +#endif + } +#endif +} diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCMidiWindow.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCMidiWindow.cs.meta new file mode 100644 index 00000000..05eaba0e --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCMidiWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 926862297eaa5434cb1640ece9bd8510 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs new file mode 100644 index 00000000..ded4df74 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs @@ -0,0 +1,395 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; +using VRC.SDK3.Editor; +using VRC.SDKBase.Editor; +using VRC.SDKBase.Editor.BuildPipeline; +using VRC.Udon; +using VRC.Udon.Common.Interfaces; +using Object = UnityEngine.Object; + +[assembly: VRCSdkControlPanelBuilder(typeof(VRCSdkControlPanelWorldBuilder3))] + +namespace VRC.SDK3.Editor +{ + public class VRCSdkControlPanelWorldBuilder3 : VRCSdkControlPanelWorldBuilder + { + #region IVRCSdkControlPanelBuilder implementation + public override bool IsValidBuilder(out string message) + { + bool result = base.IsValidBuilder(out message); + message = "A VRCSceneDescriptor or VRCAvatarDescriptor\nis required to build VRChat SDK Content"; + return result; + } + + public override void ShowBuilder() + { + List<UdonBehaviour> failedBehaviours = ShouldShowPrimitivesWarning(); + if (failedBehaviours.Count > 0) + { + _builder.OnGUIWarning(null, + "Udon Objects reference builtin Unity mesh assets, this won't work. Consider making a copy of the mesh to use instead.", + () => + { + Selection.objects = failedBehaviours.Select(s => s.gameObject).Cast<Object>().ToArray(); + }, FixPrimitivesWarning); + } + base.ShowBuilder(); + } + + public override void SelectAllComponents() + { + Debug.Log("SelectAllComponents"); + } + + protected override bool IsSDK3Scene() + { + return true; + } + + protected override void OnGUISceneCheck(VRC.SDKBase.VRC_SceneDescriptor scene) + { + base.OnGUISceneCheck(scene); + + var resyncNotEnabled = Object.FindObjectsOfType<VRC.SDK3.Video.Components.Base.BaseVRCVideoPlayer>().Where(vp => !vp.EnableAutomaticResync).ToArray(); + if (resyncNotEnabled.Length > 0) + { + _builder.OnGUIWarning(null, + "Video Players do not have automatic resync enabled; audio may become desynchronized from video during low performance.", + () => + { + Selection.objects = resyncNotEnabled.Select(s => s.gameObject).Cast<Object>().ToArray(); + }, + () => + { + foreach (var vp in resyncNotEnabled) + vp.EnableAutomaticResync = true; + }); + } + + foreach (VRC.SDK3.Components.VRCObjectSync os in Object.FindObjectsOfType<VRC.SDK3.Components.VRCObjectSync>()) + { + if (os.GetComponents<VRC.Udon.UdonBehaviour>().Any((ub) => ub.SyncIsManual)) + _builder.OnGUIError(scene, "Object Sync cannot share an object with a manually synchronized Udon Behaviour", + delegate { Selection.activeObject = os.gameObject; }, null); + if (os.GetComponent<VRC.SDK3.Components.VRCObjectPool>() != null) + _builder.OnGUIError(scene, "Object Sync cannot share an object with an object pool", + delegate { Selection.activeObject = os.gameObject; }, null); + } + } + + #endregion + + public override void OnGUIScene() + { + GUILayout.Label("", VRCSdkControlPanel.scrollViewSeparatorStyle); + + _builderScrollPos = GUILayout.BeginScrollView(_builderScrollPos, false, false, GUIStyle.none, + GUI.skin.verticalScrollbar, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth), + GUILayout.MinHeight(217)); + + GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); + GUILayout.BeginHorizontal(); + GUILayout.BeginVertical(GUILayout.Width(300)); + EditorGUILayout.Space(); + GUILayout.Label("Local Testing", VRCSdkControlPanel.infoGuiStyle); + GUILayout.Label( + "Before uploading your world you may build and test it in the VRChat client. You won't be able to invite anyone from online but you can launch multiple of your own clients.", + VRCSdkControlPanel.infoGuiStyle); + GUILayout.EndVertical(); + GUILayout.BeginVertical(GUILayout.Width(200)); + EditorGUILayout.Space(); + VRCSettings.NumClients = EditorGUILayout.IntField("Number of Clients", VRCSettings.NumClients, GUILayout.MaxWidth(190)); + EditorGUILayout.Space(); + VRCSettings.ForceNoVR = EditorGUILayout.Toggle("Force Non-VR", VRCSettings.ForceNoVR, GUILayout.MaxWidth(190)); + EditorGUILayout.Space(); + if (VRCSettings.DisplayAdvancedSettings) + { + VRCSettings.WatchWorlds = + EditorGUILayout.Toggle("Enable World Reload", VRCSettings.WatchWorlds, GUILayout.MaxWidth(190)); + EditorGUILayout.Space(); + } + + GUI.enabled = _builder.NoGuiErrorsOrIssues(); + + string lastUrl = VRC_SdkBuilder.GetLastUrl(); + + bool doReload = VRCSettings.WatchWorlds && VRCSettings.NumClients == 0; + + bool lastBuildPresent = lastUrl != null; + if (lastBuildPresent == false) + GUI.enabled = false; + if (VRCSettings.DisplayAdvancedSettings) + { + string lastBuildLabel = doReload ? "Reload Last Build" : "Last Build"; + if (GUILayout.Button(lastBuildLabel)) + { + if (doReload) + { + // Todo: get this from settings or make key a const + string path = EditorPrefs.GetString("lastVRCPath"); + if (File.Exists(path)) + { + File.SetLastWriteTimeUtc(path, DateTime.Now); + } + else + { + Debug.LogWarning($"Cannot find last built scene, please Rebuild."); + } + } + else + { + VRC_SdkBuilder.shouldBuildUnityPackage = false; + VRC_SdkBuilder.RunLastExportedSceneResource(); + } + } + + if (Core.APIUser.CurrentUser.hasSuperPowers) + { + if (GUILayout.Button("Copy Test URL")) + { + TextEditor te = new TextEditor {text = lastUrl}; + te.SelectAll(); + te.Copy(); + } + } + } + + GUI.enabled = _builder.NoGuiErrorsOrIssues() || + Core.APIUser.CurrentUser.developerType == Core.APIUser.DeveloperType.Internal; + +#if UNITY_ANDROID + EditorGUI.BeginDisabledGroup(true); +#endif + string buildLabel = doReload ? "Build & Reload" : "Build & Test"; + if (GUILayout.Button(buildLabel)) + { + bool buildTestBlocked = !VRCBuildPipelineCallbacks.OnVRCSDKBuildRequested(VRCSDKRequestedBuildType.Scene); + if (!buildTestBlocked) + { + EnvConfig.ConfigurePlayerSettings(); + VRC_SdkBuilder.shouldBuildUnityPackage = false; + AssetExporter.CleanupUnityPackageExport(); // force unity package rebuild on next publish + VRC_SdkBuilder.PreBuildBehaviourPackaging(); + if (doReload) + { + VRC_SdkBuilder.ExportSceneResource(); + } + else + { + VRC_SdkBuilder.ExportSceneResourceAndRun(); + } + } + } +#if UNITY_ANDROID + EditorGUI.EndDisabledGroup(); +#endif + + GUILayout.EndVertical(); + + if (Event.current.type != EventType.Used) + { + GUILayout.EndHorizontal(); + EditorGUILayout.Space(); + GUILayout.EndVertical(); + } + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); + + GUILayout.BeginHorizontal(); + GUILayout.BeginVertical(GUILayout.Width(300)); + EditorGUILayout.Space(); + GUILayout.Label("Online Publishing", VRCSdkControlPanel.infoGuiStyle); + GUILayout.Label( + "In order for other people to enter your world in VRChat it must be built and published to our game servers.", + VRCSdkControlPanel.infoGuiStyle); + EditorGUILayout.Space(); + GUILayout.EndVertical(); + GUILayout.BeginVertical(GUILayout.Width(200)); + EditorGUILayout.Space(); + + if (lastBuildPresent == false) + GUI.enabled = false; + if (VRCSettings.DisplayAdvancedSettings) + { + if (GUILayout.Button("Last Build")) + { + if (Core.APIUser.CurrentUser.canPublishWorlds) + { + EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", false); + VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled; + VRC_SdkBuilder.UploadLastExportedSceneBlueprint(); + } + else + { + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + } + + GUI.enabled = _builder.NoGuiErrorsOrIssues() || + Core.APIUser.CurrentUser.developerType == Core.APIUser.DeveloperType.Internal; + if (GUILayout.Button(VRCSdkControlPanel.GetBuildAndPublishButtonString())) + { + bool buildBlocked = !VRCBuildPipelineCallbacks.OnVRCSDKBuildRequested(VRCSDKRequestedBuildType.Scene); + if (!buildBlocked) + { + if (Core.APIUser.CurrentUser.canPublishWorlds) + { + EnvConfig.ConfigurePlayerSettings(); + EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", false); + + VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled; + VRC_SdkBuilder.PreBuildBehaviourPackaging(); + VRC_SdkBuilder.ExportAndUploadSceneBlueprint(); + } + else + { + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + } + + GUILayout.EndVertical(); + GUI.enabled = true; + + if (Event.current.type == EventType.Used) return; + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + GUILayout.EndScrollView(); + } + + private static Mesh[] _primitiveMeshes; + + private static List<UdonBehaviour> ShouldShowPrimitivesWarning() + { + if (_primitiveMeshes == null) + { + PrimitiveType[] primitiveTypes = (PrimitiveType[]) System.Enum.GetValues(typeof(PrimitiveType)); + _primitiveMeshes = new Mesh[primitiveTypes.Length]; + + for (int i = 0; i < primitiveTypes.Length; i++) + { + PrimitiveType primitiveType = primitiveTypes[i]; + GameObject go = GameObject.CreatePrimitive(primitiveType); + _primitiveMeshes[i] = go.GetComponent<MeshFilter>().sharedMesh; + Object.DestroyImmediate(go); + } + } + + UdonBehaviour[] allBehaviours = Object.FindObjectsOfType<UdonBehaviour>(); + List<UdonBehaviour> failedBehaviours = new List<UdonBehaviour>(allBehaviours.Length); + foreach (UdonBehaviour behaviour in allBehaviours) + { + IUdonVariableTable publicVariables = behaviour.publicVariables; + foreach (string symbol in publicVariables.VariableSymbols) + { + if (!publicVariables.TryGetVariableValue(symbol, out Mesh mesh)) + { + continue; + } + + if (mesh == null) + { + continue; + } + + bool all = true; + foreach (Mesh primitiveMesh in _primitiveMeshes) + { + if (mesh != primitiveMesh) + { + continue; + } + + all = false; + break; + } + + if (all) + { + continue; + } + + failedBehaviours.Add(behaviour); + } + } + + return failedBehaviours; + } + + private void FixPrimitivesWarning() + { + UdonBehaviour[] allObjects = Object.FindObjectsOfType<UdonBehaviour>(); + foreach (UdonBehaviour behaviour in allObjects) + { + IUdonVariableTable publicVariables = behaviour.publicVariables; + foreach (string symbol in publicVariables.VariableSymbols) + { + if (!publicVariables.TryGetVariableValue(symbol, out Mesh mesh)) + { + continue; + } + + if (mesh == null) + { + continue; + } + + bool all = true; + foreach (Mesh primitiveMesh in _primitiveMeshes) + { + if (mesh != primitiveMesh) + { + continue; + } + + all = false; + break; + } + + if (all) + { + continue; + } + + Mesh clone = Object.Instantiate(mesh); + + Scene scene = behaviour.gameObject.scene; + string scenePath = Path.GetDirectoryName(scene.path) ?? "Assets"; + + string folderName = $"{scene.name}_MeshClones"; + string folderPath = Path.Combine(scenePath, folderName); + + if (!AssetDatabase.IsValidFolder(folderPath)) + { + AssetDatabase.CreateFolder(scenePath, folderName); + } + + string assetPath = Path.Combine(folderPath, $"{clone.name}.asset"); + + Mesh existingClone = AssetDatabase.LoadAssetAtPath<Mesh>(assetPath); + if (existingClone == null) + { + AssetDatabase.CreateAsset(clone, assetPath); + AssetDatabase.Refresh(); + } + else + { + clone = existingClone; + } + + publicVariables.TrySetVariableValue(symbol, clone); + EditorSceneManager.MarkSceneDirty(behaviour.gameObject.scene); + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs.meta new file mode 100644 index 00000000..70e096f1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e91af89bd4738014f93521e8333fcfbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime.meta new file mode 100644 index 00000000..387b1ffc --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18b4b5a6edd6e3945a2584b0177122c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi.meta new file mode 100644 index 00000000..96e1993a --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 41b228f88e2afce4fa7152bf11eda080 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi.meta new file mode 100644 index 00000000..0b38e67b --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c4ba92a5dc8e6c14bb9b8dfce6851a99 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Event.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Event.cs new file mode 100644 index 00000000..110564e7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Event.cs @@ -0,0 +1,17 @@ +namespace PortMidi +{ + public class Event + { + public Event(PmEvent pmEvent) + { + this.Status = PortMidiMarshal.Pm_MessageStatus(pmEvent.message); + this.Data1 = PortMidiMarshal.Pm_MessageData1(pmEvent.message); + this.Data2 = PortMidiMarshal.Pm_MessageData2(pmEvent.message); + } + + public long Timestamp { get; set; } + public long Status { get; set; } + public long Data1 { get; set; } + public long Data2 { get; set; } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Event.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Event.cs.meta new file mode 100644 index 00000000..d02de3b3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Event.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61b00e22036b5cc49a565aaec91ca8cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceInfo.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceInfo.cs new file mode 100644 index 00000000..001816fe --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceInfo.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortMidi +{ + public struct MidiDeviceInfo + { + PmDeviceInfo info; + + internal MidiDeviceInfo(int id, IntPtr ptr) + { + ID = id; + this.info = (PmDeviceInfo) Marshal.PtrToStructure(ptr, typeof(PmDeviceInfo)); + } + + public int ID { get; set; } + + public string Interface => Marshal.PtrToStringAnsi(info.Interface); + + public string Name => Marshal.PtrToStringAnsi(info.Name); + + public bool IsInput => info.Input != 0; + + public bool IsOutput => info.Output != 0; + + public bool IsOpened => info.Opened != 0; + + public override string ToString() + { + return + $"{Interface} - {Name} ({(IsInput ? (IsOutput ? "I/O" : "Input") : (IsOutput ? "Output" : "N/A"))} {(IsOpened ? "open" : String.Empty)})"; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceInfo.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceInfo.cs.meta new file mode 100644 index 00000000..60628362 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbb88a986ea685d41a9eac3aa3dd8c60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceManager.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceManager.cs new file mode 100644 index 00000000..4628afc1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using PmDeviceID = System.Int32; +using PortMidiStream = System.IntPtr; +using PmError = PortMidi.MidiErrorType; + +namespace PortMidi +{ + public static class MidiDeviceManager + { + private const int DefaultBufferSize = 1024; + + static MidiDeviceManager() + { + PortMidiMarshal.Pm_Initialize(); + AppDomain.CurrentDomain.DomainUnload += delegate { PortMidiMarshal.Pm_Terminate(); }; + } + + public static int DeviceCount => PortMidiMarshal.Pm_CountDevices(); + + public static int DefaultInputDeviceId => PortMidiMarshal.Pm_GetDefaultInputDeviceID(); + + public static int DefaultOutputDeviceId => PortMidiMarshal.Pm_GetDefaultOutputDeviceID(); + + public static IEnumerable<MidiDeviceInfo> AllDevices + { + get + { + for (var i = 0; i < DeviceCount; i++) + { + yield return GetDeviceInfo(i); + } + } + } + + private static MidiDeviceInfo GetDeviceInfo(PmDeviceID id) + { + return new MidiDeviceInfo(id, PortMidiMarshal.Pm_GetDeviceInfo(id)); + } + + public static MidiInput OpenInput(PmDeviceID inputDevice) + { + return OpenInput(inputDevice, DefaultBufferSize); + } + + private static MidiInput OpenInput(PmDeviceID inputDevice, int bufferSize) + { + PortMidiStream stream = default; + var e = PortMidiMarshal.Pm_OpenInput(out stream, inputDevice, IntPtr.Zero, bufferSize, null, IntPtr.Zero); + if (e != PmError.NoError) + { + throw new MidiException(e, $"Failed to open MIDI input device {e}"); + } + + return new MidiInput(stream, inputDevice); + } + + public static MidiOutput OpenOutput(PmDeviceID outputDevice) + { + PortMidiStream stream; + var e = PortMidiMarshal.Pm_OpenOutput(out stream, outputDevice, IntPtr.Zero, 0, null, IntPtr.Zero, 0); + if (e != PmError.NoError) + { + throw new MidiException(e, $"Failed to open MIDI output device {e}"); + } + return new MidiOutput(stream, outputDevice, 0); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceManager.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceManager.cs.meta new file mode 100644 index 00000000..3874e4b5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiDeviceManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02b0549f0815d8a4982133c0a99880cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiErrorType.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiErrorType.cs new file mode 100644 index 00000000..e03dfdcf --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiErrorType.cs @@ -0,0 +1,18 @@ +namespace PortMidi +{ + public enum MidiErrorType + { + NoError = 0, + NoData = 0, + GotData = 1, + HostError = -10000, + InvalidDeviceId, + InsufficientMemory, + BufferTooSmall, + BufferOverflow, + BadPointer, + BadData, + InternalError, + BufferMaxSize, + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiErrorType.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiErrorType.cs.meta new file mode 100644 index 00000000..15768ae2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiErrorType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b1e1637c177e0c43af16fefddc0826c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiEvent.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiEvent.cs new file mode 100644 index 00000000..b304cefd --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiEvent.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortMidi +{ + [StructLayout(LayoutKind.Sequential)] + public struct MidiEvent + { + MidiMessage msg; + Int32 ts; + [NonSerialized] byte[] sysex; + + public MidiMessage Message + { + get => msg; + set => msg = value; + } + + public Int32 Timestamp + { + get => ts; + set => ts = value; + } + + public byte[] SysEx + { + get => sysex; + set => sysex = value; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiEvent.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiEvent.cs.meta new file mode 100644 index 00000000..cb252310 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8f59aea3b1948f4ebb0d7835b69bbb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiException.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiException.cs new file mode 100644 index 00000000..551297b2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiException.cs @@ -0,0 +1,22 @@ +using System; + +namespace PortMidi +{ + public class MidiException : Exception + { + MidiErrorType error_type; + + public MidiException(MidiErrorType errorType, string message) + : this(errorType, message, null) + { + } + + public MidiException(MidiErrorType errorType, string message, Exception innerException) + : base(message, innerException) + { + error_type = errorType; + } + + public MidiErrorType ErrorType => error_type; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiException.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiException.cs.meta new file mode 100644 index 00000000..82da420f --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a9aa39367a5a6014e8ae35c4c0fc0fd0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiFilter.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiFilter.cs new file mode 100644 index 00000000..d270806a --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiFilter.cs @@ -0,0 +1,30 @@ +using System; + +namespace PortMidi +{ + [Flags] + public enum MidiFilter : int + { + Active = 1 << 0x0E, + SysEx = 1, + Clock = 1 << 0x08, + Play = ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)), + Tick = (1 << 0x09), + FD = (1 << 0x0D), + Undefined = FD, + Reset = (1 << 0x0F), + RealTime = (Active | SysEx | Clock | Play | Undefined | Reset | Tick), + Note = ((1 << 0x19) | (1 << 0x18)), + CAF = (1 << 0x1D), + PAF = (1 << 0x1A), + AF = (CAF | PAF), + Program = (1 << 0x1C), + Control = (1 << 0x1B), + PitchBend = (1 << 0x1E), + MTC = (1 << 0x01), + SongPosition = (1 << 0x02), + SongSelect = (1 << 0x03), + Tune = (1 << 0x06), + SystemCommon = (MTC | SongPosition | SongSelect | Tune) + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiFilter.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiFilter.cs.meta new file mode 100644 index 00000000..f553d7f5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiFilter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9ddf13cca3c55164692f303b16a5fb2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiInput.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiInput.cs new file mode 100644 index 00000000..dbb35623 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiInput.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace PortMidi +{ + public class MidiInput : MidiStream + { + public MidiInput(IntPtr stream, Int32 inputDevice) + : base(stream, inputDevice) + { + } + + public bool HasData => PortMidiMarshal.Pm_Poll(stream) == MidiErrorType.GotData; + + public int Read(byte[] buffer, int index, int length) + { + var gch = GCHandle.Alloc(buffer); + try + { + var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, index); + int size = PortMidiMarshal.Pm_Read(stream, ptr, length); + if (size < 0) + { + throw new MidiException((MidiErrorType) size, + PortMidiMarshal.Pm_GetErrorText((MidiErrorType) size)); + } + return size * 4; + } + finally + { + gch.Free(); + } + } + + public Event ReadEvent(byte[] buffer, int index, int length) + { + var gch = GCHandle.Alloc(buffer); + try + { + var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, index); + int size = PortMidiMarshal.Pm_Read(stream, ptr, length); + if (size < 0) + { + throw new MidiException((MidiErrorType) size, + PortMidiMarshal.Pm_GetErrorText((MidiErrorType) size)); + } + + return new Event(Marshal.PtrToStructure<PmEvent>(ptr)); + } + finally + { + gch.Free(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiInput.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiInput.cs.meta new file mode 100644 index 00000000..c46ce4b1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiInput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 324be2ea40d796c49b74c60b8c2e5c97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiMessage.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiMessage.cs new file mode 100644 index 00000000..a7b7c9c4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiMessage.cs @@ -0,0 +1,20 @@ +using System; + +namespace PortMidi +{ + public struct MidiMessage + { + private int v; + public MidiMessage(int value) + { + v = value; + } + + public MidiMessage(int status, int data1, int data2) + { + v = ((data2 << 16) & 0xFF0000) | ((data1 << 8) & 0xFF00) | (status & 0xFF); + } + + public Int32 Value => v; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiMessage.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiMessage.cs.meta new file mode 100644 index 00000000..02f67375 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c18643a5c49be3649ade32f7c9b19e99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiOutput.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiOutput.cs new file mode 100644 index 00000000..fe56081c --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiOutput.cs @@ -0,0 +1,67 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortMidi +{ + public class MidiOutput : MidiStream + { + public MidiOutput(IntPtr stream, Int32 outputDevice, int latency) + : base(stream, outputDevice) + { + } + + public void Write(MidiEvent midiEvent) + { + if (midiEvent.SysEx != null) + { + WriteSysEx(midiEvent.Timestamp, midiEvent.SysEx); + } + else + { + Write(midiEvent.Timestamp, midiEvent.Message); + } + } + + private void Write(Int32 when, MidiMessage msg) + { + var ret = PortMidiMarshal.Pm_WriteShort(stream, when, msg); + if (ret != MidiErrorType.NoError) + { + throw new MidiException(ret, + $"Failed to write message {msg.Value} : {PortMidiMarshal.Pm_GetErrorText((MidiErrorType) ret)}"); + } + } + + private void WriteSysEx(Int32 when, byte[] sysEx) + { + var ret = PortMidiMarshal.Pm_WriteSysEx(stream, when, sysEx); + if (ret != MidiErrorType.NoError) + throw new MidiException(ret, + $"Failed to write sysEx message : {PortMidiMarshal.Pm_GetErrorText((MidiErrorType) ret)}"); + } + + public void Write(MidiEvent[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + private void Write(MidiEvent[] buffer, int index, int length) + { + var gch = GCHandle.Alloc(buffer); + try + { + var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, index); + var ret = PortMidiMarshal.Pm_Write(stream, ptr, length); + if (ret != MidiErrorType.NoError) + { + throw new MidiException(ret, + $"Failed to write messages : {PortMidiMarshal.Pm_GetErrorText((MidiErrorType) ret)}"); + } + } + finally + { + gch.Free(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiOutput.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiOutput.cs.meta new file mode 100644 index 00000000..cc6a9a22 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiOutput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a806da9b51653af47b83c3c10a6eed65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiStream.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiStream.cs new file mode 100644 index 00000000..e4d80447 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiStream.cs @@ -0,0 +1,41 @@ +using System; + +namespace PortMidi +{ + public abstract class MidiStream : IDisposable + { + internal IntPtr stream; + internal Int32 device; + + protected MidiStream(IntPtr stream, Int32 deviceID) + { + this.stream = stream; + this.device = deviceID; + } + + public void Abort() + { + PortMidiMarshal.Pm_Abort(stream); + } + + public void Close() + { + Dispose(); + } + + public void Dispose() + { + PortMidiMarshal.Pm_Close(stream); + } + + public void SetFilter(MidiFilter filters) + { + PortMidiMarshal.Pm_SetFilter(stream, filters); + } + + public void SetChannelMask(int mask) + { + PortMidiMarshal.Pm_SetChannelMask(stream, mask); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiStream.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiStream.cs.meta new file mode 100644 index 00000000..a16d7e5c --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiStream.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f858f0a52902aaf4298247159434e1ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiTimeProcDelegate.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiTimeProcDelegate.cs new file mode 100644 index 00000000..c0a21dc2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiTimeProcDelegate.cs @@ -0,0 +1,7 @@ +using System; +using PmTimestamp = System.Int32; + +namespace PortMidi +{ + public delegate PmTimestamp MidiTimeProcDelegate(IntPtr timeInfo); +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiTimeProcDelegate.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiTimeProcDelegate.cs.meta new file mode 100644 index 00000000..79fb10f5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/MidiTimeProcDelegate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 63050b27ae9f6ab4cb3837f915f15067 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins.meta new file mode 100644 index 00000000..9828731f --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 743cfca463c6cc94fabfda636f4eb439 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows.meta new file mode 100644 index 00000000..fe5fb2cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc4d155a8e6f6f144871b6ee03542b7c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows/portmidi.dll b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows/portmidi.dll Binary files differnew file mode 100644 index 00000000..ba5be4e5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows/portmidi.dll diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows/portmidi.dll.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows/portmidi.dll.meta new file mode 100644 index 00000000..36f81d69 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/Plugins/Windows/portmidi.dll.meta @@ -0,0 +1,96 @@ +fileFormatVersion: 2 +guid: 372656c38e5c12f48b819e12fa08ac7b +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + '': Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux: 0 + Exclude Linux64: 0 + Exclude LinuxUniversal: 0 + Exclude OSXUniversal: 0 + Exclude WebGL: 1 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: {} + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmDeviceInfo.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmDeviceInfo.cs new file mode 100644 index 00000000..e2ecccdd --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmDeviceInfo.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortMidi +{ + [StructLayout(LayoutKind.Sequential)] + internal struct PmDeviceInfo + { + public int StructVersion; + public IntPtr Interface; + public IntPtr Name; + public int Input; + public int Output; + public int Opened; + + public override string ToString() + { + return $"{StructVersion}, {Interface}, {Name}, {Input}, {Output}, {Opened}"; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmDeviceInfo.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmDeviceInfo.cs.meta new file mode 100644 index 00000000..048fa702 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmDeviceInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e20f952b815e0fd4c89e244be795b605 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmEvent.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmEvent.cs new file mode 100644 index 00000000..b5d68bce --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmEvent.cs @@ -0,0 +1,11 @@ +using PmMessage = System.Int32; +using PmTimestamp = System.Int32; + +namespace PortMidi +{ + public struct PmEvent + { + public PmMessage message; + public PmTimestamp timestamp; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmEvent.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmEvent.cs.meta new file mode 100644 index 00000000..d8ec66ee --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PmEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36b7824d7ad3b3f4fbb5ed6d2a5766ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PortMidiMarshal.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PortMidiMarshal.cs new file mode 100644 index 00000000..58f02e11 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PortMidiMarshal.cs @@ -0,0 +1,105 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortMidi +{ + internal class PortMidiMarshal + { + private const int Hdrlength = 50; + private const uint PmHostErrorMsgLen = 256; + const int PmNoDevice = -1; + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_Initialize(); + + [DllImport("portmidi")] + public static extern IntPtr Pm_GetDeviceInfo(Int32 id); + + [DllImport("portmidi")] + public static extern int Pm_CountDevices(); + + [DllImport("portmidi")] + public static extern Int32 Pm_GetDefaultInputDeviceID(); + + [DllImport("portmidi")] + public static extern Int32 Pm_GetDefaultOutputDeviceID(); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_OpenInput( + out IntPtr stream, + Int32 inputDevice, + IntPtr inputDriverInfo, + int bufferSize, + MidiTimeProcDelegate timeProc, + IntPtr timeInfo); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_OpenOutput( + out IntPtr stream, + Int32 outputDevice, + IntPtr outputDriverInfo, + int bufferSize, + MidiTimeProcDelegate time_proc, + IntPtr time_info, + int latency); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_SetFilter(IntPtr stream, MidiFilter filters); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_SetChannelMask(IntPtr stream, int mask); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_Poll(IntPtr stream); + + [DllImport("portmidi")] + public static extern int Pm_Read(IntPtr stream, IntPtr buffer, int length); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_Write(IntPtr stream, IntPtr buffer, int length); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_WriteSysEx(IntPtr stream, Int32 when, byte[] msg); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_WriteShort(IntPtr stream, Int32 when, MidiMessage msg); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_Abort(IntPtr stream); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_Close(IntPtr stream); + + [DllImport("portmidi")] + public static extern MidiErrorType Pm_Terminate(); + + [DllImport("portmidi")] + public static extern int Pm_HasHostError(IntPtr stream); + + [DllImport("portmidi")] + public static extern string Pm_GetErrorText(MidiErrorType errnum); + + [DllImport("portmidi")] + public static extern void Pm_GetHostErrorText(IntPtr msg, uint len); + + public static int Pm_Channel(int channel) + { + return 1 << channel; + } + + public static int Pm_MessageStatus(int msg) + { + return msg & 0xFF; + } + + public static int Pm_MessageData1(int msg) + { + return (msg >> 8) & 0xFF; + } + + public static int Pm_MessageData2(int msg) + { + return (msg >> 16) & 0xFF; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PortMidiMarshal.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PortMidiMarshal.cs.meta new file mode 100644 index 00000000..09c36972 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/PortMidi/PortMidiMarshal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d4b9d0d9dfad1c4894df5c2a5530696 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs new file mode 100644 index 00000000..e4a6200b --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs @@ -0,0 +1,116 @@ +#if (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN) && !UNITY_ANDROID +using System; +using System.Collections.Generic; +using System.Linq; +#if VRC_CLIENT +using VRC.Core; +#endif +using PortMidi; +using UnityEngine; +using VRC.SDK3.Midi; + + +namespace VRC.SDKBase.Midi +{ + public class VRCPortMidiInput: IVRCMidiInput + { + + private MidiInput _input; + private byte[] _data; + private MidiDeviceInfo _info; + + public bool OpenDevice(string name = null) + { + try + { + if (!string.IsNullOrWhiteSpace(name)) + { + _info = MidiDeviceManager.AllDevices.FirstOrDefault(device => + device.IsInput && device.Name.ToLower().Contains(name.ToLower())); + } + else + { + _info = MidiDeviceManager.AllDevices.FirstOrDefault(device => + device.ID == MidiDeviceManager.DefaultInputDeviceId); + } + + _input = MidiDeviceManager.OpenInput(_info.ID); + _input.SetFilter(MidiFilter.Active | MidiFilter.SysEx | MidiFilter.Clock | MidiFilter.Play | MidiFilter.Tick | MidiFilter.Undefined | MidiFilter.Reset | MidiFilter.RealTime | MidiFilter.AF | MidiFilter.Program | MidiFilter.PitchBend | MidiFilter.SystemCommon); + _data = new byte[1024]; + return true; + } + catch (Exception e) + { + #if VRC_CLIENT + VRC.Core.Logger.LogError($"Error opening Default Device: {e.Message}"); + #else + Debug.Log($"Error opening Default Device: {e.Message}"); + #endif + return false; + } + } + + public void Close() + { + if (_input != null) + { + _input.Close(); + } + } + + public void Update() + { + if (_input == null) return; + + if (_input.HasData) + { + // Portmidi reports 4 bytes per event but the buffer has 8 bytes so we multiply count by 2 + int count = (_input.Read(_data, 0, _data.Length)) * 2; + for (int i = 0; i < count; i+=8) + { + ConvertAndSend(_data[i], _data[i + 1], _data[i + 2]); + } + } + } + + public IEnumerable<string> GetDeviceNames() + { + return MidiDeviceManager.AllDevices.Select(d => d.Name); + } + + private void ConvertAndSend(byte status, byte data1, byte data2) + { + var command = status & 0xF0; // mask off all but top 4 bits + + if (command >= 0x80 && command <= 0xE0) { + // it's a voice message + // find the channel by masking off all but the low 4 bits + var channel = status & 0x0F; + + if (command == VRCMidiHandler.STATUS_NOTE_ON || command == VRCMidiHandler.STATUS_NOTE_OFF || command == VRCMidiHandler.STATUS_CONTROL_CHANGE) + { + OnMidiVoiceMessage?.Invoke(this, new MidiVoiceEventArgs(command, channel, data1, data2)); + } + else + { +#if VRC_CLIENT + VRC.Core.Logger.Log($"command:{command} channel:{channel} data1:{data1} data2:{data2}"); +#else + Debug.Log($"command:{command} channel:{channel} data1:{data1} data2:{data2}"); +#endif + } + + } + else + { + // it's a system message, ignore for now + OnMidiRawMessage?.Invoke(this, new MidiRawEventArgs(status, data1, data2)); + } + } + + public string Name => _info.Name; + public event MidiVoiceMessageDelegate OnMidiVoiceMessage; + public event MidiRawMessageDelegate OnMidiRawMessage; + } +} +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs.meta new file mode 100644 index 00000000..156b4665 --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fb33369bfa554472b08fc6828450a2c4 +timeCreated: 1607454401
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/UnityEventFilter.cs b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/UnityEventFilter.cs new file mode 100644 index 00000000..79228fad --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/UnityEventFilter.cs @@ -0,0 +1,1069 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityEngine.Video; +#if VRC_SDK_VRCSDK3 +using VRC.SDK3.Components; +using TMPro; +#endif +#if UDON +using VRC.Udon; + +#endif +#if !VRC_CLIENT && UNITY_EDITOR && VRC_SDK_VRCSDK3 +using UnityEditor; +using UnityEngine.SceneManagement; +#endif + +namespace VRC.Core +{ + public static class UnityEventFilter + { + // These types are will always be prohibited even if they are derived from an allowed type. + private static readonly HashSet<Type> _prohibitedUIEventTargetTypes = new HashSet<Type> + { + #if VRC_CLIENT + typeof(RenderHeads.Media.AVProVideo.MediaPlayer), + #endif + #if VRC_SDK_VRCSDK3 + typeof(VRCUrlInputField), + #endif + typeof(VideoPlayer) + }; + + private static readonly Lazy<Dictionary<Type, AllowedMethodFilter>> _allowedUnityEventTargetTypes = + new Lazy<Dictionary<Type, AllowedMethodFilter>>(GetRuntimeUnityEventTargetAccessFilterDictionary); + + private static Dictionary<Type, AllowedMethodFilter> AllowedUnityEventTargetTypes => _allowedUnityEventTargetTypes.Value; + + private static readonly Lazy<int> _debugLevel = new Lazy<int>(InitializeLogging); + private static int DebugLevel => _debugLevel.Value; + + // Builds a HashSet of allowed types, and their derived types, and removes explicitly prohibited types. + private static Dictionary<Type, AllowedMethodFilter> GetRuntimeUnityEventTargetAccessFilterDictionary() + { + Dictionary<Type, AllowedMethodFilter> accessFilterDictionary = new Dictionary<Type, AllowedMethodFilter>(_initialTargetAccessFilters); + AddDerivedTypes(accessFilterDictionary); + RemoveProhibitedTypes(accessFilterDictionary); + + #if VERBOSE_EVENT_SANITIZATION_LOGGING + StringBuilder stringBuilder = new StringBuilder(); + foreach(KeyValuePair<Type, AllowedMethodFilter> entry in accessFilterDictionary) + { + stringBuilder.AppendLine(entry.Key.FullName); + AllowedMethodFilter targetMethodAccessFilter = entry.Value; + foreach(string targetMethod in targetMethodAccessFilter.GetTargetMethodNames()) + { + stringBuilder.AppendLine($" {targetMethod}"); + } + + stringBuilder.AppendLine(); + } + + VerboseLog(stringBuilder.ToString()); + #endif + + return accessFilterDictionary; + } + + #if !VRC_CLIENT && UNITY_EDITOR && VRC_SDK_VRCSDK3 + [RuntimeInitializeOnLoadMethod] + private static void SetupPlayMode() + { + EditorApplication.playModeStateChanged += RunFilteringOnPlayModeEntry; + } + + private static void RunFilteringOnPlayModeEntry(PlayModeStateChange playModeStateChange) + { + switch(playModeStateChange) + { + case PlayModeStateChange.EnteredPlayMode: + { + for(int sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++) + { + Scene currentScene = SceneManager.GetSceneAt(sceneIndex); + List<GameObject> rootGameObjects = new List<GameObject>(); + currentScene.GetRootGameObjects(rootGameObjects); + + FilterEvents(rootGameObjects); + } + + break; + } + case PlayModeStateChange.EnteredEditMode: + case PlayModeStateChange.ExitingEditMode: + case PlayModeStateChange.ExitingPlayMode: + { + return; + } + default: + { + throw new ArgumentOutOfRangeException(nameof(playModeStateChange), playModeStateChange, null); + } + } + } + #endif + + private static int InitializeLogging() + { + int hashCode = typeof(UnityEventFilter).GetHashCode(); + Logger.DescribeDebugLevel(hashCode, "UnityEventFilter", Logger.Color.red); + Logger.AddDebugLevel(hashCode); + return hashCode; + } + + [PublicAPI] + public static void FilterEvents(GameObject gameObject) + { + FilterUIEvents(gameObject); + FilterEventTriggerEvents(gameObject); + FilterAnimatorEvents(gameObject); + } + + [PublicAPI] + public static void FilterEvents(List<GameObject> gameObjects) + { + FilterUIEvents(gameObjects); + FilterEventTriggerEvents(gameObjects); + FilterAnimatorEvents(gameObjects); + } + + [PublicAPI] + public static void FilterUIEvents(GameObject gameObject) + { + List<UIBehaviour> uiBehaviours = new List<UIBehaviour>(); + gameObject.GetComponentsInChildren(true, uiBehaviours); + + FilterUIBehaviourEvents(uiBehaviours); + } + + [PublicAPI] + public static void FilterUIEvents(List<GameObject> gameObjects) + { + HashSet<UIBehaviour> uiBehaviours = new HashSet<UIBehaviour>(); + List<UIBehaviour> uiBehavioursWorkingList = new List<UIBehaviour>(); + foreach(GameObject gameObject in gameObjects) + { + gameObject.GetComponentsInChildren(true, uiBehavioursWorkingList); + uiBehaviours.UnionWith(uiBehavioursWorkingList); + } + + FilterUIBehaviourEvents(uiBehaviours); + } + + [PublicAPI] + public static void FilterEventTriggerEvents(GameObject gameObject) + { + List<EventTrigger> eventTriggers = new List<EventTrigger>(); + gameObject.GetComponentsInChildren(true, eventTriggers); + + FilterEventTriggerEvents(eventTriggers); + } + + [PublicAPI] + public static void FilterEventTriggerEvents(List<GameObject> gameObjects) + { + HashSet<EventTrigger> eventTriggers = new HashSet<EventTrigger>(); + List<EventTrigger> eventTriggerWorkingList = new List<EventTrigger>(); + foreach(GameObject gameObject in gameObjects) + { + gameObject.GetComponentsInChildren(true, eventTriggerWorkingList); + eventTriggers.UnionWith(eventTriggerWorkingList); + } + + FilterEventTriggerEvents(eventTriggers); + } + + [PublicAPI] + public static void FilterAnimatorEvents(GameObject gameObject) + { + List<Animator> animators = new List<Animator>(); + gameObject.GetComponentsInChildren(true, animators); + + FilterAnimatorEvents(animators); + } + + [PublicAPI] + public static void FilterAnimatorEvents(List<GameObject> gameObjects) + { + HashSet<Animator> animators = new HashSet<Animator>(); + List<Animator> animatorsWorkingList = new List<Animator>(); + foreach(GameObject gameObject in gameObjects) + { + gameObject.GetComponentsInChildren(true, animatorsWorkingList); + animators.UnionWith(animatorsWorkingList); + } + + FilterAnimatorEvents(animators); + } + + private static void FilterUIBehaviourEvents(IEnumerable<UIBehaviour> uiBehaviours) + { + Dictionary<Type, List<UIBehaviour>> uiBehavioursByType = new Dictionary<Type, List<UIBehaviour>>(); + foreach(UIBehaviour uiBehaviour in uiBehaviours) + { + if(uiBehaviour == null) + { + continue; + } + + Type uiBehaviourType = uiBehaviour.GetType(); + if(!uiBehavioursByType.TryGetValue(uiBehaviourType, out List<UIBehaviour> uiBehavioursOfType)) + { + uiBehavioursByType.Add(uiBehaviourType, new List<UIBehaviour> {uiBehaviour}); + continue; + } + + uiBehavioursOfType.Add(uiBehaviour); + } + + foreach(KeyValuePair<Type, List<UIBehaviour>> uiBehavioursOfTypeKvp in uiBehavioursByType) + { + Type uiBehaviourType = uiBehavioursOfTypeKvp.Key; + FieldInfo[] fieldInfos = uiBehaviourType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + List<FieldInfo> unityEventFieldInfos = new List<FieldInfo>(); + foreach(FieldInfo fieldInfo in fieldInfos) + { + if(typeof(UnityEventBase).IsAssignableFrom(fieldInfo.FieldType)) + { + unityEventFieldInfos.Add(fieldInfo); + } + } + + if(unityEventFieldInfos.Count <= 0) + { + continue; + } + + FieldInfo persistentCallsGroupFieldInfo = typeof(UnityEventBase).GetField("m_PersistentCalls", BindingFlags.Instance | BindingFlags.NonPublic); + if(persistentCallsGroupFieldInfo == null) + { + VerboseLog($"Could not find 'm_PersistentCalls' on UnityEventBase."); + return; + } + + foreach(UIBehaviour uiBehaviour in uiBehavioursOfTypeKvp.Value) + { + VerboseLog($"Checking '{uiBehaviour.name} for UI Events.", uiBehaviour); + foreach(FieldInfo unityEventFieldInfo in unityEventFieldInfos) + { + VerboseLog($"Checking field '{unityEventFieldInfo.Name}' on '{uiBehaviour.name}.", uiBehaviour); + UnityEventBase unityEventBase = unityEventFieldInfo.GetValue(uiBehaviour) as UnityEventBase; + if(unityEventBase == null) + { + VerboseLog($"Null '{unityEventFieldInfo.Name}' UnityEvent on {uiBehaviour.name}.", uiBehaviour); + continue; + } + + int numEventListeners = unityEventBase.GetPersistentEventCount(); + VerboseLog($"There are '{numEventListeners}' on event '{unityEventFieldInfo.Name}' on '{uiBehaviour.name}.", uiBehaviour); + for(int index = 0; index < numEventListeners; index++) + { + string persistentMethodName = unityEventBase.GetPersistentMethodName(index); + + UnityEngine.Object persistentTarget = unityEventBase.GetPersistentTarget(index); + if(persistentTarget == null) + { + VerboseLog($"The target for listener '{index}' on event '{unityEventFieldInfo.Name}' on '{uiBehaviour.name} is null.", uiBehaviour); + continue; + } + + if(IsTargetPermitted(persistentTarget, persistentMethodName)) + { + VerboseLog( + $"Allowing event '{unityEventFieldInfo.Name}' on '{uiBehaviour.name}' to call '{persistentMethodName}' on target '{persistentTarget.name}'.", + uiBehaviour); + + continue; + } + + LogRemoval( + $"Events on '{uiBehaviour.name}' were removed because one of them targeted a prohibited type '{persistentTarget.GetType().Name}', method '{persistentMethodName}' or object '{persistentTarget.name}'.", + uiBehaviour); + + unityEventFieldInfo.SetValue(uiBehaviour, Activator.CreateInstance(unityEventBase.GetType())); + break; + } + } + } + } + } + + private static void FilterEventTriggerEvents(IEnumerable<EventTrigger> eventTriggers) + { + FieldInfo persistentCallsGroupFieldInfo = typeof(UnityEventBase).GetField("m_PersistentCalls", BindingFlags.Instance | BindingFlags.NonPublic); + if(persistentCallsGroupFieldInfo == null) + { + VerboseLog($"Could not find 'm_PersistentCalls' on UnityEventBase."); + return; + } + + foreach(EventTrigger eventTrigger in eventTriggers) + { + VerboseLog($"Checking '{eventTrigger.name} for Unity Events.", eventTrigger); + + List<EventTrigger.Entry> triggers = eventTrigger.triggers; + if(triggers.Count <= 0) + { + continue; + } + + for(int i = triggers.Count - 1; i >= 0; i--) + { + EventTrigger.Entry entry = triggers[i]; + UnityEventBase unityEventBase = entry.callback; + if(unityEventBase == null) + { + VerboseLog($"Null '{entry.eventID}' UnityEvent on {eventTrigger.name}.", eventTrigger); + continue; + } + + int numEventListeners = unityEventBase.GetPersistentEventCount(); + VerboseLog($"There are '{numEventListeners}' on event '{entry.eventID}' on '{eventTrigger.name}.", eventTrigger); + for(int index = 0; index < numEventListeners; index++) + { + string persistentMethodName = unityEventBase.GetPersistentMethodName(index); + + UnityEngine.Object persistentTarget = unityEventBase.GetPersistentTarget(index); + if(persistentTarget == null) + { + VerboseLog($"The target for listener '{index}' on event '{entry.eventID}' on '{eventTrigger.name} is null.", eventTrigger); + continue; + } + + if(IsTargetPermitted(persistentTarget, persistentMethodName)) + { + VerboseLog( + $"Allowing event '{entry.eventID}' on '{eventTrigger.name}' to call '{persistentMethodName}' on target '{persistentTarget.name}'.", + eventTrigger); + + continue; + } + + LogRemoval( + $"Events on '{eventTrigger.name}' were removed because one of them targeted a prohibited type '{persistentTarget.GetType().Name}', method '{persistentMethodName}' or object '{persistentTarget.name}'.", + eventTrigger); + + triggers.RemoveAt(i); + break; + } + } + } + } + + private static void FilterAnimatorEvents(IEnumerable<Animator> animators) + { + foreach(Animator animator in animators) + { + if(animator == null) + { + continue; + } + + RuntimeAnimatorController animatorController = animator.runtimeAnimatorController; + if(animatorController == null) + { + return; + } + + foreach(AnimationClip animationClip in animatorController.animationClips) + { + if(animationClip == null) + { + continue; + } + + foreach(AnimationEvent animationEvent in animationClip.events) + { + if(animationEvent == null) + { + continue; + } + + string animationEventFunctionName = animationEvent.functionName; + if(_allowedAnimationEventFunctionNames.Contains(animationEventFunctionName)) + { + continue; + } + + animationClip.events = null; + LogRemoval( + $"Removed AnimationEvents from AnimationClip used by the Animator on '{animator.gameObject}' because the event targets '{animationEventFunctionName}' which is not allowed."); + + break; + } + } + } + } + + [Conditional("VERBOSE_EVENT_SANITIZATION_LOGGING")] + private static void VerboseLog(string message, UnityEngine.Object target = null) + { + Logger.LogWarning(message, DebugLevel, target); + } + + private static void LogRemoval(string message, UnityEngine.Object target = null) + { + Logger.LogWarning(message, DebugLevel, target); + } + + private static bool IsTargetPermitted(UnityEngine.Object target, string targetMethod) + { + // Block anything blacklisted by Udon to prevent UnityEvents from being used to bypass the blacklist. + // NOTE: This will only block events targeting objects that are blacklisted before the UnityEventSanitizer is run. + // If objects are added to the blacklist after scene loading has finished it will be necessary to re-run the UnityEventSanitizer. + #if UDON + if(UdonManager.Instance.IsBlacklisted(target)) + { + return false; + } + #endif + + Type persistentTargetType = target.GetType(); + if(!AllowedUnityEventTargetTypes.TryGetValue(persistentTargetType, out AllowedMethodFilter accessFilter)) + { + return false; + } + + return accessFilter.IsTargetMethodAllowed(targetMethod); + } + + // Adds types derived from whitelisted types. + private static void AddDerivedTypes(Dictionary<Type, AllowedMethodFilter> accessFilterDictionary) + { + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach(Assembly assembly in assemblies) + { + foreach(Type type in assembly.GetTypes()) + { + if(accessFilterDictionary.ContainsKey(type)) + { + continue; + } + + if(!typeof(Component).IsAssignableFrom(type)) + { + continue; + } + + Type currentType = type; + while(currentType != typeof(object) && currentType != null) + { + if(accessFilterDictionary.TryGetValue(currentType, out AllowedMethodFilter accessFilter)) + { + accessFilterDictionary.Add(type, accessFilter); + break; + } + + currentType = currentType.BaseType; + } + } + } + } + + // Removes prohibited types and types derived from them. + private static void RemoveProhibitedTypes(Dictionary<Type, AllowedMethodFilter> accessFilterDictionary) + { + foreach(Type prohibitedType in _prohibitedUIEventTargetTypes) + { + foreach(Type accessFilterType in accessFilterDictionary.Keys.ToArray()) + { + if(prohibitedType.IsAssignableFrom(accessFilterType)) + { + accessFilterDictionary.Remove(accessFilterType); + } + } + } + } + + private static readonly Dictionary<Type, AllowedMethodFilter> _initialTargetAccessFilters = new Dictionary<Type, AllowedMethodFilter> + { + { + typeof(GameObject), new AllowedMethodFilter( + new List<string> + { + nameof(GameObject.SetActive) + }, + new List<string>()) + }, + { + typeof(AudioSource), + new AllowedMethodFilter( + new List<string> + { + nameof(AudioSource.Pause), + nameof(AudioSource.Play), + nameof(AudioSource.PlayDelayed), + nameof(AudioSource.PlayOneShot), + nameof(AudioSource.Stop), + nameof(AudioSource.UnPause) + }, + new List<string> + { + nameof(AudioSource.bypassEffects), + nameof(AudioSource.bypassListenerEffects), + nameof(AudioSource.bypassReverbZones), + nameof(AudioSource.dopplerLevel), + nameof(AudioSource.enabled), + nameof(AudioSource.loop), + nameof(AudioSource.maxDistance), + nameof(AudioSource.rolloffMode), + nameof(AudioSource.minDistance), + nameof(AudioSource.mute), + nameof(AudioSource.pitch), + nameof(AudioSource.playOnAwake), + nameof(AudioSource.priority), + nameof(AudioSource.spatialize), + nameof(AudioSource.spread), + nameof(AudioSource.time), + nameof(AudioSource.volume) + } + ) + }, + { + typeof(AudioDistortionFilter), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(AudioDistortionFilter.distortionLevel), + nameof(AudioDistortionFilter.enabled) + }) + }, + { + typeof(AudioEchoFilter), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(AudioEchoFilter.decayRatio), + nameof(AudioEchoFilter.delay), + nameof(AudioEchoFilter.dryMix), + nameof(AudioEchoFilter.enabled), + nameof(AudioEchoFilter.wetMix) + }) + }, + { + typeof(AudioHighPassFilter), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(AudioHighPassFilter.cutoffFrequency), + nameof(AudioHighPassFilter.enabled), + nameof(AudioHighPassFilter.highpassResonanceQ) + }) + }, + { + typeof(AudioLowPassFilter), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(AudioLowPassFilter.cutoffFrequency), + nameof(AudioLowPassFilter.enabled), + nameof(AudioLowPassFilter.lowpassResonanceQ) + }) + }, + { + typeof(AudioReverbFilter), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(AudioReverbFilter.decayHFRatio), + nameof(AudioReverbFilter.decayTime), + nameof(AudioReverbFilter.density), + nameof(AudioReverbFilter.diffusion), + nameof(AudioReverbFilter.dryLevel), + nameof(AudioReverbFilter.enabled), + nameof(AudioReverbFilter.hfReference), + nameof(AudioReverbFilter.reflectionsDelay), + nameof(AudioReverbFilter.reflectionsLevel), + nameof(AudioReverbFilter.reverbDelay), + nameof(AudioReverbFilter.reverbLevel), + nameof(AudioReverbFilter.room), + nameof(AudioReverbFilter.roomHF), + nameof(AudioReverbFilter.roomLF) + }) + }, + { + typeof(AudioReverbZone), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(AudioReverbZone.decayHFRatio), + nameof(AudioReverbZone.decayTime), + nameof(AudioReverbZone.density), + nameof(AudioReverbZone.diffusion), + nameof(AudioReverbZone.enabled), + nameof(AudioReverbZone.HFReference), + nameof(AudioReverbZone.LFReference), + nameof(AudioReverbZone.maxDistance), + nameof(AudioReverbZone.minDistance), + nameof(AudioReverbZone.reflections), + nameof(AudioReverbZone.reflectionsDelay), + nameof(AudioReverbZone.room), + nameof(AudioReverbZone.roomHF), + nameof(AudioReverbZone.roomLF) + }) + }, + #if UDON + { + typeof(UdonBehaviour), new AllowedMethodFilter( + new List<string> + { + nameof(UdonBehaviour.RunProgram), + nameof(UdonBehaviour.SendCustomEvent), + nameof(UdonBehaviour.Interact), + }, + new List<string>() + { + nameof(UdonBehaviour.enabled) + }) + }, + #endif + { + typeof(MeshRenderer), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(MeshRenderer.shadowCastingMode), + nameof(MeshRenderer.enabled), + nameof(MeshRenderer.probeAnchor), + nameof(MeshRenderer.probeAnchor), + nameof(MeshRenderer.receiveShadows), + nameof(MeshRenderer.lightProbeUsage) + }) + }, + { + typeof(Collider), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Collider.enabled), + nameof(Collider.isTrigger) + }) + }, + { + typeof(SkinnedMeshRenderer), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(SkinnedMeshRenderer.allowOcclusionWhenDynamic), + nameof(SkinnedMeshRenderer.shadowCastingMode), + nameof(SkinnedMeshRenderer.enabled), + nameof(SkinnedMeshRenderer.lightProbeProxyVolumeOverride), + nameof(SkinnedMeshRenderer.motionVectorGenerationMode), + nameof(SkinnedMeshRenderer.probeAnchor), + nameof(SkinnedMeshRenderer.receiveShadows), + nameof(SkinnedMeshRenderer.rootBone), + nameof(SkinnedMeshRenderer.skinnedMotionVectors), + nameof(SkinnedMeshRenderer.updateWhenOffscreen), + nameof(SkinnedMeshRenderer.lightProbeUsage) + }) + }, + { + typeof(Light), new AllowedMethodFilter( + new List<string> + { + nameof(Light.Reset) + }, + new List<string> + { + nameof(Light.bounceIntensity), + nameof(Light.colorTemperature), + nameof(Light.cookie), + nameof(Light.enabled), + nameof(Light.intensity), + nameof(Light.range), + nameof(Light.shadowBias), + nameof(Light.shadowNearPlane), + nameof(Light.shadowNormalBias), + nameof(Light.shadowStrength), + nameof(Light.spotAngle) + }) + }, + { + typeof(ParticleSystem), new AllowedMethodFilter( + new List<string> + { + nameof(ParticleSystem.Clear), + nameof(ParticleSystem.Emit), + nameof(ParticleSystem.Pause), + nameof(ParticleSystem.Pause), + nameof(ParticleSystem.Play), + nameof(ParticleSystem.Simulate), + nameof(ParticleSystem.Stop), + nameof(ParticleSystem.Stop), + nameof(ParticleSystem.TriggerSubEmitter) + }, + new List<string> + { + nameof(ParticleSystem.time), + nameof(ParticleSystem.useAutoRandomSeed) + }) + }, + { + typeof(ParticleSystemForceField), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(ParticleSystemForceField.endRange), + nameof(ParticleSystemForceField.gravityFocus), + nameof(ParticleSystemForceField.length), + nameof(ParticleSystemForceField.multiplyDragByParticleSize), + nameof(ParticleSystemForceField.multiplyDragByParticleVelocity), + nameof(ParticleSystemForceField.startRange) + }) + }, + { + typeof(Projector), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Projector.aspectRatio), + nameof(Projector.enabled), + nameof(Projector.nearClipPlane), + nameof(Projector.farClipPlane), + nameof(Projector.fieldOfView), + nameof(Projector.orthographic), + nameof(Projector.orthographicSize) + }) + }, + { + typeof(LineRenderer), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(LineRenderer.allowOcclusionWhenDynamic), + nameof(LineRenderer.shadowCastingMode), + nameof(LineRenderer.enabled), + nameof(LineRenderer.endWidth), + nameof(LineRenderer.loop), + nameof(LineRenderer.motionVectorGenerationMode), + nameof(LineRenderer.numCapVertices), + nameof(LineRenderer.numCornerVertices), + nameof(LineRenderer.probeAnchor), + nameof(LineRenderer.receiveShadows), + nameof(LineRenderer.shadowBias), + nameof(LineRenderer.startWidth), + nameof(LineRenderer.lightProbeUsage), + nameof(LineRenderer.useWorldSpace), + nameof(LineRenderer.widthMultiplier) + }) + }, + { + typeof(TrailRenderer), new AllowedMethodFilter( + new List<string> + { + nameof(TrailRenderer.Clear) + }, + new List<string> + { + nameof(TrailRenderer.allowOcclusionWhenDynamic), + nameof(TrailRenderer.autodestruct), + nameof(TrailRenderer.shadowCastingMode), + nameof(TrailRenderer.enabled), + nameof(TrailRenderer.emitting), + nameof(TrailRenderer.endWidth), + nameof(TrailRenderer.motionVectorGenerationMode), + nameof(TrailRenderer.numCapVertices), + nameof(TrailRenderer.numCornerVertices), + nameof(TrailRenderer.probeAnchor), + nameof(TrailRenderer.receiveShadows), + nameof(TrailRenderer.shadowBias), + nameof(TrailRenderer.startWidth), + nameof(TrailRenderer.lightProbeUsage), + nameof(TrailRenderer.widthMultiplier) + }) + }, + { + typeof(Animator), new AllowedMethodFilter( + new List<string> + { + nameof(Animator.Play), + nameof(Animator.PlayInFixedTime), + nameof(Animator.Rebind), + nameof(Animator.SetBool), + nameof(Animator.SetFloat), + nameof(Animator.SetInteger), + nameof(Animator.SetTrigger), + nameof(Animator.ResetTrigger) + }, + new List<string> + { + nameof(Animator.speed), + nameof(Animator.enabled) + }) + }, + { + typeof(Text), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Text.alignByGeometry), + nameof(Text.enabled), + nameof(Text.fontSize), + nameof(Text.lineSpacing), + nameof(Text.maskable), + nameof(Text.raycastTarget), + nameof(Text.resizeTextForBestFit), + nameof(Text.resizeTextMaxSize), + nameof(Text.resizeTextMinSize), + nameof(Text.supportRichText), + nameof(Text.text) + }) + }, + { + typeof(Image), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Image.alphaHitTestMinimumThreshold), + nameof(Image.enabled), + nameof(Image.fillAmount), + nameof(Image.fillCenter), + nameof(Image.fillClockwise), + nameof(Image.fillOrigin), + nameof(Image.maskable), + nameof(Image.preserveAspect), + nameof(Image.raycastTarget), + nameof(Image.useSpriteMesh) + }) + }, + { + typeof(RawImage), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(RawImage.enabled), + nameof(RawImage.maskable), + nameof(RawImage.raycastTarget) + }) + }, + { + typeof(InputField), new AllowedMethodFilter( + new List<string> + { + "Append", + nameof(InputField.ForceLabelUpdate) + }, + new List<string> + { + nameof(InputField.caretBlinkRate), + nameof(InputField.caretPosition), + nameof(InputField.caretWidth), + nameof(InputField.characterLimit), + nameof(InputField.customCaretColor), + nameof(InputField.enabled), + nameof(InputField.interactable), + nameof(InputField.readOnly), + nameof(InputField.selectionAnchorPosition), + nameof(InputField.text), + nameof(InputField.textComponent), + nameof(InputField.selectionFocusPosition) + }) + }, + { + typeof(Dropdown), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Dropdown.captionText), + nameof(Dropdown.enabled), + nameof(Dropdown.interactable), + nameof(Dropdown.itemText), + nameof(Dropdown.targetGraphic), + nameof(Dropdown.template), + nameof(Dropdown.value) + }) + }, + { + typeof(Slider), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Slider.enabled), + nameof(Slider.fillRect), + nameof(Slider.handleRect), + nameof(Slider.interactable), + nameof(Slider.maxValue), + nameof(Slider.minValue), + nameof(Slider.normalizedValue), + nameof(Slider.targetGraphic), + nameof(Slider.value), + nameof(Slider.wholeNumbers) + }) + }, + { + typeof(Toggle), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Toggle.enabled), + nameof(Toggle.group), + nameof(Toggle.interactable), + nameof(Toggle.isOn), + nameof(Toggle.targetGraphic) + }) + }, + { + typeof(Scrollbar), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Scrollbar.enabled), + nameof(Scrollbar.handleRect), + nameof(Scrollbar.interactable), + nameof(Scrollbar.numberOfSteps), + nameof(Scrollbar.size), + nameof(Scrollbar.targetGraphic), + nameof(Scrollbar.value) + }) + }, + { + typeof(ScrollRect), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(ScrollRect.content), + nameof(ScrollRect.decelerationRate), + nameof(ScrollRect.elasticity), + nameof(ScrollRect.enabled), + nameof(ScrollRect.horizontal), + nameof(ScrollRect.horizontalNormalizedPosition), + nameof(ScrollRect.horizontalScrollbar), + nameof(ScrollRect.horizontalScrollbarSpacing), + nameof(ScrollRect.inertia), + nameof(ScrollRect.scrollSensitivity), + nameof(ScrollRect.vertical), + nameof(ScrollRect.verticalNormalizedPosition), + nameof(ScrollRect.verticalScrollbar), + nameof(ScrollRect.verticalScrollbarSpacing), + nameof(ScrollRect.viewport) + }) + }, + { + typeof(Button), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Button.enabled), + nameof(Button.interactable), + nameof(Button.targetGraphic) + }) + }, + { + typeof(Mask), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Mask.enabled), + nameof(Mask.showMaskGraphic) + }) + }, + { + typeof(RectMask2D), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(RectMask2D.enabled) + }) + }, + { + typeof(Selectable), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(Selectable.enabled), + nameof(Selectable.interactable), + nameof(Selectable.targetGraphic) + }) + }, + { + typeof(ToggleGroup), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(ToggleGroup.allowSwitchOff), + nameof(ToggleGroup.enabled) + }) + }, + #if VRC_SDK_VRCSDK3 // only access Cinemachine and TMPro after install + { + typeof(TextMeshPro), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(TextMeshPro.text), + }) + }, + { + typeof(TextMeshProUGUI), new AllowedMethodFilter( + new List<string>(), + new List<string> + { + nameof(TextMeshProUGUI.text), + }) + }, + { + typeof(Cinemachine.CinemachineVirtualCamera), new AllowedMethodFilter( + new List<string>() + { + nameof(Cinemachine.CinemachineVirtualCamera.Priority), + }, + new List<string>()) + }, + #endif + }; + + private static readonly HashSet<string> _allowedAnimationEventFunctionNames = new HashSet<string> + { + "RunProgram", + "SendCustomEvent", + "Play", + "Pause", + "Stop", + "PlayInFixedTime", + "Rebind", + "SetBool", + "SetFloat", + "SetInteger", + "SetTrigger", + "ResetTrigger", + "SetActive" + }; + + private class AllowedMethodFilter + { + private readonly HashSet<string> _allowedTargets; + + [PublicAPI] + public AllowedMethodFilter(List<string> allowedTargetMethodNames, List<string> allowedTargetPropertyNames) + { + _allowedTargets = new HashSet<string>(); + _allowedTargets.UnionWith(allowedTargetMethodNames); + foreach(string allowedTargetProperty in allowedTargetPropertyNames) + { + _allowedTargets.Add($"get_{allowedTargetProperty}"); + _allowedTargets.Add($"set_{allowedTargetProperty}"); + } + } + + public bool IsTargetMethodAllowed(string targetMethodName) + { + return _allowedTargets.Contains(targetMethodName); + } + + #if VERBOSE_EVENT_SANITIZATION_LOGGING + public List<string> GetTargetMethodNames() + { + return _allowedTargets.ToList(); + } + #endif + } + } +} diff --git a/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/UnityEventFilter.cs.meta b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/UnityEventFilter.cs.meta new file mode 100644 index 00000000..5137d5fe --- /dev/null +++ b/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/UnityEventFilter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 87f13d6a98e54fb78ca47650a29029a5 +timeCreated: 1594690552
\ No newline at end of file |