Skip to content

Commit d5d7399

Browse files
committed
started caramel library for interfacing with real gamepad
1 parent 6812312 commit d5d7399

File tree

8 files changed

+610
-209
lines changed

8 files changed

+610
-209
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ OPTION(VANILLA_BUILD_RPI "Build SDL2 app for Raspberry Pi" ON)
2424
OPTION(VANILLA_BUILD_TESTS "Build unit tests for Vanilla" OFF)
2525
OPTION(VANILLA_BUILD_PIPE "Build vanilla-pipe for connecting to Wii U (Linux only)" ${LINUX})
2626
OPTION(VANILLA_BUILD_VENDORED "Build Vanilla with \"vendored\" third-party libraries" ${vendored_default})
27+
OPTION(VANILLA_BUILD_CARAMEL "Build Caramel library" OFF)
2728

2829
add_subdirectory(lib)
2930
if (VANILLA_BUILD_PIPE)
@@ -35,3 +36,6 @@ endif()
3536
if (VANILLA_BUILD_RPI)
3637
add_subdirectory(rpi)
3738
endif()
39+
if (VANILLA_BUILD_CARAMEL)
40+
add_subdirectory(caramel)
41+
endif()

caramel/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
add_executable(caramel
2+
main.c
3+
)
4+
5+
target_link_libraries(caramel PRIVATE
6+
vanilla
7+
m
8+
)

