A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION
2using System;
3using System.Runtime.CompilerServices;
4using System.Runtime.InteropServices;
5using UnityEngine.InputSystem.Controls;
6using UnityEngine.InputSystem.Layouts;
7using UnityEngine.InputSystem.LowLevel;
8using UnityEngine.InputSystem.Switch.LowLevel;
9using UnityEngine.InputSystem.Utilities;
10
11////REVIEW: The Switch controller can be used to point at things; can we somehow help leverage that?
12
13namespace UnityEngine.InputSystem.Switch.LowLevel
14{
15#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA
16 /// <summary>
17 /// Structure of HID input reports for Switch Pro controllers.
18 /// </summary>
19 [StructLayout(LayoutKind.Explicit, Size = 7)]
20 internal struct SwitchProControllerHIDInputState : IInputStateTypeInfo
21 {
22 public static FourCC Format = new FourCC('S', 'P', 'V', 'S'); // Switch Pro Virtual State
23
24 public FourCC format => Format;
25
26 [InputControl(name = "leftStick", layout = "Stick", format = "VC2B")]
27 [InputControl(name = "leftStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
28 [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")]
29 [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")]
30 [InputControl(name = "leftStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
31 [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")]
32 [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")]
33 [FieldOffset(0)] public byte leftStickX;
34 [FieldOffset(1)] public byte leftStickY;
35
36 [InputControl(name = "rightStick", layout = "Stick", format = "VC2B")]
37 [InputControl(name = "rightStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
38 [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")]
39 [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")]
40 [InputControl(name = "rightStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
41 [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")]
42 [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")]
43 [FieldOffset(2)] public byte rightStickX;
44 [FieldOffset(3)] public byte rightStickY;
45
46 [InputControl(name = "dpad", format = "BIT", bit = 0, sizeInBits = 4)]
47 [InputControl(name = "dpad/up", bit = (int)Button.Up)]
48 [InputControl(name = "dpad/right", bit = (int)Button.Right)]
49 [InputControl(name = "dpad/down", bit = (int)Button.Down)]
50 [InputControl(name = "dpad/left", bit = (int)Button.Left)]
51 [InputControl(name = "buttonWest", displayName = "Y", shortDisplayName = "Y", bit = (int)Button.Y, usage = "SecondaryAction")]
52 [InputControl(name = "buttonNorth", displayName = "X", shortDisplayName = "X", bit = (int)Button.X)]
53 [InputControl(name = "buttonSouth", displayName = "B", shortDisplayName = "B", bit = (int)Button.B, usages = new[] { "Back", "Cancel" })]
54 [InputControl(name = "buttonEast", displayName = "A", shortDisplayName = "A", bit = (int)Button.A, usages = new[] { "PrimaryAction", "Submit" })]
55 [InputControl(name = "leftShoulder", displayName = "L", shortDisplayName = "L", bit = (uint)Button.L)]
56 [InputControl(name = "rightShoulder", displayName = "R", shortDisplayName = "R", bit = (uint)Button.R)]
57 [InputControl(name = "leftStickPress", displayName = "Left Stick", bit = (uint)Button.StickL)]
58 [InputControl(name = "rightStickPress", displayName = "Right Stick", bit = (uint)Button.StickR)]
59 [InputControl(name = "leftTrigger", displayName = "ZL", shortDisplayName = "ZL", format = "BIT", bit = (uint)Button.ZL)]
60 [InputControl(name = "rightTrigger", displayName = "ZR", shortDisplayName = "ZR", format = "BIT", bit = (uint)Button.ZR)]
61 [InputControl(name = "start", displayName = "Plus", bit = (uint)Button.Plus, usage = "Menu")]
62 [InputControl(name = "select", displayName = "Minus", bit = (uint)Button.Minus)]
63 [FieldOffset(4)] public ushort buttons1;
64
65 [InputControl(name = "capture", layout = "Button", displayName = "Capture", bit = (uint)Button.Capture - 16)]
66 [InputControl(name = "home", layout = "Button", displayName = "Home", bit = (uint)Button.Home - 16)]
67 [FieldOffset(6)] public byte buttons2;
68
69 public enum Button
70 {
71 Up = 0,
72 Right = 1,
73 Down = 2,
74 Left = 3,
75
76 West = 4,
77 North = 5,
78 South = 6,
79 East = 7,
80
81 L = 8,
82 R = 9,
83 StickL = 10,
84 StickR = 11,
85
86 ZL = 12,
87 ZR = 13,
88 Plus = 14,
89 Minus = 15,
90 Capture = 16,
91 Home = 17,
92
93 X = North,
94 B = South,
95 Y = West,
96 A = East,
97 }
98
99 [MethodImpl(MethodImplOptions.AggressiveInlining)]
100 public SwitchProControllerHIDInputState WithButton(Button button, bool value = true)
101 {
102 Set(button, value);
103 return this;
104 }
105
106 [MethodImpl(MethodImplOptions.AggressiveInlining)]
107 public void Set(Button button, bool state)
108 {
109 Debug.Assert((int)button < 18, $"Expected button < 18");
110 if ((int)button < 16)
111 {
112 var bit = (ushort)(1U << (int)button);
113 if (state)
114 buttons1 = (ushort)(buttons1 | bit);
115 else
116 buttons1 &= (ushort)~bit;
117 }
118 else if ((int)button < 18)
119 {
120 var bit = (byte)(1U << ((int)button - 16));
121 if (state)
122 buttons2 = (byte)(buttons2 | bit);
123 else
124 buttons2 &= (byte)~bit;
125 }
126 }
127
128 [MethodImpl(MethodImplOptions.AggressiveInlining)]
129 public void Press(Button button)
130 {
131 Set(button, true);
132 }
133
134 [MethodImpl(MethodImplOptions.AggressiveInlining)]
135 public void Release(Button button)
136 {
137 Set(button, false);
138 }
139 }
140#endif
141}
142
143namespace UnityEngine.InputSystem.Switch
144{
145#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION
146 /// <summary>
147 /// A Nintendo Switch Pro controller connected to a desktop mac/windows PC using the HID interface.
148 /// </summary>
149 [InputControlLayout(stateType = typeof(SwitchProControllerHIDInputState), displayName = "Switch Pro Controller")]
150 public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEventPreProcessor
151 {
152 [InputControl(name = "capture", displayName = "Capture")]
153 public ButtonControl captureButton { get; protected set; }
154
155 [InputControl(name = "home", displayName = "Home")]
156 public ButtonControl homeButton { get; protected set; }
157
158 protected override void OnAdded()
159 {
160 base.OnAdded();
161
162 captureButton = GetChildControl<ButtonControl>("capture");
163 homeButton = GetChildControl<ButtonControl>("home");
164
165 HandshakeRestart();
166 }
167
168 private static readonly SwitchMagicOutputReport.CommandIdType[] s_HandshakeSequence = new[]
169 {
170 SwitchMagicOutputReport.CommandIdType.Status,
171 SwitchMagicOutputReport.CommandIdType.Handshake,
172 SwitchMagicOutputReport.CommandIdType.Highspeed,
173 SwitchMagicOutputReport.CommandIdType.Handshake,
174 SwitchMagicOutputReport.CommandIdType.ForceUSB
175 ////TODO: Should we add a step to revert back to simple interface?
176 //// Because currently full reports don't work in old input system.
177 };
178
179 private int m_HandshakeStepIndex;
180 private double m_HandshakeTimer;
181
182 private void HandshakeRestart()
183 {
184 // Delay first command issue until some time into the future
185 m_HandshakeStepIndex = -1;
186 m_HandshakeTimer = InputRuntime.s_Instance.currentTime;
187 }
188
189 private void HandshakeTick()
190 {
191 const double handshakeRestartTimeout = 2.0;
192 const double handshakeNextStepTimeout = 0.1;
193
194 var currentTime = InputRuntime.s_Instance.currentTime;
195
196 // There were no events for last few seconds, restart handshake
197 if (currentTime >= m_LastUpdateTimeInternal + handshakeRestartTimeout &&
198 currentTime >= m_HandshakeTimer + handshakeRestartTimeout)
199 m_HandshakeStepIndex = 0;
200 // If handshake is complete, ignore the tick.
201 else if (m_HandshakeStepIndex + 1 >= s_HandshakeSequence.Length)
202 return;
203 // If we timeout, proceed to next step after some time is elapsed.
204 else if (currentTime > m_HandshakeTimer + handshakeNextStepTimeout)
205 m_HandshakeStepIndex++;
206 // If we haven't timed out on handshake step, skip the tick.
207 else
208 return;
209
210 m_HandshakeTimer = currentTime;
211
212 var command = s_HandshakeSequence[m_HandshakeStepIndex];
213
214 // Native backend rejects one of the commands based on size of descriptor.
215 // So just report both at a same time.
216 ////TODO: fix this.
217 var commandBt = SwitchMagicOutputHIDBluetooth.Create(command);
218 if (ExecuteCommand(ref commandBt) > 0)
219 return;
220
221 var commandUsb = SwitchMagicOutputHIDUSB.Create(command);
222 ExecuteCommand(ref commandUsb);
223 }
224
225 public void OnNextUpdate()
226 {
227 HandshakeTick();
228 }
229
230 // filter out three lower bits as jitter noise
231 internal const byte JitterMaskLow = 0b01111000;
232 internal const byte JitterMaskHigh = 0b10000111;
233
234 public unsafe void OnStateEvent(InputEventPtr eventPtr)
235 {
236 if (eventPtr.type == StateEvent.Type && eventPtr.stateFormat == SwitchProControllerHIDInputState.Format)
237 {
238 var currentState = (SwitchProControllerHIDInputState*)((byte*)currentStatePtr + m_StateBlock.byteOffset);
239 var newState = (SwitchProControllerHIDInputState*)StateEvent.FromUnchecked(eventPtr)->state;
240
241 var actuated =
242 // we need to make device current if axes are outside of deadzone specifying hardware jitter of sticks around zero point
243 newState->leftStickX<JitterMaskLow
244 || newState->leftStickX> JitterMaskHigh
245 || newState->leftStickY<JitterMaskLow
246 || newState->leftStickY> JitterMaskHigh
247 || newState->rightStickX<JitterMaskLow
248 || newState->rightStickX> JitterMaskHigh
249 || newState->rightStickY<JitterMaskLow
250 || newState->rightStickY> JitterMaskHigh
251 // we need to make device current if buttons state change
252 || newState->buttons1 != currentState->buttons1
253 || newState->buttons2 != currentState->buttons2;
254
255 if (!actuated)
256 InputSystem.s_Manager.DontMakeCurrentlyUpdatingDeviceCurrent();
257 }
258
259 InputState.Change(this, eventPtr);
260 }
261
262 public bool GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset)
263 {
264 return false;
265 }
266
267 public unsafe bool PreProcessEvent(InputEventPtr eventPtr)
268 {
269 if (eventPtr.type == DeltaStateEvent.Type)
270 // if someone queued delta state SPVS directly, just use as-is
271 // otherwise skip all delta state events
272 return DeltaStateEvent.FromUnchecked(eventPtr)->stateFormat == SwitchProControllerHIDInputState.Format;
273
274 // use all other non-state/non-delta-state events
275 if (eventPtr.type != StateEvent.Type)
276 return true;
277
278 var stateEvent = StateEvent.FromUnchecked(eventPtr);
279 var size = stateEvent->stateSizeInBytes;
280
281 if (stateEvent->stateFormat == SwitchProControllerHIDInputState.Format)
282 return true; // if someone queued SPVS directly, just use as-is
283
284 if (stateEvent->stateFormat != SwitchHIDGenericInputReport.Format || size < sizeof(SwitchHIDGenericInputReport))
285 return false; // skip unrecognized state events otherwise they will corrupt control states
286
287 var genericReport = (SwitchHIDGenericInputReport*)stateEvent->state;
288 if (genericReport->reportId == SwitchSimpleInputReport.ExpectedReportId && size >= SwitchSimpleInputReport.kSize)
289 {
290 var data = ((SwitchSimpleInputReport*)stateEvent->state)->ToHIDInputReport();
291 *((SwitchProControllerHIDInputState*)stateEvent->state) = data;
292 stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
293 return true;
294 }
295 else if (genericReport->reportId == SwitchFullInputReport.ExpectedReportId && size >= SwitchFullInputReport.kSize)
296 {
297 var data = ((SwitchFullInputReport*)stateEvent->state)->ToHIDInputReport();
298 *((SwitchProControllerHIDInputState*)stateEvent->state) = data;
299 stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
300 return true;
301 }
302 else if (size == 8 || size == 9) // official accessories send 8 byte reports
303 {
304 // On Windows HID stack we somehow get 1 byte extra prepended, so if we get 9 bytes, subtract one, see ISX-993
305 // 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).
306 var bugOffset = size == 9 ? 1 : 0;
307 var data = ((SwitchInputOnlyReport*)((byte*)stateEvent->state + bugOffset))->ToHIDInputReport();
308 *((SwitchProControllerHIDInputState*)stateEvent->state) = data;
309 stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
310 return true;
311 }
312 else
313 return false; // skip unrecognized reportId
314 }
315
316 [StructLayout(LayoutKind.Explicit, Size = kSize)]
317 private struct SwitchInputOnlyReport
318 {
319 public const int kSize = 7;
320
321 [FieldOffset(0)] public byte buttons0;
322 [FieldOffset(1)] public byte buttons1;
323 [FieldOffset(2)] public byte hat;
324 [FieldOffset(3)] public byte leftX;
325 [FieldOffset(4)] public byte leftY;
326 [FieldOffset(5)] public byte rightX;
327 [FieldOffset(6)] public byte rightY;
328
329 [MethodImpl(MethodImplOptions.AggressiveInlining)]
330 public SwitchProControllerHIDInputState ToHIDInputReport()
331 {
332 var state = new SwitchProControllerHIDInputState
333 {
334 leftStickX = leftX,
335 leftStickY = leftY,
336 rightStickX = rightX,
337 rightStickY = rightY
338 };
339
340 state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0);
341 state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x02) != 0);
342 state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x04) != 0);
343 state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0);
344 state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0);
345 state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0);
346 state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0);
347 state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
348 state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
349 state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
350 state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0);
351 state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0);
352 state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
353 state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
354
355 var left = false;
356 var up = false;
357 var right = false;
358 var down = false;
359
360 switch (hat)
361 {
362 case 0:
363 up = true;
364 break;
365 case 1:
366 up = true;
367 right = true;
368 break;
369 case 2:
370 right = true;
371 break;
372 case 3:
373 down = true;
374 right = true;
375 break;
376 case 4:
377 down = true;
378 break;
379 case 5:
380 down = true;
381 left = true;
382 break;
383 case 6:
384 left = true;
385 break;
386 case 7:
387 up = true;
388 left = true;
389 break;
390 }
391
392 state.Set(SwitchProControllerHIDInputState.Button.Left, left);
393 state.Set(SwitchProControllerHIDInputState.Button.Up, up);
394 state.Set(SwitchProControllerHIDInputState.Button.Right, right);
395 state.Set(SwitchProControllerHIDInputState.Button.Down, down);
396 return state;
397 }
398 }
399
400 [StructLayout(LayoutKind.Explicit, Size = kSize)]
401 private struct SwitchSimpleInputReport
402 {
403 public const int kSize = 12;
404 public const byte ExpectedReportId = 0x3f;
405
406 [FieldOffset(0)] public byte reportId;
407 [FieldOffset(1)] public byte buttons0;
408 [FieldOffset(2)] public byte buttons1;
409 [FieldOffset(3)] public byte hat;
410 [FieldOffset(4)] public ushort leftX;
411 [FieldOffset(6)] public ushort leftY;
412 [FieldOffset(8)] public ushort rightX;
413 [FieldOffset(10)] public ushort rightY;
414
415 [MethodImpl(MethodImplOptions.AggressiveInlining)]
416 public SwitchProControllerHIDInputState ToHIDInputReport()
417 {
418 var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftX, 16, 8);
419 var leftYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftY, 16, 8);
420 var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightX, 16, 8);
421 var rightYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightY, 16, 8);
422
423 var state = new SwitchProControllerHIDInputState
424 {
425 leftStickX = leftXByte,
426 leftStickY = leftYByte,
427 rightStickX = rightXByte,
428 rightStickY = rightYByte
429 };
430
431 state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x01) != 0);
432 state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x02) != 0);
433 state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x04) != 0);
434 state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0);
435 state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0);
436 state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0);
437 state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0);
438 state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
439 state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
440 state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
441 state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0);
442 state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0);
443 state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
444 state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
445
446 var left = false;
447 var up = false;
448 var right = false;
449 var down = false;
450
451 switch (hat)
452 {
453 case 0:
454 up = true;
455 break;
456 case 1:
457 up = true;
458 right = true;
459 break;
460 case 2:
461 right = true;
462 break;
463 case 3:
464 down = true;
465 right = true;
466 break;
467 case 4:
468 down = true;
469 break;
470 case 5:
471 down = true;
472 left = true;
473 break;
474 case 6:
475 left = true;
476 break;
477 case 7:
478 up = true;
479 left = true;
480 break;
481 }
482
483 state.Set(SwitchProControllerHIDInputState.Button.Left, left);
484 state.Set(SwitchProControllerHIDInputState.Button.Up, up);
485 state.Set(SwitchProControllerHIDInputState.Button.Right, right);
486 state.Set(SwitchProControllerHIDInputState.Button.Down, down);
487
488 return state;
489 }
490 }
491
492 [StructLayout(LayoutKind.Explicit, Size = kSize)]
493 private struct SwitchFullInputReport
494 {
495 public const int kSize = 25;
496 public const byte ExpectedReportId = 0x30;
497
498 [FieldOffset(0)] public byte reportId;
499 [FieldOffset(3)] public byte buttons0;
500 [FieldOffset(4)] public byte buttons1;
501 [FieldOffset(5)] public byte buttons2;
502 [FieldOffset(6)] public byte left0;
503 [FieldOffset(7)] public byte left1;
504 [FieldOffset(8)] public byte left2;
505 [FieldOffset(9)] public byte right0;
506 [FieldOffset(10)] public byte right1;
507 [FieldOffset(11)] public byte right2;
508
509 [MethodImpl(MethodImplOptions.AggressiveInlining)]
510 public SwitchProControllerHIDInputState ToHIDInputReport()
511 {
512 ////TODO: calibration curve
513
514 var leftXRaw = (uint)(left0 | ((left1 & 0x0F) << 8));
515 var leftYRaw = (uint)(((left1 & 0xF0) >> 4) | (left2 << 4));
516 var rightXRaw = (uint)(right0 | ((right1 & 0x0F) << 8));
517 var rightYRaw = (uint)(((right1 & 0xF0) >> 4) | (right2 << 4));
518
519 var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftXRaw, 12, 8);
520 var leftYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftYRaw, 12, 8));
521 var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightXRaw, 12, 8);
522 var rightYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightYRaw, 12, 8));
523
524 var state = new SwitchProControllerHIDInputState
525 {
526 leftStickX = leftXByte,
527 leftStickY = leftYByte,
528 rightStickX = rightXByte,
529 rightStickY = rightYByte
530 };
531
532 state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0);
533 state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x02) != 0);
534 state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x04) != 0);
535 state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x08) != 0);
536 state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x40) != 0);
537 state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
538 state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
539 state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
540 state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x04) != 0);
541 state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x08) != 0);
542 state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
543 state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
544 state.Set(SwitchProControllerHIDInputState.Button.Down, (buttons2 & 0x01) != 0);
545 state.Set(SwitchProControllerHIDInputState.Button.Up, (buttons2 & 0x02) != 0);
546 state.Set(SwitchProControllerHIDInputState.Button.Right, (buttons2 & 0x04) != 0);
547 state.Set(SwitchProControllerHIDInputState.Button.Left, (buttons2 & 0x08) != 0);
548 state.Set(SwitchProControllerHIDInputState.Button.L, (buttons2 & 0x40) != 0);
549 state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons2 & 0x80) != 0);
550
551 return state;
552 }
553 }
554
555 [StructLayout(LayoutKind.Explicit)]
556 private struct SwitchHIDGenericInputReport
557 {
558 public static FourCC Format => new FourCC('H', 'I', 'D');
559
560 [FieldOffset(0)] public byte reportId;
561 }
562
563 [StructLayout(LayoutKind.Explicit, Size = kSize)]
564 internal struct SwitchMagicOutputReport
565 {
566 public const int kSize = 49;
567
568 public const byte ExpectedReplyInputReportId = 0x81;
569
570 [FieldOffset(0)] public byte reportType;
571 [FieldOffset(1)] public byte commandId;
572
573 internal enum ReportType
574 {
575 Magic = 0x80
576 }
577
578 public enum CommandIdType
579 {
580 Status = 0x01,
581 Handshake = 0x02,
582 Highspeed = 0x03,
583 ForceUSB = 0x04,
584 }
585 }
586
587 [StructLayout(LayoutKind.Explicit, Size = kSize)]
588 internal struct SwitchMagicOutputHIDBluetooth : IInputDeviceCommandInfo
589 {
590 public static FourCC Type => new FourCC('H', 'I', 'D', 'O');
591 public FourCC typeStatic => Type;
592
593 public const int kSize = InputDeviceCommand.kBaseCommandSize + 49;
594
595 [FieldOffset(0)] public InputDeviceCommand baseCommand;
596 [FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report;
597
598 public static SwitchMagicOutputHIDBluetooth Create(SwitchMagicOutputReport.CommandIdType type)
599 {
600 return new SwitchMagicOutputHIDBluetooth
601 {
602 baseCommand = new InputDeviceCommand(Type, kSize),
603 report = new SwitchMagicOutputReport
604 {
605 reportType = (byte)SwitchMagicOutputReport.ReportType.Magic,
606 commandId = (byte)type
607 }
608 };
609 }
610 }
611
612 [StructLayout(LayoutKind.Explicit, Size = kSize)]
613 internal struct SwitchMagicOutputHIDUSB : IInputDeviceCommandInfo
614 {
615 public static FourCC Type => new FourCC('H', 'I', 'D', 'O');
616 public FourCC typeStatic => Type;
617
618 public const int kSize = InputDeviceCommand.kBaseCommandSize + 64;
619
620 [FieldOffset(0)] public InputDeviceCommand baseCommand;
621 [FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report;
622
623 public static SwitchMagicOutputHIDUSB Create(SwitchMagicOutputReport.CommandIdType type)
624 {
625 return new SwitchMagicOutputHIDUSB
626 {
627 baseCommand = new InputDeviceCommand(Type, kSize),
628 report = new SwitchMagicOutputReport
629 {
630 reportType = (byte)SwitchMagicOutputReport.ReportType.Magic,
631 commandId = (byte)type
632 }
633 };
634 }
635 }
636 }
637#endif
638}
639#endif // UNITY_EDITOR || UNITY_SWITCH