diff --git a/Makefile b/Makefile index 90124567f4..d9543d71f2 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ RENDER_API ?= GL WINDOW_API ?= SDL2 # Audio backends: SDL1, SDL2 AUDIO_API ?= SDL2 -# Controller backends (can have multiple, space separated): SDL2, SDL1 +# Controller backends (can have multiple, space separated): SDL2, SDL1, RAPHNET CONTROLLER_API ?= SDL2 # Misc settings for EXTERNAL_DATA @@ -316,6 +316,10 @@ LEVEL_DIRS := $(patsubst levels/%,%,$(dir $(wildcard levels/*/header.h))) SRC_DIRS := src src/engine src/game src/audio src/menu src/buffers actors levels bin data assets src/pc src/pc/gfx src/pc/audio src/pc/controller src/pc/fs src/pc/fs/packtypes ASM_DIRS := +ifneq (,$(findstring RAPHNET,${CONTROLLER_API})) + SRC_DIRS += src/pc/controller/raphnet +endif + ifeq ($(DISCORDRPC),1) SRC_DIRS += src/pc/discord endif @@ -571,6 +575,10 @@ ifneq ($(SDL1_USED)$(SDL2_USED),00) endif endif +ifneq (,$(findstring RAPHNET,${CONTROLLER_API})) + BACKEND_LDFLAGS += -lhidapi +endif + ifeq ($(WINDOWS_BUILD),1) CC_CHECK := $(CC) -fsyntax-only -fsigned-char $(BACKEND_CFLAGS) $(INCLUDE_CFLAGS) -Wall -Wextra -Wno-format-security $(VERSION_CFLAGS) $(GRUCODE_CFLAGS) CFLAGS := $(OPT_FLAGS) $(INCLUDE_CFLAGS) $(BACKEND_CFLAGS) $(VERSION_CFLAGS) $(GRUCODE_CFLAGS) -fno-strict-aliasing -fwrapv diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index 34ca9ca05a..c150172228 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -7,12 +7,16 @@ #include "controller_recorded_tas.h" #include "controller_keyboard.h" #include "controller_sdl.h" +#include "controller_raphnet.h" // Analog camera movement by Pathétique (github.com/vrmiguel), y0shin and Mors // Contribute or communicate bugs at github.com/vrmiguel/sm64-analog-camera static struct ControllerAPI *controller_implementations[] = { &controller_recorded_tas, + #if defined(CAPI_RAPHNET) + &controller_raphnet, + #endif #if defined(CAPI_SDL2) || defined(CAPI_SDL1) &controller_sdl, #endif diff --git a/src/pc/controller/controller_raphnet.c b/src/pc/controller/controller_raphnet.c new file mode 100644 index 0000000000..82fb02ad31 --- /dev/null +++ b/src/pc/controller/controller_raphnet.c @@ -0,0 +1,121 @@ +#ifdef CAPI_RAPHNET + +#include +#include + +#include "controller_api.h" +#include "raphnet/plugin_back.h" +#include "lib/src/osContInternal.h" +#include "macros.h" + +static int l_PluginInit = 0; +static int n_controllers = 0; + +static u32 pifRam[16]; + +static void DebugMessage(int level, const char *message, ...) { + switch (level) { + case PB_MSG_ERROR: + printf("Raphnet: [ERROR] "); + break; + case PB_MSG_WARNING: + printf("Raphnet: [WARNING] "); + break; + case PB_MSG_INFO: + printf("Raphnet: [INFO] "); + break; + case PB_MSG_STATUS: + printf("Raphnet: [STATUS] "); + break; + case PB_MSG_VERBOSE: + printf("Raphnet: [VERBOSE] "); + break; + default: + printf("Raphnet: "); + break; + } + + va_list args; + va_start(args, message); + vprintf(message, args); + va_end(args); +} + +static void controller_raphnet_init() { + if (l_PluginInit) { + return; + } + l_PluginInit = 1; + + pb_init(DebugMessage); + + n_controllers = pb_scanControllers(); + if (n_controllers <= 0) { + DebugMessage(PB_MSG_ERROR, "No adapters detected\n"); + return; + } + + pb_romOpen(); +} + +static void controller_raphnet_read(OSContPad *pad) { + if (n_controllers <= 0) { + return; + } + + u8 *cmdBufPtr; + OSContPackedRequest request; + s32 i; + cmdBufPtr = (u8 *) pifRam; + + for (i = 0; i < 16; i++) { + pifRam[i] = 0; + } + + request.padOrEnd = 0xff; + request.txLen = 1; + request.rxLen = 4; + request.command = 1; + request.data1 = 0xff; + request.data2 = 0xff; + request.data3 = 0xff; + request.data4 = 0xff; + for (i = 0; i < 4; i++) { + * (OSContPackedRequest *) cmdBufPtr = request; + cmdBufPtr += sizeof(OSContPackedRequest); + } + *cmdBufPtr = 0xfe; + + pb_readController(0, (u8 *) pifRam + 1); + pb_readController(-1, NULL); + + OSContPackedRead response = * (OSContPackedRead *) pifRam; + pad->errnum = (response.rxLen & 0xc0) >> 4; + if (pad->errnum == 0) { + pad->button = BE_TO_HOST16(response.button); + pad->stick_x = response.rawStickX; + pad->stick_y = response.rawStickY; + } +} + +static u32 controller_raphnet_rawkey() { + return VK_INVALID; +} + +static void controller_raphnet_shutdown() { + pb_romClosed(); + pb_shutdown(); +} + +struct ControllerAPI controller_raphnet = { + VK_INVALID, + controller_raphnet_init, + controller_raphnet_read, + controller_raphnet_rawkey, + NULL, + NULL, + NULL, + controller_raphnet_shutdown, +}; + +#endif // CAPI_RAPHNET \ No newline at end of file diff --git a/src/pc/controller/controller_raphnet.h b/src/pc/controller/controller_raphnet.h new file mode 100644 index 0000000000..f933b81448 --- /dev/null +++ b/src/pc/controller/controller_raphnet.h @@ -0,0 +1,8 @@ +#ifndef CONTROLLER_RAPHNET_H +#define CONTROLLER_RAPHNET_H + +#include "controller_api.h" + +extern struct ControllerAPI controller_raphnet; + +#endif \ No newline at end of file diff --git a/src/pc/controller/raphnet/gcn64.c b/src/pc/controller/raphnet/gcn64.c new file mode 100644 index 0000000000..cc233ac147 --- /dev/null +++ b/src/pc/controller/raphnet/gcn64.c @@ -0,0 +1,371 @@ +/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware + Copyright (C) 2007-2017 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 +#include +#include +#include "gcn64.h" +#include "gcn64_priv.h" +#include "gcn64lib.h" +#include "requests.h" + +#include + +static int dusbr_verbose = 0; + +#define IS_VERBOSE() (dusbr_verbose) + +struct supported_adapter { + uint16_t vid, pid; + int if_number; + struct gcn64_adapter_caps caps; +}; + +static struct supported_adapter supported_adapters[] = { + /* vid, pid, if_no, { n_raw, bio_support } */ + + { OUR_VENDOR_ID, 0x0017, 1, { 1, 0 } }, // GC/N64 USB v3.0, 3.1.0, 3.1.1 + { OUR_VENDOR_ID, 0x001D, 1, { 1, 0 } }, // GC/N64 USB v3.2.0 ... v3.3.x + { OUR_VENDOR_ID, 0x0020, 1, { 1, 0 } }, // GCN64->USB v3.2.1 (N64 mode) + { OUR_VENDOR_ID, 0x0021, 1, { 1, 0 } }, // GCN64->USB v3.2.1 (GC mode) + { OUR_VENDOR_ID, 0x0022, 1, { 2, 0 } }, // GCN64->USB v3.3.x (2x GC/N64 mode) + { OUR_VENDOR_ID, 0x0030, 1, { 2, 0 } }, // GCN64->USB v3.3.0 (2x N64-only mode) + { OUR_VENDOR_ID, 0x0031, 1, { 2, 0 } }, // GCN64->USB v3.3.0 (2x GC-only mode) + + { OUR_VENDOR_ID, 0x0032, 1, { 1, 1 } }, // GC/N64 USB v3.4.x (GC/N64 mode) + { OUR_VENDOR_ID, 0x0033, 1, { 1, 1 } }, // GC/N64 USB v3.4.x (N64 mode) + { OUR_VENDOR_ID, 0x0034, 1, { 1, 1 } }, // GC/N64 USB v3.4.x (GC mode) + { OUR_VENDOR_ID, 0x0035, 1, { 2, 1 } }, // GC/N64 USB v3.4.x (2x GC/N64 mode) + { OUR_VENDOR_ID, 0x0036, 1, { 2, 1 } }, // GC/N64 USB v3.4.x (2x N64-only mode) + { OUR_VENDOR_ID, 0x0037, 1, { 2, 1 } }, // GC/N64 USB v3.4.x (2x GC-only mode) + + // GC/N64 USB v3.5.x flavours + { OUR_VENDOR_ID, 0x0038, 1, { 1, 1 } }, // (GC/N64 mode) + { OUR_VENDOR_ID, 0x0039, 1, { 1, 1 } }, // (N64 mode) + { OUR_VENDOR_ID, 0x003A, 1, { 1, 1 } }, // (GC mode) + { OUR_VENDOR_ID, 0x003B, 2, { 2, 1 } }, // (2x GC/N64 mode) + { OUR_VENDOR_ID, 0x003C, 2, { 2, 1 } }, // (2x N64-only mode) + { OUR_VENDOR_ID, 0x003D, 2, { 2, 1 } }, // (2x GC-only mode) + + // GC/N64 USB v3.6.x flavours + { OUR_VENDOR_ID, 0x0060, 1, { 1, 1 } }, // (GC/N64 mode) + { OUR_VENDOR_ID, 0x0061, 1, { 1, 1 } }, // (N64 mode) + { OUR_VENDOR_ID, 0x0063, 2, { 2, 1 } }, // (2x GC/N64 mode) + { OUR_VENDOR_ID, 0x0064, 2, { 2, 1 } }, // (2x N64-only mode) + { OUR_VENDOR_ID, 0x0067, 1, { 1, 1 } }, // (GC/N64 in GC keyboard mode) + + { }, // terminator +}; + +int gcn64_init(int verbose) +{ + dusbr_verbose = verbose; + hid_init(); + return 0; +} + +void gcn64_shutdown(void) +{ + hid_exit(); +} + +static char isProductIdHandled(unsigned short pid, int interface_number, struct gcn64_adapter_caps *caps) +{ + int i; + + for (i=0; supported_adapters[i].vid; i++) { + if (pid == supported_adapters[i].pid) { + if (interface_number == supported_adapters[i].if_number) { + if (caps) { + memcpy(caps, &supported_adapters[i].caps, sizeof (struct gcn64_adapter_caps)); + } + return 1; + } + } + } + + return 0; +} + +struct gcn64_list_ctx *gcn64_allocListCtx(void) +{ + struct gcn64_list_ctx *ctx; + ctx = calloc(1, sizeof(struct gcn64_list_ctx)); + return ctx; +} + +void gcn64_freeListCtx(struct gcn64_list_ctx *ctx) +{ + if (ctx) { + if (ctx->devs) { + hid_free_enumeration(ctx->devs); + } + free(ctx); + } +} + +int gcn64_countDevices(void) +{ + struct gcn64_list_ctx *ctx; + struct gcn64_info inf; + int count = 0; + + ctx = gcn64_allocListCtx(); + while (gcn64_listDevices(&inf, ctx)) { + count++; + } + gcn64_freeListCtx(ctx); + + return count; +} + +/** + * \brief List instances of our rgbleds device on the USB busses. + * \param info Pointer to gcn64_info structure to store data + * \param dst Destination buffer for device serial number/id. + * \param dstbuf_size Destination buffer size. + */ +struct gcn64_info *gcn64_listDevices(struct gcn64_info *info, struct gcn64_list_ctx *ctx) +{ + struct gcn64_adapter_caps caps; + + memset(info, 0, sizeof(struct gcn64_info)); + + if (!ctx) { + fprintf(stderr, "gcn64_listDevices: Passed null context\n"); + return NULL; + } + + if (ctx->devs) + goto jumpin; + + if (IS_VERBOSE()) { + printf("Start listing\n"); + } + + ctx->devs = hid_enumerate(OUR_VENDOR_ID, 0x0000); + if (!ctx->devs) { + printf("Hid enumerate returned NULL\n"); + return NULL; + } + + for (ctx->cur_dev = ctx->devs; ctx->cur_dev; ctx->cur_dev = ctx->cur_dev->next) + { + if (IS_VERBOSE()) { + printf("Considering 0x%04x:0x%04x\n", ctx->cur_dev->vendor_id, ctx->cur_dev->product_id); + } + if (isProductIdHandled(ctx->cur_dev->product_id, ctx->cur_dev->interface_number, &caps)) + { + info->usb_vid = ctx->cur_dev->vendor_id; + info->usb_pid = ctx->cur_dev->product_id; + wcsncpy(info->str_prodname, ctx->cur_dev->product_string, PRODNAME_MAXCHARS-1); + wcsncpy(info->str_serial, ctx->cur_dev->serial_number, SERIAL_MAXCHARS-1); + strncpy(info->str_path, ctx->cur_dev->path, PATH_MAXCHARS-1); + memcpy(&info->caps, &caps, sizeof(info->caps)); + return info; + } + + jumpin: + // prevent 'error: label at end of compound statement' + continue; + } + + return NULL; +} + +gcn64_hdl_t gcn64_openDevice(struct gcn64_info *dev) +{ + hid_device *hdev; + gcn64_hdl_t hdl; + + if (!dev) + return NULL; + + if (IS_VERBOSE()) { + printf("Opening device path: '%s'\n", dev->str_path); + } + + hdev = hid_open_path(dev->str_path); + if (!hdev) { + return NULL; + } + + hdl = malloc(sizeof(struct _gcn64_hdl_t)); + if (!hdl) { + perror("malloc"); + hid_close(hdev); + return NULL; + } + hdl->hdev = hdev; + hdl->report_size = 63; + memcpy(&hdl->caps, &dev->caps, sizeof(hdl->caps)); + + if (!dev->caps.bio_support) { + printf("Pre-3.4 version detected. Setting report size to 40 bytes\n"); + hdl->report_size = 40; + } + + return hdl; +} + +gcn64_hdl_t gcn64_openBy(struct gcn64_info *dev, unsigned char flags) +{ + struct gcn64_list_ctx *ctx; + struct gcn64_info inf; + gcn64_hdl_t h; + + if (IS_VERBOSE()) + printf("gcn64_openBy, flags=0x%02x\n", flags); + + ctx = gcn64_allocListCtx(); + if (!ctx) + return NULL; + + while (gcn64_listDevices(&inf, ctx)) { + if (IS_VERBOSE()) + printf("Considering '%s'\n", inf.str_path); + + if (flags & GCN64_FLG_OPEN_BY_SERIAL) { + if (wcscmp(inf.str_serial, dev->str_serial)) + continue; + } + + if (flags & GCN64_FLG_OPEN_BY_PATH) { + if (strcmp(inf.str_path, dev->str_path)) + continue; + } + + if (flags & GCN64_FLG_OPEN_BY_VID) { + if (inf.usb_vid != dev->usb_vid) + continue; + } + + if (flags & GCN64_FLG_OPEN_BY_PID) { + if (inf.usb_pid != dev->usb_pid) + continue; + } + + if (IS_VERBOSE()) + printf("Found device. opening...\n"); + + h = gcn64_openDevice(&inf); + gcn64_freeListCtx(ctx); + return h; + } + + gcn64_freeListCtx(ctx); + return NULL; +} + +void gcn64_closeDevice(gcn64_hdl_t hdl) +{ + hid_device *hdev = hdl->hdev; + + if (hdev) { + hid_close(hdev); + } + + free(hdl); +} + +int gcn64_send_cmd(gcn64_hdl_t hdl, const unsigned char *cmd, int cmdlen) +{ + hid_device *hdev = hdl->hdev; + unsigned char buffer[hdl->report_size+1]; + int n; + + if (cmdlen > (sizeof(buffer)-1)) { + fprintf(stderr, "Error: Command too long\n"); + return -1; + } + + memset(buffer, 0, sizeof(buffer)); + + buffer[0] = 0x00; // report ID set to 0 (device has only one) + memcpy(buffer + 1, cmd, cmdlen); + + n = hid_send_feature_report(hdev, buffer, sizeof(buffer)); + if (n < 0) { + fprintf(stderr, "Could not send feature report (%ls)\n", hid_error(hdev)); + return -1; + } + + return 0; +} + +int gcn64_poll_result(gcn64_hdl_t hdl, unsigned char *cmd, int cmd_maxlen) +{ + hid_device *hdev = hdl->hdev; + unsigned char buffer[hdl->report_size+1]; + int res_len; + int n; + + memset(buffer, 0, sizeof(buffer)); + buffer[0] = 0x00; // report ID set to 0 (device has only one) + + n = hid_get_feature_report(hdev, buffer, sizeof(buffer)); + if (n < 0) { + fprintf(stderr, "Could not send feature report (%ls)\n", hid_error(hdev)); + return -1; + } + if (n==0) { + return 0; + } + res_len = n-1; + + if (res_len>0) { + int copy_len; + + copy_len = res_len; + if (copy_len > cmd_maxlen) { + copy_len = cmd_maxlen; + } + if (cmd) { + memcpy(cmd, buffer+1, copy_len); + } + } + + return res_len; +} + +int gcn64_exchange(gcn64_hdl_t hdl, unsigned char *outcmd, int outlen, unsigned char *result, int result_max) +{ + int n; + + n = gcn64_send_cmd(hdl, outcmd, outlen); + if (n<0) { + fprintf(stderr, "Error sending command\n"); + return -1; + } + + /* Answer to the command comes later. For now, this is polled, but in + * the future an interrupt-in transfer could be used. */ + do { + n = gcn64_poll_result(hdl, result, result_max); + if (n < 0) { + fprintf(stderr, "Error\r\n"); + break; + } + if (n==0) { +// printf("."); fflush(stdout); + } + + } while (n==0); + + return n; +} + diff --git a/src/pc/controller/raphnet/gcn64.h b/src/pc/controller/raphnet/gcn64.h new file mode 100644 index 0000000000..460cb24596 --- /dev/null +++ b/src/pc/controller/raphnet/gcn64.h @@ -0,0 +1,59 @@ +#ifndef _gcn64_h__ +#define _gcn64_h__ + +#include + +#define OUR_VENDOR_ID 0x289b +#define PRODNAME_MAXCHARS 256 +#define SERIAL_MAXCHARS 256 +#define PATH_MAXCHARS 256 + +struct gcn64_adapter_caps { + int n_raw_channels; + int bio_support; +}; + +struct gcn64_info { + wchar_t str_prodname[PRODNAME_MAXCHARS]; + wchar_t str_serial[SERIAL_MAXCHARS]; + char str_path[PATH_MAXCHARS]; + int usb_vid, usb_pid; + int access; // True unless direct access to read serial/prodname failed due to permissions. + struct gcn64_adapter_caps caps; +}; + +struct gcn64_list_ctx; + +typedef struct _gcn64_hdl_t *gcn64_hdl_t; // Cast from hid_device + +int gcn64_init(int verbose); +void gcn64_shutdown(void); + +struct gcn64_list_ctx *gcn64_allocListCtx(void); +void gcn64_freeListCtx(struct gcn64_list_ctx *ctx); +struct gcn64_info *gcn64_listDevices(struct gcn64_info *info, struct gcn64_list_ctx *ctx); +int gcn64_countDevices(void); + +gcn64_hdl_t gcn64_openDevice(struct gcn64_info *dev); + +#define GCN64_FLG_OPEN_BY_SERIAL 1 /** Serial must match */ +#define GCN64_FLG_OPEN_BY_PATH 2 /** Path must match */ +#define GCN64_FLG_OPEN_BY_VID 4 /** USB VID must match */ +#define GCN64_FLG_OPEN_BY_PID 8 /** USB PID MUST match */ +/** + * \brief Open a device matching a serial number + * \param dev The device structure + * \param flags Flags + * \return A handle to the opened device, or NULL if not found + **/ +gcn64_hdl_t gcn64_openBy(struct gcn64_info *dev, unsigned char flags); + +void gcn64_closeDevice(gcn64_hdl_t hdl); + +int gcn64_send_cmd(gcn64_hdl_t hdl, const unsigned char *cmd, int len); +int gcn64_poll_result(gcn64_hdl_t hdl, unsigned char *cmd, int cmdlen); + +int gcn64_exchange(gcn64_hdl_t hdl, unsigned char *outcmd, int outlen, unsigned char *result, int result_max); + +#endif // _gcn64_h__ + diff --git a/src/pc/controller/raphnet/gcn64_priv.h b/src/pc/controller/raphnet/gcn64_priv.h new file mode 100644 index 0000000000..9269514d41 --- /dev/null +++ b/src/pc/controller/raphnet/gcn64_priv.h @@ -0,0 +1,17 @@ +#ifndef _gcn64_priv_h__ +#define _gcn64_priv_h__ + +#include +#include "gcn64.h" + +struct gcn64_list_ctx { + struct hid_device_info *devs, *cur_dev; +}; + +typedef struct _gcn64_hdl_t { + hid_device *hdev; + int report_size; + struct gcn64_adapter_caps caps; +} *gcn64_hdl_t; + +#endif diff --git a/src/pc/controller/raphnet/gcn64_protocol.h b/src/pc/controller/raphnet/gcn64_protocol.h new file mode 100644 index 0000000000..0c3ba244d0 --- /dev/null +++ b/src/pc/controller/raphnet/gcn64_protocol.h @@ -0,0 +1,166 @@ +#ifndef _gcn64_protocol_h__ +#define _gcn64_protocol_h__ + +#define CONTROLLER_IS_ABSENT 0 +#define CONTROLLER_IS_N64 1 +#define CONTROLLER_IS_GC 2 +#define CONTROLLER_IS_GC_KEYBOARD 3 +#define CONTROLLER_IS_UNKNOWN 4 + + +/* Return many unknown bits, but two are about the expansion port. */ +#define N64_GET_CAPABILITIES 0x00 +#define N64_RESET 0xFF +#define N64_CAPS_REPLY_LENGTH 3 + +#define OFFSET_EXT_REMOVED 22 +#define OFFSET_EXT_PRESENT 23 + +/* Returns button states and axis values */ +#define N64_GET_STATUS 0x01 +#define N64_GET_STATUS_REPLY_LENGTH 4 + +/* Read from the expansion bus. */ +#define N64_EXPANSION_READ 0x02 + +/* Write to the expansion bus. */ +#define N64_EXPANSION_WRITE 0x03 + +/* Return information about controller. */ +#define GC_GETID 0x00 +#define GC_GETID_REPLY_LENGTH 3 + +/* 3-byte get status command. Returns axis and buttons. Also + * controls motor. */ +#define GC_GETSTATUS1 0x40 +#define GC_GETSTATUS2 0x03 +#define GC_GETSTATUS3(rumbling) ((rumbling) ? 0x01 : 0x00) +#define GC_GETSTATUS_REPLY_LENGTH 8 + +/* 3-byte poll keyboard command. + * Source: http://hitmen.c02.at/files/yagcd/yagcd/chap9.html#sec9.3.3 + * */ +#define GC_POLL_KB1 0x54 +#define GC_POLL_KB2 0x00 +#define GC_POLL_KB3 0x00 + +/* Gamecube keycodes are from table 9.3.2: + * http://hitmen.c02.at/files/yagcd/yagcd/chap9.html#sec9.3.2 + * + * But the table appears to have been built using a PC keyboard + * converted (Tototek). + * + * I was working with an ASCII keyboard so I made a few adjustments + * to reflect the /real/ key functions. For instance: + * + * LWIN, RWIN and MENU are in fact the Henkan, muhenkan and katakana/hiragana keys. + * They are equivalemnt to the HID keys International 4, International 5 and International 2. + * The - key (code 0x3F) is also HID international 1. + * The PrntScrn key (code 0x36) is in fact the Yen key (International 3). + * The bracket/brace [{ and }] keys are at different places on this Japanese keyboard, + * but following standard PC practice, some keycodes are named after the US usage. Hence, + * the aforementioned keys are the 2 keys right of P, even if they are not labeled. + */ +#define GC_KEY_RESERVED 0x00 // No key + +#define GC_KEY_HOME 0x06 +#define GC_KEY_END 0x07 +#define GC_KEY_PGUP 0x08 +#define GC_KEY_PGDN 0x09 +#define GC_KEY_SCROLL_LOCK 0x0A + +#define GC_KEY_A 0x10 +#define GC_KEY_B 0x11 +#define GC_KEY_C 0x12 +#define GC_KEY_D 0x13 +#define GC_KEY_E 0x14 +#define GC_KEY_F 0x15 +#define GC_KEY_G 0x16 +#define GC_KEY_H 0x17 +#define GC_KEY_I 0x18 +#define GC_KEY_J 0x19 +#define GC_KEY_K 0x1A +#define GC_KEY_L 0x1B +#define GC_KEY_M 0x1C +#define GC_KEY_N 0x1D +#define GC_KEY_O 0x1E +#define GC_KEY_P 0x1F +#define GC_KEY_Q 0x20 +#define GC_KEY_R 0x21 +#define GC_KEY_S 0x22 +#define GC_KEY_T 0x23 +#define GC_KEY_U 0x24 +#define GC_KEY_V 0x25 +#define GC_KEY_W 0x26 +#define GC_KEY_X 0x27 +#define GC_KEY_Y 0x28 +#define GC_KEY_Z 0x29 +#define GC_KEY_1 0x2A +#define GC_KEY_2 0x2B +#define GC_KEY_3 0x2C +#define GC_KEY_4 0x2D +#define GC_KEY_5 0x2E +#define GC_KEY_6 0x2F +#define GC_KEY_7 0x30 +#define GC_KEY_8 0x31 +#define GC_KEY_9 0x32 +#define GC_KEY_0 0x33 +#define GC_KEY_DASH_UNDERSCORE 0x34 // Key next to 0 +#define GC_KEY_PLUS_EQUAL 0x35 +#define GC_KEY_YEN 0x36 // Yen on ascii keyboard. HID International 3. +#define GC_KEY_OPEN_BRKT_BRACE 0x37 // 1st key right of P +#define GC_KEY_CLOSE_BRKT_BRACE 0x38 // 2nd key right of P +#define GC_KEY_SEMI_COLON_COLON 0x39 // ;: +#define GC_KEY_QUOTES 0x3A // '" + +// The 3rd key after 'L'. HID Keyboard non-us # and ~ +#define GC_KEY_BRACKET_MU 0x3B +#define GC_KEY_COMMA_ST 0x3C // ,< +#define GC_KEY_PERIOD_GT 0x3D // .> +#define GC_KEY_SLASH_QUESTION 0x3E // /? + +// (The extra key before right-shift on japanese keyboards. +// HID code International 1 [HID usage tables Footnote 15-20]). +#define GC_KEY_INTERNATIONAL1 0x3F +#define GC_KEY_F1 0x40 +#define GC_KEY_F2 0x41 +#define GC_KEY_F3 0x42 +#define GC_KEY_F4 0x43 +#define GC_KEY_F5 0x44 +#define GC_KEY_F6 0x45 +#define GC_KEY_F7 0x46 +#define GC_KEY_F8 0x47 +#define GC_KEY_F9 0x48 +#define GC_KEY_F10 0x49 +#define GC_KEY_F11 0x4A +#define GC_KEY_F12 0x4B +#define GC_KEY_ESC 0x4C +#define GC_KEY_INSERT 0x4D +#define GC_KEY_DELETE 0x4E + +// (Hankaku/zenkaku/kanji button). Also known as `~ +#define GC_KEY_HANKAKU 0x4F +#define GC_KEY_BACKSPACE 0x50 +#define GC_KEY_TAB 0x51 + +#define GC_KEY_CAPS_LOCK 0x53 +#define GC_KEY_LEFT_SHIFT 0x54 +#define GC_KEY_RIGHT_SHIFT 0x55 +#define GC_KEY_LEFT_CTRL 0x56 +#define GC_KEY_LEFT_ALT 0x57 +#define GC_KEY_MUHENKAN 0x58 // HID international 5 +#define GC_KEY_SPACE 0x59 +#define GC_KEY_HENKAN 0x5A // HID international 4 +#define GC_KEY_KANA 0x5B // HID international 2 +#define GC_KEY_LEFT 0x5C +#define GC_KEY_DOWN 0x5D +#define GC_KEY_UP 0x5E +#define GC_KEY_RIGHT 0x5F + +#define GC_KEY_ENTER 0x61 + +void gcn64protocol_hwinit(void); +int gcn64_detectController(void); +unsigned char gcn64_transaction(const unsigned char *tx, int tx_len, unsigned char *rx, unsigned char rx_max); + +#endif // _gcn64_protocol_h__ diff --git a/src/pc/controller/raphnet/gcn64lib.c b/src/pc/controller/raphnet/gcn64lib.c new file mode 100644 index 0000000000..445d546adf --- /dev/null +++ b/src/pc/controller/raphnet/gcn64lib.c @@ -0,0 +1,444 @@ +/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware + Copyright (C) 2007-2015 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 +#include "gcn64lib.h" +#include "gcn64_priv.h" +#include "requests.h" +#include "gcn64_protocol.h" +#include "hexdump.h" + +int gcn64lib_getConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *rx, unsigned char rx_max) +{ + unsigned char cmd[2]; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = RQ_GCN64_GET_CONFIG_PARAM; + cmd[1] = param; + + n = gcn64_exchange(hdl, cmd, 2, rx, rx_max); + if (n<2) + return n; + + n -= 2; + + // Drop the leading CMD and PARAM + if (n) { + memmove(rx, rx+2, n); + } + + return n; +} +int gcn64lib_setConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *data, unsigned char len) +{ + unsigned char cmd[2 + len]; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = RQ_GCN64_SET_CONFIG_PARAM; + cmd[1] = param; + memcpy(cmd + 2, data, len); + + n = gcn64_exchange(hdl, cmd, 2 + len, cmd, sizeof(cmd)); + if (n<0) + return n; + + return 0; +} + +int gcn64lib_suspendPolling(gcn64_hdl_t hdl, unsigned char suspend) +{ + unsigned char cmd[2]; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = RQ_GCN64_SUSPEND_POLLING; + cmd[1] = suspend; + + n = gcn64_exchange(hdl, cmd, 2, cmd, sizeof(cmd)); + if (n<0) + return n; + + return 0; +} + +int gcn64lib_getVersion(gcn64_hdl_t hdl, char *dst, int dstmax) +{ + unsigned char cmd[32]; + int n; + + if (!hdl) { + return -1; + } + + if (dstmax <= 0) + return -1; + + cmd[0] = RQ_GCN64_GET_VERSION; + + n = gcn64_exchange(hdl, cmd, 1, cmd, sizeof(cmd)); + if (n<0) + return n; + + dst[0] = 0; + if (n > 1) { + strncpy(dst, (char*)cmd+1, n); + } + dst[dstmax-1] = 0; + + return 0; +} + +int gcn64lib_getControllerType(gcn64_hdl_t hdl, int chn) +{ + unsigned char cmd[32]; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = RQ_GCN64_GET_CONTROLLER_TYPE; + cmd[1] = chn; + + n = gcn64_exchange(hdl, cmd, 2, cmd, sizeof(cmd)); + if (n<0) + return n; + if (n<3) + return -1; + + return cmd[2]; +} + +const char *gcn64lib_controllerName(int type) +{ + switch(type) { + case CTL_TYPE_NONE: return "No controller"; + case CTL_TYPE_N64: return "N64 Controller"; + case CTL_TYPE_GC: return "GC Controller"; + case CTL_TYPE_GCKB: return "GC Keyboard"; + default: + return "Unknown"; + } +} + +int gcn64lib_getSignature(gcn64_hdl_t hdl, char *dst, int dstmax) +{ + unsigned char cmd[40]; + int n; + + if (!hdl) { + return -1; + } + + if (dstmax <= 0) + return -1; + + cmd[0] = RQ_GCN64_GET_SIGNATURE; + + n = gcn64_exchange(hdl, cmd, 1, cmd, sizeof(cmd)); + if (n<0) + return n; + + dst[0] = 0; + if (n > 1) { + strncpy(dst, (char*)cmd+1, n); + } + dst[dstmax-1] = 0; + + return 0; +} + +int gcn64lib_forceVibration(gcn64_hdl_t hdl, unsigned char channel, unsigned char vibrate) +{ + unsigned char cmd[3]; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = RQ_GCN64_SET_VIBRATION; + cmd[1] = channel; + cmd[2] = vibrate; + + n = gcn64_exchange(hdl, cmd, 3, cmd, sizeof(cmd)); + if (n<0) + return n; + + return 0; +} + +int gcn64lib_rawSiCommand(gcn64_hdl_t hdl, unsigned char channel, unsigned char *tx, unsigned char tx_len, unsigned char *rx, unsigned char max_rx) +{ + unsigned char cmd[3 + tx_len]; + unsigned char rep[3 + 64]; + int cmdlen, rx_len, n; + + if (!hdl) { + return -1; + } + + if (!tx) { + return -1; + } + + cmd[0] = RQ_GCN64_RAW_SI_COMMAND; + cmd[1] = channel; + cmd[2] = tx_len; + memcpy(cmd+3, tx, tx_len); + cmdlen = 3 + tx_len; + + n = gcn64_exchange(hdl, cmd, cmdlen, rep, sizeof(rep)); + if (n<0) + return n; + + rx_len = rep[2]; + if (rx) { + memcpy(rx, rep + 3, rx_len); + } + + return rx_len; +} + +int gcn64lib_16bit_scan(gcn64_hdl_t hdl, unsigned short min, unsigned short max) +{ + int id, n; + unsigned char buf[64]; + + if (!hdl) { + return -1; + } + + for (id = min; id<=max; id++) { + buf[0] = id >> 8; + buf[1] = id & 0xff; + n = gcn64lib_rawSiCommand(hdl, 0, buf, 2, buf, sizeof(buf)); + if (n > 0) { + printf("CMD 0x%04x answer: ", id); + printHexBuf(buf, n); + } + } + + return 0; +} + +int gcn64lib_8bit_scan(gcn64_hdl_t hdl, unsigned char min, unsigned char max) +{ + int id, n; + unsigned char buf[64]; + + if (!hdl) { + return -1; + } + + for (id = min; id<=max; id++) { + buf[0] = id; + n = gcn64lib_rawSiCommand(hdl, 0, buf, 1, buf, sizeof(buf)); + if (n > 0) { + printf("CMD 0x%02x answer: ", id); + printHexBuf(buf, n); + } + } + + return 0; +} + +int gcn64lib_bootloader(gcn64_hdl_t hdl) +{ + unsigned char cmd[4]; + int cmdlen; + + if (!hdl) { + return -1; + } + + cmd[0] = RQ_GCN64_JUMP_TO_BOOTLOADER; + cmdlen = 1; + + gcn64_exchange(hdl, cmd, cmdlen, cmd, sizeof(cmd)); + + return 0; +} + +int gcn64lib_n64_expansionWrite(gcn64_hdl_t hdl, unsigned short addr, unsigned char *data, int len) +{ + unsigned char cmd[3 + len]; + int cmdlen; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = N64_EXPANSION_WRITE; + cmd[1] = addr>>8; // Address high byte + cmd[2] = addr&0xff; // Address low byte + memcpy(cmd + 3, data, len); + cmdlen = 3 + len; + + n = gcn64lib_rawSiCommand(hdl, 0, cmd, cmdlen, cmd, sizeof(cmd)); + if (n != 1) { + printf("expansion write returned != 1 (%d)\n", n); + return -1; + } + + return cmd[0]; +} + +int gcn64lib_n64_expansionRead(gcn64_hdl_t hdl, unsigned short addr, unsigned char *dst, int max_len) +{ + unsigned char cmd[3]; + int n; + + if (!hdl) { + return -1; + } + + cmd[0] = N64_EXPANSION_READ; + cmd[1] = addr>>8; // Address high byte + cmd[2] = addr&0xff; // Address low byte + + n = gcn64lib_rawSiCommand(hdl, 0, cmd, 3, dst, max_len); + if (n < 0) + return n; + + return n; +} + +static int gcn64lib_blockIO_compat(gcn64_hdl_t hdl, struct blockio_op *iops, int n_iops) +{ + int i; + int res; + + for (i=0; icaps.bio_support) { + return gcn64lib_blockIO_compat(hdl, iops, n_iops); + } + else { + // Request format: + // + // RQ_GCN64_BLOCK_IO + // chn, n_tx, n_rx, tx[] + // ... + // + // The adapter stops processing the buffer when the + // buffer ends or when a channel set to 0xff is encountered. + // + memset(iobuf, 0xff, sizeof(iobuf)); + iobuf[0] = RQ_GCN64_BLOCK_IO; + for (p=1, i=0; i sizeof(iobuf)) { + fprintf(stderr, "io blocks do not fit in buffer\n"); + return -1; + } + iobuf[p] = iops[i].chn; + p++; + iobuf[p] = iops[i].tx_len & BIO_RXTX_MASK; + p++; + iobuf[p] = iops[i].rx_len & BIO_RXTX_MASK; + p++; + + memcpy(iobuf + p, iops[i].tx_data, iops[i].tx_len); + p += iops[i].tx_len; + } +#ifdef DEBUG_BLOCKIO + fputs("blockIO request: ", stdout); + printHexBuf(iobuf, sizeof(iobuf)); +#endif + + n = gcn64_exchange(hdl, iobuf, sizeof(iobuf), iobuf, sizeof(iobuf)); + if (n < 0) { + return n; + } + if (n != sizeof(iobuf)) { + fprintf(stderr, "Unexpected iobuf reply size\n"); + return -1; + } +#ifdef DEBUG_BLOCKIO + fputs("blockIO answer.: ", stdout); + printHexBuf(iobuf, sizeof(iobuf)); + printf("\n"); +#endif + // Answer format: + // + // RQ_GCN64_BLOCK_IO + // n_rx, rx[n_rx] + // ... + // + // n_rx will have bits set according to the result. See BIO_RX*. rx will always + // occupy the number of bytes set in the request, regardless of the result. + // + if (iobuf[0] != RQ_GCN64_BLOCK_IO) { + fprintf(stderr, "Invalid iobuf reply\n"); + return -1; + } + + for (p=1,i=0; i= sizeof(iobuf)) { + fprintf(stderr, "blockIO: adapter reports too much received data\n"); + break; + } + + iops[i].rx_len = iobuf[p]; + p++; + if (p + (iops[i].rx_len & BIO_RXTX_MASK) >= sizeof(iobuf)) { + fprintf(stderr, "blockIO: adapter reports too much received data\n"); + break; + } + memcpy(iops[i].rx_data, iobuf + p, iops[i].rx_len & BIO_RXTX_MASK); + p += iops[i].rx_len & BIO_RXTX_MASK; + } + } + + return 0; +} + diff --git a/src/pc/controller/raphnet/gcn64lib.h b/src/pc/controller/raphnet/gcn64lib.h new file mode 100644 index 0000000000..c06639baff --- /dev/null +++ b/src/pc/controller/raphnet/gcn64lib.h @@ -0,0 +1,42 @@ +#ifndef _gcn64_lib_h__ +#define _gcn64_lib_h__ + +#include "gcn64.h" + +#define CTL_TYPE_NONE 0 +#define CTL_TYPE_N64 1 +#define CTL_TYPE_GC 2 +#define CTL_TYPE_GCKB 3 + +int gcn64lib_suspendPolling(gcn64_hdl_t hdl, unsigned char suspend); +int gcn64lib_setConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *data, unsigned char len); +int gcn64lib_getConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *rx, unsigned char rx_max); +int gcn64lib_rawSiCommand(gcn64_hdl_t hdl, unsigned char channel, unsigned char *tx, unsigned char tx_len, unsigned char *rx, unsigned char max_rx); +int gcn64lib_getVersion(gcn64_hdl_t hdl, char *dst, int dstmax); +int gcn64lib_getSignature(gcn64_hdl_t hdl, char *dst, int dstmax); +int gcn64lib_forceVibration(gcn64_hdl_t hdl, unsigned char channel, unsigned char vibrate); +int gcn64lib_getControllerType(gcn64_hdl_t hdl, int chn); +const char *gcn64lib_controllerName(int type); +int gcn64lib_bootloader(gcn64_hdl_t hdl); + +int gcn64lib_n64_expansionWrite(gcn64_hdl_t hdl, unsigned short addr, unsigned char *data, int len); +int gcn64lib_n64_expansionRead(gcn64_hdl_t hdl, unsigned short addr, unsigned char *dst, int max_len); + +int gcn64lib_8bit_scan(gcn64_hdl_t hdl, unsigned char min, unsigned char max); +int gcn64lib_16bit_scan(gcn64_hdl_t hdl, unsigned short min, unsigned short max); + +#define BIO_RXTX_MASK 0x3F +#define BIO_RX_LEN_TIMEDOUT 0x80 +#define BIO_RX_LEN_PARTIAL 0x40 + +struct blockio_op { + unsigned char chn; + unsigned char tx_len; + unsigned char *tx_data; + unsigned char rx_len; // expected/max bytes + unsigned char *rx_data; +}; + +int gcn64lib_blockIO(gcn64_hdl_t hdl, struct blockio_op *iops, int n_iops); + +#endif // _gcn64_lib_h__ diff --git a/src/pc/controller/raphnet/hexdump.c b/src/pc/controller/raphnet/hexdump.c new file mode 100644 index 0000000000..4027f9a3b5 --- /dev/null +++ b/src/pc/controller/raphnet/hexdump.c @@ -0,0 +1,29 @@ +/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware + Copyright (C) 2007-2015 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 + +void printHexBuf(unsigned char *buf, int n) +{ + int i; + + for (i=0; i +#include +#include "plugin_back.h" +#include "gcn64.h" +#include "gcn64lib.h" +#include "hexdump.h" + +#undef ENABLE_TIMING + +#ifdef ENABLE_TIMING +#include + +#if defined(__MINGW32__) || defined(__MINGW64__) +#error Timing not supported under Windows +#endif +#endif + +#undef TIME_RAW_IO +#undef TIME_COMMAND_TO_READ + +#undef TRACE_BLOCK_IO +// Filter +#define EXTENSION_RW_ONLY 0 + +#define MAX_ADAPTERS 4 +#define MAX_CHANNELS 4 + +static void nodebug(int l, const char *m, ...) { } + +static pb_debugFunc DebugMessage = nodebug; + + + +#define MAX_OPS 64 + +struct adapter { + gcn64_hdl_t handle; + struct gcn64_info inf; + struct blockio_op biops[MAX_OPS]; + int n_ops; +}; + +static int g_n_adapters = 0; +struct adapter g_adapters[MAX_ADAPTERS] = { }; + +struct rawChannel { + struct adapter *adapter; + int chn; +}; + +/* Multiple adapters are supported, some are single player, others + * two-player. As they are discovered during scan, their + * channels (corresponding to physical controller ports) are added + * to this table. Then, when plugin_front() calls functions in this + * files with a specified channel, the channel is the index in this + * table. + */ +static struct rawChannel g_channels[MAX_CHANNELS] = { }; +static int g_n_channels = 0; + +int pb_init(pb_debugFunc debugFn) +{ + DebugMessage = debugFn; + gcn64_init(0); + return 0; +} + +static void pb_freeAllAdapters(void) +{ + int i; + + for (i=0; iinf, lctx)) { + + adap->handle = gcn64_openDevice(&adap->inf); + if (!adap->handle) { + DebugMessage(PB_MSG_ERROR, "Could not open gcn64 device serial '%ls'. Skipping it.\n", adap->inf.str_serial); + continue; + } + + DebugMessage(PB_MSG_INFO, "Found USB device 0x%04x:0x%04x serial '%ls' name '%ls'\n", + adap->inf.usb_vid, adap->inf.usb_pid, adap->inf.str_serial, adap->inf.str_prodname); + DebugMessage(PB_MSG_INFO, "Adapter supports %d raw channel(s)\n", adap->inf.caps.n_raw_channels); + + g_n_adapters++; + if (g_n_adapters >= MAX_ADAPTERS) + break; + + adap = &g_adapters[g_n_adapters]; + } + gcn64_freeListCtx(lctx); + + /* Pass 2: Fill the g_channel[] array with the available raw channels. + * For instance, if there are adapters A, B and C (where A and C are single-player + * and B is dual-player), we get this: + * + * [0] = Adapter A, raw channel 0 + * [1] = Adapter B, raw channel 0 + * [2] = Adapter B, raw channel 1 + * [3] = Adapter C, raw channel 0 + * + * */ + for (i=0; iinf.caps.n_raw_channels <= 0) + continue; + + for (j=0; jinf.caps.n_raw_channels; j++) { + if (g_n_channels >= MAX_CHANNELS) { + return g_n_channels; + } + g_channels[g_n_channels].adapter = adap; + g_channels[g_n_channels].chn = j; + DebugMessage(PB_MSG_INFO, "Channel %d: Adapter '%ls' raw channel %d\n", g_n_channels, adap->inf.str_serial, j); + g_n_channels++; + } + } + + return g_n_channels; +} + +int pb_romOpen(void) +{ + int i; + + for (i=0; i 1 ? 's':' '); + +} + +static void debug_raw_commands_out(unsigned char *command, int channel_id) +{ + int result = command[1]; + int i; + + printf("chn %d: result: 0x%02x : ", channel_id, result); + if (0 == (command[1] & 0xC0)) { + for (i=0; ibiops; + + /* Skip adapters that do not have IO operations queued. */ + if (adap->n_ops <= 0) + continue; + +#ifdef TRACE_BLOCK_IO + for (i=0; in_ops; i++) { + if (EXTENSION_RW_ONLY && (biops[i].tx_data[0] < 0x02)) + continue; + op_was_extio[i] = 1; + printf("Before blockIO: op %d, chn: %d, : tx: 0x%02x, rx: 0x%02x, data: ", i, biops[i].chn, + biops[i].tx_len, biops[i].rx_len); + printHexBuf(biops[i].tx_data, biops[i].tx_len); + } +#endif + +#ifdef TIME_RAW_IO + timing(1, NULL); +#endif + res = gcn64lib_blockIO(adap->handle, biops, adap->n_ops); +#ifdef TIME_RAW_IO + timing(0, "blockIO"); +#endif + + if (res == 0) { + // biops rx_data pointed into PIFram so data is there. But we need + // to patch the RX length parameters (the two most significant bits + // are error bits such as timeout..) + for (i=0; in_ops; i++) { + // in PIFram, the read length is one byte before the tx data. A rare + // occasion to use a negative array index ;) + biops[i].tx_data[-1] = biops[i].rx_len; + +#ifdef TRACE_BLOCK_IO + if (EXTENSION_RW_ONLY && (!op_was_extio[i])) // Read + continue; + + printf("After blockIO: op %d, chn: %d, : tx: 0x%02x, rx: 0x%02x, data: ", i, biops[i].chn, + biops[i].tx_len, biops[i].rx_len); + if (biops[i].rx_len & BIO_RX_LEN_TIMEDOUT) { + printf("Timeout\n"); + } else if (biops[i].rx_len & BIO_RX_LEN_PARTIAL) { + printf("Incomplete\n"); + } else { + printHexBuf(biops[i].rx_data, biops[i].rx_len); + } +#endif + } + } else { + // For debugging + //exit(1); + } + + adap->n_ops = 0; + } + return 0; + + +} + +static int pb_commandIsValid(int Control, unsigned char *Command) +{ + if (Control < 0 || Control >= g_n_channels) { + DebugMessage(PB_MSG_WARNING, "pb_readController called with Control=%d\n", Control); + return 0; + } + + if (!Command) { + DebugMessage(PB_MSG_WARNING, "pb_readController called with NULL Command pointer\n"); + return 0; + } + + // When a CIC challenge took place in update_pif_write(), the pif ram + // contains a bunch 0xFF followed by 0x00 at offsets 46 and 47. Offset + // 48 onwards contains the challenge answer. + // + // Then when update_pif_read() is called, the 0xFF bytes are be skipped + // up to the two 0x00 bytes that increase the channel to 2. Then the + // challenge answer is (incorrectly) processed as if it were commands + // for the third controller. + // + // This cause issues with the raphnetraw plugin since it modifies pif ram + // to store the result or command error flags. This corrupts the reponse + // and leads to challenge failure. + // + // As I know of no controller commands above 0x03, the filter below guards + // against this condition... + // + if (Control == 2 && Command[2] > 0x03) { + DebugMessage(PB_MSG_WARNING, "Invalid controller command\n"); + return 0; + } + + // When Mario Kart 64 uses a controller pak, such PIF ram content + // occurs: + // + // ff 03 21 02 01 f7 ff ff + // ff ff ff ff ff ff ff ff + // ff ff ff ff ff ff ff ff + // ff ff ff ff ff ff ff ff + // ff ff ff ff ff ff ff 21 + // fe 00 00 00 00 00 00 00 + // 00 00 00 00 00 00 00 00 + // 00 00 00 00 00 00 00 00 + // + // It results in this: + // - Transmission of 3 bytes with a 33 bytes return on channel 0, + // - Transmission of 33 bytes with 254 bytes in return on channel 1!? + // + // Obviously the second transaction is an error. The 0xFE (254) that follows + // is where processing should actually stop. This happens to be an invalid length detectable + // by looking at the two most significant bits.. + // + if (Command[0] == 0xFE && Command[1] == 0x00) { + DebugMessage(PB_MSG_WARNING, "Ignoring invalid io operation (T: 0x%02x, R: 0x%02x)\n", + Command[0], Command[1]); + return 0; + } + + return 1; +} + +int pb_readController(int Control, unsigned char *Command) +{ + struct rawChannel *channel; + struct adapter *adap; + struct blockio_op *biops; + + // Called with -1 at the end of PIF ram. + if (Control == -1) { + return pb_performIo(); + } + + /* Check for out of bounds Control parameter, for + * NULL Command and filter various invalid conditions. */ + if (!pb_commandIsValid(Control, Command)) { + return 0; + } + + /* Add the IO operation to the block io list of + * the adapter serving this channel. */ + + channel = &g_channels[Control]; + adap = channel->adapter; + + if (adap->n_ops >= MAX_OPS) { + DebugMessage(PB_MSG_ERROR, "Too many io ops\n"); + } else { + biops = adap->biops; + + biops[adap->n_ops].chn = channel->chn; // Control; + biops[adap->n_ops].tx_len = Command[0] & BIO_RXTX_MASK; + biops[adap->n_ops].rx_len = Command[1] & BIO_RXTX_MASK; + biops[adap->n_ops].tx_data = Command + 2; + biops[adap->n_ops].rx_data = Command + 2 + biops[adap->n_ops].tx_len; + + if (biops[adap->n_ops].tx_len == 0 || biops[adap->n_ops].rx_len == 0) { + DebugMessage(PB_MSG_WARNING, "TX or RX was zero\n"); + return 0; + } + + adap->n_ops++; + } + + return 0; +} diff --git a/src/pc/controller/raphnet/plugin_back.h b/src/pc/controller/raphnet/plugin_back.h new file mode 100644 index 0000000000..d2aa859e2d --- /dev/null +++ b/src/pc/controller/raphnet/plugin_back.h @@ -0,0 +1,22 @@ +#ifndef _plugin_back_h__ +#define _plugin_back_h__ + +/* Those match mupen64plus levels */ +#define PB_MSG_ERROR 1 +#define PB_MSG_WARNING 2 +#define PB_MSG_INFO 3 +#define PB_MSG_STATUS 4 +#define PB_MSG_VERBOSE 5 + +typedef void (*pb_debugFunc)(int level, const char *message, ...); + +int pb_init(pb_debugFunc debugFn); +int pb_shutdown(void); +int pb_scanControllers(void); +int pb_readController(int Control, unsigned char *Command); +int pb_controllerCommand(int Control, unsigned char *Command); +int pb_romOpen(void); +int pb_romClosed(void); + +#endif // _plugin_back_h__ + diff --git a/src/pc/controller/raphnet/requests.h b/src/pc/controller/raphnet/requests.h new file mode 100644 index 0000000000..9eccf0d1ce --- /dev/null +++ b/src/pc/controller/raphnet/requests.h @@ -0,0 +1,38 @@ +#ifndef _gcn64_requests_h__ +#define _gcn64_requests_h__ + +/* Commands */ +#define RQ_GCN64_SET_CONFIG_PARAM 0x01 +#define RQ_GCN64_GET_CONFIG_PARAM 0x02 +#define RQ_GCN64_SUSPEND_POLLING 0x03 +#define RQ_GCN64_GET_VERSION 0x04 +#define RQ_GCN64_GET_SIGNATURE 0x05 +#define RQ_GCN64_GET_CONTROLLER_TYPE 0x06 +#define RQ_GCN64_SET_VIBRATION 0x07 +#define RQ_GCN64_RAW_SI_COMMAND 0x80 +#define RQ_GCN64_BLOCK_IO 0x81 +#define RQ_GCN64_JUMP_TO_BOOTLOADER 0xFF + +/* Configuration parameters and constants */ +#define CFG_PARAM_MODE 0x00 + +/* Values for mode */ +#define CFG_MODE_STANDARD 0x00 +#define CFG_MODE_N64_ONLY 0x01 +#define CFG_MODE_GC_ONLY 0x02 + +#define CFG_PARAM_SERIAL 0x01 + +#define CFG_PARAM_POLL_INTERVAL0 0x10 +#define CFG_PARAM_POLL_INTERVAL1 0x11 +#define CFG_PARAM_POLL_INTERVAL2 0x12 +#define CFG_PARAM_POLL_INTERVAL3 0x13 + +#define CFG_PARAM_N64_SQUARE 0x20 // Not implemented +#define CFG_PARAM_GC_MAIN_SQUARE 0x21 // Not implemented +#define CFG_PARAM_GC_CSTICK_SQUARE 0x22 // Not implemented +#define CFG_PARAM_FULL_SLIDERS 0x23 +#define CFG_PARAM_INVERT_TRIG 0x24 + + +#endif