From 4999f07c1477a987fe9dbdb13fd204b7de640b16 Mon Sep 17 00:00:00 2001 From: Raphael Assenat Date: Tue, 6 Nov 2018 10:38:39 -0500 Subject: [PATCH] Add support for Gamecube keyboards --- Makefile.inc | 8 +- changelog.txt | 3 + gamecube.c | 54 ++++++ gamecube.h | 1 + gamepads.h | 7 + gc_kb.c | 93 ++++++++++ gc_kb.h | 3 + hid_keycodes.h | 110 ++++++++++++ main.c | 452 +++++++++++++++++++++++++++++++++++++++++++++++-- reportdesc.c | 18 ++ requests.h | 3 + usbpad.c | 30 ++++ usbpad.h | 3 + 13 files changed, 765 insertions(+), 20 deletions(-) create mode 100644 gc_kb.c create mode 100644 gc_kb.h create mode 100644 hid_keycodes.h diff --git a/Makefile.inc b/Makefile.inc index 988609a..1b34e86 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -1,4 +1,4 @@ -OBJS=main.o usb.o usbpad.o mappings.o gcn64_protocol.o n64.o gamecube.o usart1.o bootloader.o eeprom.o config.o hiddata.o usbstrings.o intervaltimer.o intervaltimer2.o version.o gcn64txrx0.o gcn64txrx1.o gcn64txrx2.o gcn64txrx3.o gamepads.o stkchk.o -VERSIONSTR=\"3.5.2\" -VERSIONSTR_SHORT=\"3.5\" -VERSIONBCD=0x0352 +OBJS=main.o usb.o usbpad.o mappings.o gcn64_protocol.o n64.o gamecube.o usart1.o bootloader.o eeprom.o config.o hiddata.o usbstrings.o intervaltimer.o intervaltimer2.o version.o gcn64txrx0.o gcn64txrx1.o gcn64txrx2.o gcn64txrx3.o gamepads.o stkchk.o gc_kb.o +VERSIONSTR=\"3.6.0\" +VERSIONSTR_SHORT=\"3.6\" +VERSIONBCD=0x0360 diff --git a/changelog.txt b/changelog.txt index 1d3a643..ab7f265 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +- November 6, 2018 : Version 3.6.0 + - Add gamecube keyboard support + - September 18, 2018 : Version 3.5.2 - Improve PID (force feedback) implementation - Implement reset firmware command diff --git a/gamecube.c b/gamecube.c index e9c3b59..34bdc1b 100644 --- a/gamecube.c +++ b/gamecube.c @@ -24,7 +24,9 @@ /*********** prototypes *************/ static void gamecubeInit(unsigned char chn); +static void gamecubeInitKB(unsigned char chn); static char gamecubeUpdate(unsigned char chn); +static char gamecubeUpdateKB(unsigned char chn); static char gamecubeChanged(unsigned char chn); static char gc_rumbling[GAMEPAD_MAX_CHANNELS] = { }; @@ -39,6 +41,11 @@ static void gamecubeInit(unsigned char chn) gamecubeUpdate(chn); } +static void gamecubeInitKB(unsigned char chn) +{ + gamecubeUpdateKB(chn); +} + void gc_decodeAnswer(unsigned char chn, unsigned char data[8]) { unsigned char x,y,cx,cy; @@ -115,6 +122,39 @@ void gc_decodeAnswer(unsigned char chn, unsigned char data[8]) } } +static char gamecubeUpdateKB(unsigned char chn) +{ + unsigned char tmpdata[GC_GETSTATUS_REPLY_LENGTH]; + unsigned char count; + unsigned char i, lrc; + + tmpdata[0] = GC_POLL_KB1; + tmpdata[1] = GC_POLL_KB2; + tmpdata[2] = GC_POLL_KB3; + + count = gcn64_transaction(chn, tmpdata, 3, tmpdata, GC_GETSTATUS_REPLY_LENGTH); + if (count != GC_GETSTATUS_REPLY_LENGTH) { + return 1; + } + + // Compute LRC + for (i=0, lrc=0; i<6; i++) { + lrc ^= tmpdata[i]; + } + + if (tmpdata[7] != lrc) { + return 1; // LRC error + } + + // Ok, fill the report + last_built_report[chn].pad_type = PAD_TYPE_GC_KB; + for (i=0; i<3; i++) { + last_built_report[chn].gckb.keys[i] = tmpdata[4+i]; + } + + return 0; +} + static char gamecubeUpdate(unsigned char chn) { unsigned char tmpdata[GC_GETSTATUS_REPLY_LENGTH]; @@ -181,3 +221,17 @@ Gamepad *gamecubeGetGamepad(void) { return &GamecubeGamepad; } + +Gamepad GamecubeKeyboard = { + .init = gamecubeInitKB, + .update = gamecubeUpdateKB, + .changed = gamecubeChanged, + .getReport = gamecubeGetReport, + .probe = gamecubeProbe, +}; + +Gamepad *gamecubeGetKeyboard(void) +{ + return &GamecubeKeyboard; +} + diff --git a/gamecube.h b/gamecube.h index 3949ab7..9114205 100644 --- a/gamecube.h +++ b/gamecube.h @@ -1,4 +1,5 @@ #include "gamepads.h" Gamepad *gamecubeGetGamepad(void); +Gamepad *gamecubeGetKeyboard(void); diff --git a/gamepads.h b/gamepads.h index 2b62481..07c6afb 100644 --- a/gamepads.h +++ b/gamepads.h @@ -6,6 +6,7 @@ #define PAD_TYPE_NONE 0 #define PAD_TYPE_N64 4 #define PAD_TYPE_GAMECUBE 5 +#define PAD_TYPE_GC_KB 6 #define N64_RAW_SIZE 4 #define GC_RAW_SIZE 8 @@ -69,11 +70,17 @@ typedef struct _gc_pad_data { #define GC_ALL_BUTTONS (GC_BTN_START|GC_BTN_Y|GC_BTN_X|GC_BTN_B|GC_BTN_A|GC_BTN_L|GC_BTN_R|GC_BTN_Z|GC_BTN_DPAD_UP|GC_BTN_DPAD_DOWN|GC_BTN_DPAD_RIGHT|GC_BTN_DPAD_LEFT) +typedef struct _gc_keyboard_data { + unsigned char pad_type; + unsigned char keys[3]; +} gc_keyboard_data; + typedef struct _gamepad_data { union { unsigned char pad_type; // PAD_TYPE_* n64_pad_data n64; gc_pad_data gc; + gc_keyboard_data gckb; }; } gamepad_data; diff --git a/gc_kb.c b/gc_kb.c new file mode 100644 index 0000000..a67a760 --- /dev/null +++ b/gc_kb.c @@ -0,0 +1,93 @@ +/* gc_n64_usb : Gamecube or N64 controller to USB firmware + Copyright (C) 2007-2013 Raphael Assenat + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include +#include "gc_kb.h" +#include "hid_keycodes.h" +#include "gcn64_protocol.h" + + +// http://www2d.biglobe.ne.jp/~msyk/keyboard/layout/usbkeycode.html + +static const unsigned char gc_to_hid_table[] PROGMEM = { + GC_KEY_RESERVED, HID_KB_NOEVENT, + GC_KEY_HOME, HID_KB_HOME, + GC_KEY_END, HID_KB_END, + GC_KEY_PGUP, HID_KB_PGUP, + GC_KEY_PGDN, HID_KB_PGDN, + GC_KEY_SCROLL_LOCK, HID_KB_SCROLL_LOCK, + GC_KEY_DASH_UNDERSCORE, HID_KB_DASH_UNDERSCORE, + GC_KEY_PLUS_EQUAL, HID_KB_EQUAL_PLUS, + GC_KEY_YEN, HID_KB_INTERNATIONAL3, + GC_KEY_OPEN_BRKT_BRACE, HID_KB_OPEN_BRKT_BRACE, + GC_KEY_SEMI_COLON_COLON,HID_KB_SEMI_COLON_COLON, + GC_KEY_QUOTES, HID_KB_QUOTES, + GC_KEY_CLOSE_BRKT_BRACE,HID_KB_CLOSE_BRKT_BRACE, + GC_KEY_BRACKET_MU, HID_KB_NONUS_HASH_TILDE, + GC_KEY_COMMA_ST, HID_KB_COMMA_SMALLER_THAN, + GC_KEY_PERIOD_GT, HID_KB_PERIOD_GREATER_THAN, + GC_KEY_SLASH_QUESTION, HID_KB_SLASH_QUESTION, + GC_KEY_INTERNATIONAL1, HID_KB_INTERNATIONAL1, + GC_KEY_ESC, HID_KB_ESCAPE, + GC_KEY_INSERT, HID_KB_INSERT, + GC_KEY_DELETE, HID_KB_DELETE_FORWARD, + GC_KEY_HANKAKU, HID_KB_GRAVE_ACCENT_AND_TILDE, + GC_KEY_BACKSPACE, HID_KB_BACKSPACE, + GC_KEY_TAB, HID_KB_TAB, + GC_KEY_CAPS_LOCK, HID_KB_CAPS_LOCK, + GC_KEY_MUHENKAN, HID_KB_INTERNATIONAL5, + GC_KEY_SPACE, HID_KB_SPACE, + GC_KEY_HENKAN, HID_KB_INTERNATIONAL4, + GC_KEY_KANA, HID_KB_INTERNATIONAL2, + GC_KEY_LEFT, HID_KB_LEFT_ARROW, + GC_KEY_DOWN, HID_KB_DOWN_ARROW, + GC_KEY_UP, HID_KB_UP_ARROW, + GC_KEY_RIGHT, HID_KB_RIGHT_ARROW, + GC_KEY_ENTER, HID_KB_ENTER, + + /* "shift" keys */ + GC_KEY_LEFT_SHIFT, HID_KB_LEFT_SHIFT, + GC_KEY_RIGHT_SHIFT, HID_KB_RIGHT_SHIFT, + GC_KEY_LEFT_CTRL, HID_KB_LEFT_CONTROL, + + /* This keyboard only has a left alt key. But as right alt is required to access some + * functions on japanese keyboards, I map the key to right alt. + * + * eg: RO-MAJI on the hiragana/katakana key */ + GC_KEY_LEFT_ALT, HID_KB_RIGHT_ALT, +}; + +unsigned char gcKeycodeToHID(unsigned char gc_code) +{ + int i; + + if (gc_code >= GC_KEY_A && gc_code <= GC_KEY_0) { + // Note: This works since A-Z, 1-9, 0 have consecutive keycode values. + return (gc_code - GC_KEY_A) + HID_KB_A; + } + if (gc_code >= GC_KEY_F1 && gc_code <= GC_KEY_F12) { + return (gc_code - GC_KEY_F1) + HID_KB_F1; + } + + for (i=0; ihotplug)) { + // For gamecube, this make sure the next + // analog values we read become the center + // reference. + pads[channel]->hotplug(channel); + } + } + + /* Read from the pad by calling update */ + if (pads[channel]) { + if (pads[channel]->update(channel)) { + error_count[channel]++; + if (error_count[channel] > MAX_READ_ERRORS) { + pads[channel] = NULL; + error_count[channel] = 0; + continue; + } + } else { + error_count[channel]=0; + } + + if (pads[channel]->changed(channel)) + { + pads[channel]->getReport(channel, &pad_data); + + if ((num_players == 1) && (channel == 0)) { + // single-port adapter in keyboard mode (kb in port 1) + usbpad_update_kb(&usbpads[channel], &pad_data); + } else if ((num_players == 2) && (channel == 1)) { + // dual-port adapter in keyboard mode (kb in port 2) + usbpad_update_kb(&usbpads[channel], &pad_data); + } else { + usbpad_update(&usbpads[channel], &pad_data); + } + state = STATE_WAIT_INTERRUPT_READY; + continue; + } + } else { + /* Just make sure the gamepad state holds valid data + * to appear inactive (no buttons and axes in neutral) */ + usbpad_update(&usbpads[channel], NULL); + } + } + /* If there were change on any of the gamepads, state will + * be set to STATE_WAIT_INTERRUPT_READY. Otherwise, go back + * to WAIT_POLLTIME. */ + if (state == STATE_POLL_PAD) { + state = STATE_WAIT_POLLTIME; + } + break; + + case STATE_WAIT_INTERRUPT_READY: + /* Wait until one of the interrupt endpoint is ready */ + if (usb_interruptReady_ep1() || (num_players>1 && usb_interruptReady_ep2())) { + state = STATE_TRANSMIT; + } + break; + + case STATE_TRANSMIT: + if (usb_interruptReady_ep1()) { + if (num_players == 1) { + // Single-port adapters have the keyboard in port 1 + usb_interruptSend_ep1(usbpad_getReportBuffer(&usbpads[0]), usbpad_getReportSizeKB()); + } else { + usb_interruptSend_ep1(usbpad_getReportBuffer(&usbpads[0]), usbpad_getReportSize()); + } + } + // Keyboard is always in second port on dual port adapters + if (num_players>1 && usb_interruptReady_ep2()) { + usb_interruptSend_ep2(usbpad_getReportBuffer(&usbpads[1]), usbpad_getReportSizeKB()); + } + state = STATE_WAIT_POLLTIME; + break; + + } + + for (channel=0; channel < num_players; channel++) { + gamepad_vibrate = usbpad_mustVibrate(&usbpads[channel]); + if (last_v[channel] != gamepad_vibrate) { + if (pads[channel] && pads[channel]->setVibration) { + pads[channel]->setVibration(channel, gamepad_vibrate); + } + last_v[channel] = gamepad_vibrate; + } + } + } + + return 0; +} + diff --git a/reportdesc.c b/reportdesc.c index 83edf24..ea45ece 100644 --- a/reportdesc.c +++ b/reportdesc.c @@ -645,3 +645,21 @@ const uint8_t gcn64_usbHidReportDescriptor[] PROGMEM = { 0xC0, // End Collection 0xC0, // End Collection }; + +static const unsigned char gcKeyboardReport[] PROGMEM = { + 0x05, 0x01, // Usage page : Generic Desktop + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection (Application) + 0x05, 0x07, // Usage Page (Key Codes) + + 0x95, 0x03, // Report Count(3) + 0x75, 0x08, // Report Size(8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0xE7, // Logical maximum (231) + + 0x19, 0x00, // Usage Minimum(0) + 0x29, 0xE7, // Usage Maximum(231) + 0x81, 0x00, // Input (Data, Array) + + 0xc0, // END_COLLECTION +}; diff --git a/requests.h b/requests.h index 6b29747..db0eb35 100644 --- a/requests.h +++ b/requests.h @@ -29,6 +29,9 @@ #define CFG_MODE_2P_STANDARD 0x10 #define CFG_MODE_2P_N64_ONLY 0x11 #define CFG_MODE_2P_GC_ONLY 0x12 +#define CFG_MODE_KEYBOARD 0x13 +#define CFG_MODE_KB_AND_JS 0x14 +#define CFG_MODE_KEYBOARD_2 0x15 #define CFG_PARAM_SERIAL 0x01 diff --git a/usbpad.c b/usbpad.c index ae50336..f71bb0d 100644 --- a/usbpad.c +++ b/usbpad.c @@ -24,6 +24,8 @@ #include "mappings.h" #include "eeprom.h" #include "config.h" +#include "hid_keycodes.h" +#include "gc_kb.h" #define REPORT_ID 1 @@ -112,6 +114,18 @@ static void buildIdleReport(unsigned char dstbuf[USBPAD_REPORT_SIZE]) dstbuf[14] = 0; } +int usbpad_getReportSizeKB(void) +{ + return 3; +} + +static void buildIdleReportKB(unsigned char dstbuf[USBPAD_REPORT_SIZE]) +{ + dstbuf[0] = HID_KB_NOEVENT; + dstbuf[1] = HID_KB_NOEVENT; + dstbuf[2] = HID_KB_NOEVENT; +} + static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[USBPAD_REPORT_SIZE]) { int16_t xval,yval,cxval,cyval,ltrig,rtrig; @@ -249,6 +263,22 @@ void usbpad_update(struct usbpad *pad, const gamepad_data *pad_data) } } +void usbpad_update_kb(struct usbpad *pad, const gamepad_data *pad_data) +{ + unsigned char i; + + /* Always start with an idle report. Specific report builders can just + * simply ignore unused parts */ + buildIdleReportKB(pad->gamepad_report0); + + if (pad_data->pad_type == PAD_TYPE_GC_KB) { + for (i=0; i<3; i++) { + pad->gamepad_report0[i] = gcKeycodeToHID(pad_data->gckb.keys[i]); + } + } +} + + void usbpad_forceVibrate(struct usbpad *pad, char force) { pad->force_vibrate = force; diff --git a/usbpad.h b/usbpad.h index 10d93ac..af569f3 100644 --- a/usbpad.h +++ b/usbpad.h @@ -36,4 +36,7 @@ uint16_t usbpad_hid_get_report(struct usbpad *pad, struct usb_request *rq, const // For mappings. ID starts at 0. #define USB_BTN(id) (0x0001 << (id)) +int usbpad_getReportSizeKB(void); +void usbpad_update_kb(struct usbpad *pad, const gamepad_data *pad_data); + #endif // USBPAD_H__