#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Switch.LowLevel; using UnityEngine.InputSystem.Utilities; ////REVIEW: The Switch controller can be used to point at things; can we somehow help leverage that? namespace UnityEngine.InputSystem.Switch.LowLevel { #if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA /// /// Structure of HID input reports for Switch Pro controllers. /// [StructLayout(LayoutKind.Explicit, Size = 7)] internal struct SwitchProControllerHIDInputState : IInputStateTypeInfo { public static FourCC Format = new FourCC('S', 'P', 'V', 'S'); // Switch Pro Virtual State public FourCC format => Format; [InputControl(name = "leftStick", layout = "Stick", format = "VC2B")] [InputControl(name = "leftStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")] [InputControl(name = "leftStick/left", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")] [InputControl(name = "leftStick/right", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85")] [InputControl(name = "leftStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")] [InputControl(name = "leftStick/up", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")] [InputControl(name = "leftStick/down", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85,invert=false")] [FieldOffset(0)] public byte leftStickX; [FieldOffset(1)] public byte leftStickY; [InputControl(name = "rightStick", layout = "Stick", format = "VC2B")] [InputControl(name = "rightStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")] [InputControl(name = "rightStick/left", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")] [InputControl(name = "rightStick/right", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")] [InputControl(name = "rightStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")] [InputControl(name = "rightStick/up", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")] [InputControl(name = "rightStick/down", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85,invert=false")] [FieldOffset(2)] public byte rightStickX; [FieldOffset(3)] public byte rightStickY; [InputControl(name = "dpad", format = "BIT", bit = 0, sizeInBits = 4)] [InputControl(name = "dpad/up", bit = (int)Button.Up)] [InputControl(name = "dpad/right", bit = (int)Button.Right)] [InputControl(name = "dpad/down", bit = (int)Button.Down)] [InputControl(name = "dpad/left", bit = (int)Button.Left)] [InputControl(name = "buttonWest", displayName = "Y", shortDisplayName = "Y", bit = (int)Button.Y, usage = "SecondaryAction")] [InputControl(name = "buttonNorth", displayName = "X", shortDisplayName = "X", bit = (int)Button.X)] [InputControl(name = "buttonSouth", displayName = "B", shortDisplayName = "B", bit = (int)Button.B, usage = "Back")] [InputControl(name = "buttonEast", displayName = "A", shortDisplayName = "A", bit = (int)Button.A, usage = "PrimaryAction")] [InputControl(name = "leftShoulder", displayName = "L", shortDisplayName = "L", bit = (uint)Button.L)] [InputControl(name = "rightShoulder", displayName = "R", shortDisplayName = "R", bit = (uint)Button.R)] [InputControl(name = "leftStickPress", displayName = "Left Stick", bit = (uint)Button.StickL)] [InputControl(name = "rightStickPress", displayName = "Right Stick", bit = (uint)Button.StickR)] [InputControl(name = "leftTrigger", displayName = "ZL", shortDisplayName = "ZL", format = "BIT", bit = (uint)Button.ZL)] [InputControl(name = "rightTrigger", displayName = "ZR", shortDisplayName = "ZR", format = "BIT", bit = (uint)Button.ZR)] [InputControl(name = "start", displayName = "Plus", bit = (uint)Button.Plus, usage = "Menu")] [InputControl(name = "select", displayName = "Minus", bit = (uint)Button.Minus)] [FieldOffset(4)] public ushort buttons1; [InputControl(name = "capture", layout = "Button", displayName = "Capture", bit = (uint)Button.Capture - 16)] [InputControl(name = "home", layout = "Button", displayName = "Home", bit = (uint)Button.Home - 16)] [FieldOffset(6)] public byte buttons2; public enum Button { Up = 0, Right = 1, Down = 2, Left = 3, West = 4, North = 5, South = 6, East = 7, L = 8, R = 9, StickL = 10, StickR = 11, ZL = 12, ZR = 13, Plus = 14, Minus = 15, Capture = 16, Home = 17, X = North, B = South, Y = West, A = East, } [MethodImpl(MethodImplOptions.AggressiveInlining)] public SwitchProControllerHIDInputState WithButton(Button button, bool value = true) { Set(button, value); return this; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Set(Button button, bool state) { Debug.Assert((int)button < 18, $"Expected button < 18"); if ((int)button < 16) { var bit = (ushort)(1U << (int)button); if (state) buttons1 = (ushort)(buttons1 | bit); else buttons1 &= (ushort)~bit; } else if ((int)button < 18) { var bit = (byte)(1U << ((int)button - 16)); if (state) buttons2 = (byte)(buttons2 | bit); else buttons2 &= (byte)~bit; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Press(Button button) { Set(button, true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Release(Button button) { Set(button, false); } } #endif } namespace UnityEngine.InputSystem.Switch { #if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION /// /// A Nintendo Switch Pro controller connected to a desktop mac/windows PC using the HID interface. /// [InputControlLayout(stateType = typeof(SwitchProControllerHIDInputState), displayName = "Switch Pro Controller")] public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEventPreProcessor { [InputControl(name = "capture", displayName = "Capture")] public ButtonControl captureButton { get; protected set; } [InputControl(name = "home", displayName = "Home")] public ButtonControl homeButton { get; protected set; } protected override void OnAdded() { base.OnAdded(); captureButton = GetChildControl("capture"); homeButton = GetChildControl("home"); HandshakeRestart(); } private static readonly SwitchMagicOutputReport.CommandIdType[] s_HandshakeSequence = new[] { SwitchMagicOutputReport.CommandIdType.Status, SwitchMagicOutputReport.CommandIdType.Handshake, SwitchMagicOutputReport.CommandIdType.Highspeed, SwitchMagicOutputReport.CommandIdType.Handshake, SwitchMagicOutputReport.CommandIdType.ForceUSB ////TODO: Should we add a step to revert back to simple interface? //// Because currently full reports don't work in old input system. }; private int m_HandshakeStepIndex; private double m_HandshakeTimer; private void HandshakeRestart() { // Delay first command issue until some time into the future m_HandshakeStepIndex = -1; m_HandshakeTimer = InputRuntime.s_Instance.currentTime; } private void HandshakeTick() { const double handshakeRestartTimeout = 2.0; const double handshakeNextStepTimeout = 0.1; var currentTime = InputRuntime.s_Instance.currentTime; // There were no events for last few seconds, restart handshake if (currentTime >= m_LastUpdateTimeInternal + handshakeRestartTimeout && currentTime >= m_HandshakeTimer + handshakeRestartTimeout) m_HandshakeStepIndex = 0; // If handshake is complete, ignore the tick. else if (m_HandshakeStepIndex + 1 >= s_HandshakeSequence.Length) return; // If we timeout, proceed to next step after some time is elapsed. else if (currentTime > m_HandshakeTimer + handshakeNextStepTimeout) m_HandshakeStepIndex++; // If we haven't timed out on handshake step, skip the tick. else return; m_HandshakeTimer = currentTime; var command = s_HandshakeSequence[m_HandshakeStepIndex]; // Native backend rejects one of the commands based on size of descriptor. // So just report both at a same time. ////TODO: fix this. var commandBt = SwitchMagicOutputHIDBluetooth.Create(command); if (ExecuteCommand(ref commandBt) > 0) return; var commandUsb = SwitchMagicOutputHIDUSB.Create(command); ExecuteCommand(ref commandUsb); } public void OnNextUpdate() { HandshakeTick(); } // filter out three lower bits as jitter noise internal const byte JitterMaskLow = 0b01111000; internal const byte JitterMaskHigh = 0b10000111; public unsafe void OnStateEvent(InputEventPtr eventPtr) { if (eventPtr.type == StateEvent.Type && eventPtr.stateFormat == SwitchProControllerHIDInputState.Format) { var currentState = (SwitchProControllerHIDInputState*)((byte*)currentStatePtr + m_StateBlock.byteOffset); var newState = (SwitchProControllerHIDInputState*)StateEvent.FromUnchecked(eventPtr)->state; var actuated = // we need to make device current if axes are outside of deadzone specifying hardware jitter of sticks around zero point newState->leftStickXleftStickX> JitterMaskHigh || newState->leftStickYleftStickY> JitterMaskHigh || newState->rightStickXrightStickX> JitterMaskHigh || newState->rightStickYrightStickY> JitterMaskHigh // we need to make device current if buttons state change || newState->buttons1 != currentState->buttons1 || newState->buttons2 != currentState->buttons2; if (!actuated) InputSystem.s_Manager.DontMakeCurrentlyUpdatingDeviceCurrent(); } InputState.Change(this, eventPtr); } public bool GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset) { return false; } public unsafe bool PreProcessEvent(InputEventPtr eventPtr) { if (eventPtr.type == DeltaStateEvent.Type) // if someone queued delta state SPVS directly, just use as-is // otherwise skip all delta state events return DeltaStateEvent.FromUnchecked(eventPtr)->stateFormat == SwitchProControllerHIDInputState.Format; // use all other non-state/non-delta-state events if (eventPtr.type != StateEvent.Type) return true; var stateEvent = StateEvent.FromUnchecked(eventPtr); var size = stateEvent->stateSizeInBytes; if (stateEvent->stateFormat == SwitchProControllerHIDInputState.Format) return true; // if someone queued SPVS directly, just use as-is if (stateEvent->stateFormat != SwitchHIDGenericInputReport.Format || size < sizeof(SwitchHIDGenericInputReport)) return false; // skip unrecognized state events otherwise they will corrupt control states var genericReport = (SwitchHIDGenericInputReport*)stateEvent->state; if (genericReport->reportId == SwitchSimpleInputReport.ExpectedReportId && size >= SwitchSimpleInputReport.kSize) { var data = ((SwitchSimpleInputReport*)stateEvent->state)->ToHIDInputReport(); *((SwitchProControllerHIDInputState*)stateEvent->state) = data; stateEvent->stateFormat = SwitchProControllerHIDInputState.Format; return true; } else if (genericReport->reportId == SwitchFullInputReport.ExpectedReportId && size >= SwitchFullInputReport.kSize) { var data = ((SwitchFullInputReport*)stateEvent->state)->ToHIDInputReport(); *((SwitchProControllerHIDInputState*)stateEvent->state) = data; stateEvent->stateFormat = SwitchProControllerHIDInputState.Format; return true; } else if (size == 8 || size == 9) // official accessories send 8 byte reports { // On Windows HID stack we somehow get 1 byte extra prepended, so if we get 9 bytes, subtract one, see ISX-993 // This is written in such way that if we fix it in backend, we wont break the package (Unity will report 8 bytes instead of 9 bytes). var bugOffset = size == 9 ? 1 : 0; var data = ((SwitchInputOnlyReport*)((byte*)stateEvent->state + bugOffset))->ToHIDInputReport(); *((SwitchProControllerHIDInputState*)stateEvent->state) = data; stateEvent->stateFormat = SwitchProControllerHIDInputState.Format; return true; } else return false; // skip unrecognized reportId } [StructLayout(LayoutKind.Explicit, Size = kSize)] private struct SwitchInputOnlyReport { public const int kSize = 7; [FieldOffset(0)] public byte buttons0; [FieldOffset(1)] public byte buttons1; [FieldOffset(2)] public byte hat; [FieldOffset(3)] public byte leftX; [FieldOffset(4)] public byte leftY; [FieldOffset(5)] public byte rightX; [FieldOffset(6)] public byte rightY; [MethodImpl(MethodImplOptions.AggressiveInlining)] public SwitchProControllerHIDInputState ToHIDInputReport() { var state = new SwitchProControllerHIDInputState { leftStickX = leftX, leftStickY = leftY, rightStickX = rightX, rightStickY = rightY }; state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0); state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0); state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0); state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0); state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0); state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0); var left = false; var up = false; var right = false; var down = false; switch (hat) { case 0: up = true; break; case 1: up = true; right = true; break; case 2: right = true; break; case 3: down = true; right = true; break; case 4: down = true; break; case 5: down = true; left = true; break; case 6: left = true; break; case 7: up = true; left = true; break; } state.Set(SwitchProControllerHIDInputState.Button.Left, left); state.Set(SwitchProControllerHIDInputState.Button.Up, up); state.Set(SwitchProControllerHIDInputState.Button.Right, right); state.Set(SwitchProControllerHIDInputState.Button.Down, down); return state; } } [StructLayout(LayoutKind.Explicit, Size = kSize)] private struct SwitchSimpleInputReport { public const int kSize = 12; public const byte ExpectedReportId = 0x3f; [FieldOffset(0)] public byte reportId; [FieldOffset(1)] public byte buttons0; [FieldOffset(2)] public byte buttons1; [FieldOffset(3)] public byte hat; [FieldOffset(4)] public ushort leftX; [FieldOffset(6)] public ushort leftY; [FieldOffset(8)] public ushort rightX; [FieldOffset(10)] public ushort rightY; [MethodImpl(MethodImplOptions.AggressiveInlining)] public SwitchProControllerHIDInputState ToHIDInputReport() { var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftX, 16, 8); var leftYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftY, 16, 8); var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightX, 16, 8); var rightYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightY, 16, 8); var state = new SwitchProControllerHIDInputState { leftStickX = leftXByte, leftStickY = leftYByte, rightStickX = rightXByte, rightStickY = rightYByte }; state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0); state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0); state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0); state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0); state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0); state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0); var left = false; var up = false; var right = false; var down = false; switch (hat) { case 0: up = true; break; case 1: up = true; right = true; break; case 2: right = true; break; case 3: down = true; right = true; break; case 4: down = true; break; case 5: down = true; left = true; break; case 6: left = true; break; case 7: up = true; left = true; break; } state.Set(SwitchProControllerHIDInputState.Button.Left, left); state.Set(SwitchProControllerHIDInputState.Button.Up, up); state.Set(SwitchProControllerHIDInputState.Button.Right, right); state.Set(SwitchProControllerHIDInputState.Button.Down, down); return state; } } [StructLayout(LayoutKind.Explicit, Size = kSize)] private struct SwitchFullInputReport { public const int kSize = 25; public const byte ExpectedReportId = 0x30; [FieldOffset(0)] public byte reportId; [FieldOffset(3)] public byte buttons0; [FieldOffset(4)] public byte buttons1; [FieldOffset(5)] public byte buttons2; [FieldOffset(6)] public byte left0; [FieldOffset(7)] public byte left1; [FieldOffset(8)] public byte left2; [FieldOffset(9)] public byte right0; [FieldOffset(10)] public byte right1; [FieldOffset(11)] public byte right2; [MethodImpl(MethodImplOptions.AggressiveInlining)] public SwitchProControllerHIDInputState ToHIDInputReport() { ////TODO: calibration curve var leftXRaw = (uint)(left0 | ((left1 & 0x0F) << 8)); var leftYRaw = (uint)(((left1 & 0xF0) >> 4) | (left2 << 4)); var rightXRaw = (uint)(right0 | ((right1 & 0x0F) << 8)); var rightYRaw = (uint)(((right1 & 0xF0) >> 4) | (right2 << 4)); var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftXRaw, 12, 8); var leftYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftYRaw, 12, 8)); var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightXRaw, 12, 8); var rightYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightYRaw, 12, 8)); var state = new SwitchProControllerHIDInputState { leftStickX = leftXByte, leftStickY = leftYByte, rightStickX = rightXByte, rightStickY = rightYByte }; state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x40) != 0); state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0); state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0); state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0); state.Set(SwitchProControllerHIDInputState.Button.Down, (buttons2 & 0x01) != 0); state.Set(SwitchProControllerHIDInputState.Button.Up, (buttons2 & 0x02) != 0); state.Set(SwitchProControllerHIDInputState.Button.Right, (buttons2 & 0x04) != 0); state.Set(SwitchProControllerHIDInputState.Button.Left, (buttons2 & 0x08) != 0); state.Set(SwitchProControllerHIDInputState.Button.L, (buttons2 & 0x40) != 0); state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons2 & 0x80) != 0); return state; } } [StructLayout(LayoutKind.Explicit)] private struct SwitchHIDGenericInputReport { public static FourCC Format => new FourCC('H', 'I', 'D'); [FieldOffset(0)] public byte reportId; } [StructLayout(LayoutKind.Explicit, Size = kSize)] internal struct SwitchMagicOutputReport { public const int kSize = 49; public const byte ExpectedReplyInputReportId = 0x81; [FieldOffset(0)] public byte reportType; [FieldOffset(1)] public byte commandId; internal enum ReportType { Magic = 0x80 } public enum CommandIdType { Status = 0x01, Handshake = 0x02, Highspeed = 0x03, ForceUSB = 0x04, } } [StructLayout(LayoutKind.Explicit, Size = kSize)] internal struct SwitchMagicOutputHIDBluetooth : IInputDeviceCommandInfo { public static FourCC Type => new FourCC('H', 'I', 'D', 'O'); public FourCC typeStatic => Type; public const int kSize = InputDeviceCommand.kBaseCommandSize + 49; [FieldOffset(0)] public InputDeviceCommand baseCommand; [FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report; public static SwitchMagicOutputHIDBluetooth Create(SwitchMagicOutputReport.CommandIdType type) { return new SwitchMagicOutputHIDBluetooth { baseCommand = new InputDeviceCommand(Type, kSize), report = new SwitchMagicOutputReport { reportType = (byte)SwitchMagicOutputReport.ReportType.Magic, commandId = (byte)type } }; } } [StructLayout(LayoutKind.Explicit, Size = kSize)] internal struct SwitchMagicOutputHIDUSB : IInputDeviceCommandInfo { public static FourCC Type => new FourCC('H', 'I', 'D', 'O'); public FourCC typeStatic => Type; public const int kSize = InputDeviceCommand.kBaseCommandSize + 64; [FieldOffset(0)] public InputDeviceCommand baseCommand; [FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report; public static SwitchMagicOutputHIDUSB Create(SwitchMagicOutputReport.CommandIdType type) { return new SwitchMagicOutputHIDUSB { baseCommand = new InputDeviceCommand(Type, kSize), report = new SwitchMagicOutputReport { reportType = (byte)SwitchMagicOutputReport.ReportType.Magic, commandId = (byte)type } }; } } } #endif } #endif // UNITY_EDITOR || UNITY_SWITCH