caramel/main.c

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
#include <arpa/inet.h>
2+
#include <errno.h>
3+
#include <pthread.h>
4+
#include <stdarg.h>
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <sys/time.h>
9+
#include <unistd.h>
10+
11+
#include "../lib/gamepad/command.h"
12+
#include "../lib/util.h"
13+
#include "../pipe/ports.h"
14+
15+
static const int PORTS[] = {PORT_VID, PORT_AUD, PORT_HID, PORT_MSG, PORT_CMD};
16+
#define PORT_COUNT 5
17+
18+
typedef struct listener_data_t listener_data_t;
19+
typedef void (*pkt_handler_t)(listener_data_t *ctx, const void *data, size_t size);
20+
21+
static int RUNNING = 1;
22+
23+
typedef struct listener_data_t {
24+
int socket;
25+
uint16_t port;
26+
pthread_t thread;
27+
int thread_started;
28+
struct timeval time;
29+
pkt_handler_t handler;
30+
} listener_data_t;
31+
32+
void nlprint(const char *fmt, ...)
33+
{
34+
va_list va;
35+
va_start(va, fmt);
36+
vfprintf(stderr, fmt, va);
37+
fprintf(stderr, "\n");
38+
va_end(va);
39+
}
40+
41+
ssize_t send_to_gamepad(int skt, const void *data, size_t size)
42+
{
43+
struct sockaddr_in sa = {0};
44+
sa.sin_family = AF_INET;
45+
sa.sin_addr.s_addr = inet_addr("192.168.1.11");
46+
sa.sin_port = htons(PORT_CMD);
47+
48+
sendto(skt, data, size, 0, (const struct sockaddr *) &sa, sizeof(sa));
49+
}
50+
51+
int create_socket(uint16_t port)
52+
{
53+
struct sockaddr_in sa = {0};
54+
sa.sin_family = AF_INET;
55+
sa.sin_addr.s_addr = INADDR_ANY;
56+
sa.sin_port = htons(port - 100);
57+
58+
int skt = socket(AF_INET, SOCK_DGRAM, 0);
59+
if (skt == -1) {
60+
nlprint("FAILED TO CREATE SOCKET FOR PORT %u: %i", port, errno);
61+
return -1;
62+
}
63+
64+
// Timeout after 250ms
65+
struct timeval tv = {0};
66+
tv.tv_usec = 250000;
67+
setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
68+
69+
int ret = bind(skt, (const struct sockaddr *) &sa, sizeof(sa));
70+
if (ret == -1) {
71+
nlprint("FAILED TO BIND SOCKET FOR PORT %u: %i", port, errno);
72+
close(skt);
73+
return -1;
74+
}
75+
76+
return skt;
77+
}
78+
79+
void printstart(listener_data_t *ctx)
80+
{
81+
struct timeval tv;
82+
gettimeofday(&tv, 0);
83+
84+
long time = (tv.tv_sec - ctx->time.tv_sec) * 1000000 + (tv.tv_usec - ctx->time.tv_usec);
85+
86+
fprintf(stderr, "[%li.%06li] [%u] ", time / 1000000, time % 1000000, ctx->port);
87+
}
88+
89+
void default_handler(listener_data_t *ctx, const void *data, size_t size)
90+
{
91+
printstart(ctx);
92+
nlprint("Received packet of size %zi", size);
93+
}
94+
95+
void hid_handler(listener_data_t *ctx, const void *data, size_t size)
96+
{
97+
static int count = 0;
98+
const int threshold = 180;
99+
count++;
100+
if (count % threshold == 0) {
101+
// printstart(ctx);
102+
// nlprint("%i input events", threshold);
103+
}
104+
}
105+
106+
static pthread_mutex_t cmd_mutex = PTHREAD_MUTEX_INITIALIZER;
107+
static UvcUacPacket uvc_uac_pkt;
108+
static int cmd_seq_id = 0;
109+
void *send_uvc_uac_pkt_loop(void *arg)
110+
{
111+
listener_data_t *data = (listener_data_t *) arg;
112+
113+
uvc_uac_pkt.cmd_header.packet_type = PACKET_TYPE_REQUEST;
114+
uvc_uac_pkt.cmd_header.query_type = CMD_UVC_UAC;
115+
uvc_uac_pkt.cmd_header.payload_size = sizeof(uvc_uac_pkt.uac_uvc);
116+
117+
uvc_uac_pkt.uac_uvc.f1 = 1;
118+
uvc_uac_pkt.uac_uvc.unknown_0 = 0;
119+
uvc_uac_pkt.uac_uvc.f3 = 0;
120+
uvc_uac_pkt.uac_uvc.mic_enable = 0;
121+
uvc_uac_pkt.uac_uvc.mic_mute = 0;
122+
uvc_uac_pkt.uac_uvc.mic_volume = 0x1600;
123+
uvc_uac_pkt.uac_uvc.mic_volume_2 = 0x1900;
124+
uvc_uac_pkt.uac_uvc.unknown_A = 0;
125+
uvc_uac_pkt.uac_uvc.unknown_B = 0;
126+
uvc_uac_pkt.uac_uvc.mic_freq = 16000;
127+
uvc_uac_pkt.uac_uvc.cam_enable = 0;
128+
uvc_uac_pkt.uac_uvc.cam_power = 0;
129+
uvc_uac_pkt.uac_uvc.cam_power_freq = 0;
130+
uvc_uac_pkt.uac_uvc.cam_auto_expo = 1;
131+
uvc_uac_pkt.uac_uvc.cam_expo_absolute = 0x009E0200;
132+
uvc_uac_pkt.uac_uvc.cam_brightness = 0;
133+
uvc_uac_pkt.uac_uvc.cam_contrast = 0;
134+
uvc_uac_pkt.uac_uvc.cam_gain = 0;
135+
uvc_uac_pkt.uac_uvc.cam_hue = 0x0070;
136+
uvc_uac_pkt.uac_uvc.cam_saturation = 0;
137+
uvc_uac_pkt.uac_uvc.cam_sharpness = 0x4040;
138+
uvc_uac_pkt.uac_uvc.cam_gamma = 3;
139+
uvc_uac_pkt.uac_uvc.cam_key_frame = 0x2D;
140+
uvc_uac_pkt.uac_uvc.cam_white_balance_auto = 0;
141+
uvc_uac_pkt.uac_uvc.cam_white_balance = 0x00800100;
142+
uvc_uac_pkt.uac_uvc.cam_multiplier = 0x40;
143+
uvc_uac_pkt.uac_uvc.cam_multiplier_limit = 0;
144+
145+
while (RUNNING) {
146+
pthread_mutex_lock(&cmd_mutex);
147+
uvc_uac_pkt.cmd_header.seq_id = cmd_seq_id;
148+
cmd_seq_id++;
149+
pthread_mutex_unlock(&cmd_mutex);
150+
151+
nlprint("Sending UAC/UVC packet (seq ID: %X)...", uvc_uac_pkt.cmd_header.seq_id);
152+
send_to_gamepad(data->socket, &uvc_uac_pkt, sizeof(uvc_uac_pkt));
153+
154+
sleep(1);
155+
}
156+
}
157+
158+
void cmd_handler(listener_data_t *ctx, const void *data, size_t size)
159+
{
160+
static long last_cmd = 0;
161+
162+
struct timeval tv;
163+
gettimeofday(&tv, 0);
164+
165+
long now = tv.tv_sec * 1000000 + tv.tv_usec;
166+
long delta = now - last_cmd;
167+
last_cmd = now;
168+
169+
CmdHeader *hdr = (CmdHeader *) data;
170+
const char *packet_type_str = "";
171+
switch (hdr->packet_type) {
172+
case PACKET_TYPE_REQUEST: packet_type_str = "PACKET_TYPE_REQUEST"; break;
173+
case PACKET_TYPE_REQUEST_ACK: packet_type_str = "PACKET_TYPE_REQUEST_ACK"; break;
174+
case PACKET_TYPE_RESPONSE: packet_type_str = "PACKET_TYPE_RESPONSE"; break;
175+
case PACKET_TYPE_RESPONSE_ACK: packet_type_str = "PACKET_TYPE_RESPONSE_ACK"; break;
176+
}
177+
nlprint("[CMD] packet_type = %s, query_type = %X, payload_size = %X, seq_id = %x, delta = %li us", packet_type_str, hdr->query_type, hdr->payload_size, hdr->seq_id, delta);
178+
if (hdr->payload_size > 0) {
179+
switch (hdr->query_type) {
180+
case CMD_GENERIC:
181+
{
182+
GenericPacket *gen = (GenericPacket *) data;
183+
nlprint("[GEN] version: %x, flags: %x, service ID: %u, method ID: %u, error code: %x, ids: %x %x %x, payload_size: %x",
184+
gen->generic_cmd_header.version,
185+
gen->generic_cmd_header.flags,
186+
gen->generic_cmd_header.service_id,
187+
gen->generic_cmd_header.method_id,
188+
gen->generic_cmd_header.error_code,
189+
gen->generic_cmd_header.ids[0],
190+
gen->generic_cmd_header.ids[1],
191+
gen->generic_cmd_header.ids[2],
192+
gen->generic_cmd_header.payload_size);
193+
break;
194+
}
195+
case CMD_UVC_UAC:
196+
{
197+
// UvcUacPacket *uvc = (UvcUacPacket *) data;
198+
// nlprint("[UVC] f1: %x, unknown_0: %x, f3: %x, mic_enable: %x, mic_mute: %x, mic_volume: %x, mic_volume_2: %x, unknown_A: %x, unknown_B: %x, mic_freq: %u",
199+
// uvc->uac_uvc.f1,
200+
// uvc->uac_uvc.unknown_0,
201+
// uvc->uac_uvc.f3,
202+
// uvc->uac_uvc.mic_enable,
203+
// uvc->uac_uvc.mic_mute,
204+
// uvc->uac_uvc.mic_volume,
205+
// uvc->uac_uvc.mic_volume_2,
206+
// uvc->uac_uvc.unknown_A,
207+
// uvc->uac_uvc.unknown_B,
208+
// uvc->uac_uvc.mic_freq);
209+
print_hex(((const char *) data) + sizeof(CmdHeader), hdr->payload_size);
210+
printf("\n");
211+
break;
212+
}
213+
default:
214+
{
215+
print_hex(((const char *) data) + sizeof(CmdHeader), hdr->payload_size);
216+
printf("\n");
217+
break;
218+
}
219+
}
220+
}
221+
222+
if (hdr->packet_type == PACKET_TYPE_REQUEST || hdr->packet_type == PACKET_TYPE_RESPONSE) {
223+
CmdHeader ack = create_ack_packet(hdr);
224+
send_to_gamepad(ctx->socket, &ack, sizeof(ack));
225+
}
226+
}
227+
228+
void *listen_socket(void *arg)
229+
{
230+
listener_data_t *data = (listener_data_t *) arg;
231+
char buf[4096];
232+
233+
while (RUNNING) {
234+
ssize_t r = recv(data->socket, buf, sizeof(buf), 0);
235+
if (r <= 0) {
236+
continue;
237+
} else {
238+
if (data->handler) {
239+
data->handler(data, buf, r);
240+
}
241+
}
242+
}
243+
}
244+
245+
int main(int argc, const char **argv)
246+
{
247+
listener_data_t data[PORT_COUNT];
248+
249+
pthread_t uvc_uac_loop_thread;
250+
int uvc_uac_loop_thread_created = 0;
251+
252+
struct timeval start;
253+
gettimeofday(&start, 0);
254+
255+
// Set up defaults
256+
for (int i = 0; i < PORT_COUNT; i++) {
257+
listener_data_t *d = &data[i];
258+
d->port = PORTS[i];
259+
d->socket = -1;
260+
d->thread_started = 0;
261+
d->time = start;
262+
d->handler = default_handler;
263+
}
264+
265+
data[2].handler = hid_handler;
266+
data[4].handler = cmd_handler;
267+
268+
int ready = 1;
269+
270+
// Try to set up sockets
271+
for (int i = 0; i < PORT_COUNT; i++) {
272+
listener_data_t *d = &data[i];
273+
int port = d->port;
274+
int skt = create_socket(port);
275+
if (skt == -1) {
276+
ready = 0;
277+
break;
278+
}
279+
280+
d->socket = skt;
281+
}
282+
283+
// If all sockets set up, start listening threads
284+
if (ready) {
285+
for (int i = 0; i < PORT_COUNT; i++) {
286+
listener_data_t *d = &data[i];
287+
if (pthread_create(&d->thread, 0, listen_socket, d) == 0) {
288+
d->thread_started = 1;
289+
} else {
290+
ready = 0;
291+
break;
292+
}
293+
}
294+
}
295+
296+
if (ready) {
297+
// Start ancillary threads
298+
if (pthread_create(&uvc_uac_loop_thread, 0, send_uvc_uac_pkt_loop, &data[4]) == 0) {
299+
uvc_uac_loop_thread_created = 1;
300+
}
301+
302+
// Wait for close
303+
nlprint("Running, type 'exit' or 'quit' or 'bye' to leave");
304+
while (RUNNING) {
305+
char *d = 0;
306+
size_t n;
307+
if (getline(&d, &n, stdin) >= 0) {
308+
for (size_t i = 0; i < n; i++) {
309+
if (d[i] == '\n') {
310+
d[i] = '\0';
311+
break;
312+
}
313+
}
314+
static uint16_t seq_id = 0;
315+
if (!strcmp("exit", d) || !strcmp("quit", d) || !strcmp("bye", d)) {
316+
RUNNING = 0;
317+
} else if (!strcmp("cmd", d)) {
318+
GenericPacket pkt;
319+
pkt.cmd_header.packet_type = PACKET_TYPE_REQUEST;
320+
pkt.cmd_header.query_type = CMD_GENERIC;
321+
pkt.cmd_header.payload_size = sizeof(GenericCmdHeader);
322+
pkt.cmd_header.seq_id = seq_id;
323+
seq_id++;
324+
325+
memset(&pkt.generic_cmd_header, 0, sizeof(pkt.generic_cmd_header));
326+
pkt.generic_cmd_header.magic_0x7E = 0x7E;
327+
pkt.generic_cmd_header.version = 1;
328+
pkt.generic_cmd_header.flags = 0x40;
329+
pkt.generic_cmd_header.service_id = SERVICE_ID_PERIPHERAL;
330+
pkt.generic_cmd_header.method_id = METHOD_ID_PERIPHERAL_EEPROM;
331+
332+
nlprint("Sending generic packet (seq ID: %X)...", pkt.cmd_header.seq_id);
333+
334+
send_to_gamepad(data[4].socket, &pkt, sizeof(CmdHeader) + sizeof(GenericCmdHeader));
335+
}
336+
}
337+
free(d);
338+
}
339+
}
340+
341+
// Join all threads and close all sockets
342+
RUNNING = 0;
343+
for (int i = 0; i < PORT_COUNT; i++) {
344+
listener_data_t *d = &data[i];
345+
if (d->thread_started) {
346+
pthread_join(d->thread, 0);
347+
}
348+
349+
if (d->socket != -1) {
350+
close(d->socket);
351+
}
352+
}
353+
354+
if (uvc_uac_loop_thread_created) {
355+
pthread_join(uvc_uac_loop_thread, 0);
356+
}
357+
358+
return 0;
359+
}

lib/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ if (VANILLA_BUILD_TESTS)
3131
target_link_libraries(${TEST_NAME} PRIVATE vanilla m)
3232
target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
3333
endfunction()
34-
34+
3535
add_test(bittest "test/bittest.c")
3636
add_test(reversebittest "test/reversebit.c")
3737
add_test(reversebitstresstest "test/reversebitstresstest.c")
38-
endif()
38+
endif()

0 commit comments

Comments
 (0)