-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmapping.c
More file actions
163 lines (142 loc) · 7.55 KB
/
Copy pathmapping.c
File metadata and controls
163 lines (142 loc) · 7.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* mapping.c - SCUF Envision Pro -> Nintendo Switch button mapping.
*
* THIS IS THE FILE TO EDIT to change what does what. Everything else is
* plumbing. One table, one translate function, no configuration system.
*
* Mapping is by PHYSICAL POSITION, not by label. The SCUF uses the Xbox
* layout, where the bottom face button is "A"; on a Switch pad that same
* position is "B". So SCUF A -> Switch B, and so on around the diamond.
* This is what makes the controller feel right rather than read right.
*
* SCUF (Xbox layout) Switch
* Y X
* X B -> Y A
* A B
*/
#include "mapping.h"
#include <stdio.h>
#include "scuf_report.h"
#include "switch_report.h"
/* ------------------------------------------------------------------ */
/* Button table */
/* ------------------------------------------------------------------ */
/* Which byte of the decoded state a source bit lives in. */
typedef enum { SRC_BTN, SRC_BTN2, SRC_REAR, SRC_S2 } src_field_t;
typedef struct {
src_field_t field;
uint8_t mask; /* bit within that field */
uint16_t switch_mask; /* SWITCH_MASK_*, or 0 for "unmapped" */
const char *comment;
} button_map_t;
static const button_map_t BUTTON_MAP[] = {
/* --- face buttons: positional swap, see diagram above --------- */
{ SRC_BTN, SCUF_BTN_A, SWITCH_MASK_B, "SCUF A (bottom) -> Switch B (bottom)" },
{ SRC_BTN, SCUF_BTN_B, SWITCH_MASK_A, "SCUF B (right) -> Switch A (right)" },
{ SRC_BTN, SCUF_BTN_X, SWITCH_MASK_Y, "SCUF X (left) -> Switch Y (left)" },
{ SRC_BTN, SCUF_BTN_Y, SWITCH_MASK_X, "SCUF Y (top) -> Switch X (top)" },
/* --- shoulders ------------------------------------------------ */
{ SRC_BTN, SCUF_BTN_L1, SWITCH_MASK_L, "left bumper" },
{ SRC_BTN, SCUF_BTN_R1, SWITCH_MASK_R, "right bumper" },
/* --- menu -----------------------------------------------------
* DELIBERATELY UNMAPPED. The View and Menu buttons sit close enough to
* the D-pad and face buttons to be hit by accident mid-game, so Minus and
* Plus have been moved out to the G-keys below, where nothing else is. */
{ SRC_BTN, SCUF_BTN_VIEW, 0, "View -> nothing (moved to G1, easy to hit by mistake)" },
{ SRC_BTN, SCUF_BTN_MENU, 0, "Menu -> nothing (moved to G5, easy to hit by mistake)" },
/* --- stick clicks and home ------------------------------------ */
{ SRC_BTN2, SCUF_BTN_L3, SWITCH_MASK_L3, "left stick click" },
{ SRC_BTN2, SCUF_BTN_R3, SWITCH_MASK_R3, "right stick click" },
{ SRC_BTN2, SCUF_BTN_HOME, SWITCH_MASK_HOME, "centre button -> Home" },
/* --- G-keys ---------------------------------------------------
* The G-keys report only their own bit and never a duplicate binding, so
* unlike the rear buttons they need no profile changes to be usable.
* Minus and Plus live here now, out of the way of the thumbs. */
{ SRC_BTN2, SCUF_BTN_G1, SWITCH_MASK_MINUS, "G1 -> Minus (Select)" },
{ SRC_BTN2, SCUF_BTN_G2, 0, "G2 -> unmapped (spare)" },
{ SRC_BTN2, SCUF_BTN_G3, 0, "G3 -> unmapped (spare)" },
{ SRC_BTN2, SCUF_BTN_G4, 0, "G4 -> unmapped (spare)" },
{ SRC_BTN2, SCUF_BTN_G5, SWITCH_MASK_PLUS, "G5 -> Plus (Start)" },
/* --- rear buttons ---------------------------------------------
* Physical order across the back is S1, P4, P3, P2, P1, S2.
*
* REQUIRES the rear buttons to be UNASSIGNED in the controller's
* profiles. A rear button that still has a binding emits its own bit AND
* that binding, so it would fire two things at once here. The bindings
* are PER PROFILE - clear all of them, not just the active one.
* See docs/protocol.md section 0A.
*
* All six are intentionally unmapped: the rear buttons are not wanted
* for now. The bits are decoded and available, so assigning one is a
* one-line change - but read the paragraph above first. */
{ SRC_REAR, SCUF_BTN_P1, 0, "P1 -> nothing" },
{ SRC_REAR, SCUF_BTN_P2, 0, "P2 -> nothing" },
{ SRC_REAR, SCUF_BTN_P3, 0, "P3 -> nothing" },
{ SRC_REAR, SCUF_BTN_P4, 0, "P4 -> nothing" },
{ SRC_REAR, SCUF_BTN_S1, 0, "S1 -> nothing" },
{ SRC_S2, SCUF_BTN_S2, 0, "S2 -> nothing" },
};
#define BUTTON_MAP_COUNT (sizeof(BUTTON_MAP) / sizeof(BUTTON_MAP[0]))
/* ------------------------------------------------------------------ */
/* Axis scaling */
/* ------------------------------------------------------------------ */
/* SCUF: int16, 0 centred, exact 0 at rest, round gate.
* Switch: uint8, 0x80 centred.
*
* Straight linear per-axis scale. The gate is round on both ends, so a
* diagonal reaching ~0.81 of full scale per axis is correct - do NOT try to
* renormalise it into a square. No deadzone either: the controller already
* applies one and reports exactly 0 at rest. */
static uint8_t scale_axis(int16_t v) {
int32_t scaled = ((int32_t)v * 127) / 32767;
if (scaled > 127) scaled = 127;
if (scaled < -128) scaled = -128;
return (uint8_t)(scaled + SWITCH_JOYSTICK_MID);
}
/* ------------------------------------------------------------------ */
/* Milestone 5 verification: show what the Switch is actually being sent. */
void mapping_print_switch_report(const SwitchReport *r) {
static const struct { uint16_t mask; const char *name; } NAMES[] = {
{ SWITCH_MASK_Y, "Y" }, { SWITCH_MASK_B, "B" },
{ SWITCH_MASK_A, "A" }, { SWITCH_MASK_X, "X" },
{ SWITCH_MASK_L, "L" }, { SWITCH_MASK_R, "R" },
{ SWITCH_MASK_ZL, "ZL" }, { SWITCH_MASK_ZR, "ZR" },
{ SWITCH_MASK_MINUS, "MINUS" }, { SWITCH_MASK_PLUS, "PLUS" },
{ SWITCH_MASK_L3, "L3" }, { SWITCH_MASK_R3, "R3" },
{ SWITCH_MASK_HOME, "HOME" }, { SWITCH_MASK_CAPTURE, "CAPTURE" },
};
printf(" -> switch: btn=%04X hat=%X L(%3u,%3u) R(%3u,%3u) ",
r->buttons, r->hat, r->lx, r->ly, r->rx, r->ry);
for (size_t i = 0; i < sizeof(NAMES) / sizeof(NAMES[0]); i++) {
if (r->buttons & NAMES[i].mask) printf("%s ", NAMES[i].name);
}
putchar('\n');
}
void mapping_translate(const scuf_state_t *in, SwitchReport *out) {
switch_report_neutral(out);
/* Buttons */
for (size_t i = 0; i < BUTTON_MAP_COUNT; i++) {
const button_map_t *m = &BUTTON_MAP[i];
if (m->switch_mask == 0) continue;
bool pressed = false;
switch (m->field) {
case SRC_BTN: pressed = (in->buttons & m->mask) != 0; break;
case SRC_BTN2: pressed = (in->buttons2 & m->mask) != 0; break;
case SRC_REAR: pressed = (in->rear & m->mask) != 0; break;
case SRC_S2: pressed = in->s2; break;
}
if (pressed) out->buttons |= m->switch_mask;
}
/* Analog triggers -> digital ZL/ZR. The Switch has no analog triggers. */
if (in->l2 >= SCUF_TRIGGER_THRESHOLD) out->buttons |= SWITCH_MASK_ZL;
if (in->r2 >= SCUF_TRIGGER_THRESHOLD) out->buttons |= SWITCH_MASK_ZR;
/* D-pad. The SCUF hat uses the same numbering as the Switch's, so this is
* a straight copy - but validate rather than trust, so a surprise value
* cannot become a stuck direction. */
out->hat = (in->hat <= SCUF_HAT_UPLEFT) ? in->hat : SWITCH_HAT_NOTHING;
/* Sticks */
out->lx = scale_axis(in->lx);
out->ly = scale_axis(in->ly);
out->rx = scale_axis(in->rx);
out->ry = scale_axis(in->ry);
}