diff --git a/Makefile b/Makefile index 0148866..58d0086 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ STRIP = stripios # Version FAKEMOTE_MAJOR = 0 -FAKEMOTE_MINOR = 3 -FAKEMOTE_PATCH = 1 +FAKEMOTE_MINOR = 4 +FAKEMOTE_PATCH = 0 FAKEMOTE_HASH = "$(shell git describe --dirty --always --exclude '*')" # Flags diff --git a/README.md b/README.md index 2e6973a..711f171 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ _An IOS module that fakes Wiimotes from the input of USB game controllers._ - DS3 and DS4 support includes LEDs, rumble, and the accelerometer - DS4's touchpad is used to emulate the Wiimote IR Camera pointer -- Both controllers emulate a Wiimote with the Nunchuk and Classic Controller extensions connected. Press R3 to switch between them +- Both controllers emulate a Wiimote with the Nunchuk and Classic Controller extensions connected. Press L1+L3 to switch between them +- Three IR pointer emulation modes: direct (touchpad, only for DS4), analog axis relative (move the pointer with the right analog) and analog axis absolute (the pointer is moved proportionally to the right analog starting from the center). Press R1+R3 to switch between them ## Installation 1) Download [d2x cIOS Installer for regular Wii](https://wii.guide/cios.html)/[d2x cIOS Installer for vWii](https://wiiu.hacks.guide/#/vwii-modding) and extract it to the SD card diff --git a/include/button_map.h b/include/button_map.h index ddd3f13..6b513d0 100644 --- a/include/button_map.h +++ b/include/button_map.h @@ -22,6 +22,25 @@ enum bm_classic_analog_axis_e { BM_CLASSIC_ANALOG_AXIS__NUM = BM_CLASSIC_ANALOG_AXIS_RIGHT_Y }; +/* IR pointer emulation */ +enum bm_ir_emulation_mode_e { + BM_IR_EMULATION_MODE_NONE, + BM_IR_EMULATION_MODE_DIRECT, + BM_IR_EMULATION_MODE_RELATIVE_ANALOG_AXIS, + BM_IR_EMULATION_MODE_ABSOLUTE_ANALOG_AXIS, +}; + +enum bm_ir_axis_e { + BM_IR_AXIS_NONE, + BM_IR_AXIS_X, + BM_IR_AXIS_Y, + BM_IR_AXIS__NUM = BM_IR_AXIS_Y, +}; + +struct bm_ir_emulation_state_t { + u16 position[BM_IR_AXIS__NUM]; +}; + void bm_map_wiimote( /* Inputs */ int num_buttons, u32 buttons, @@ -51,13 +70,22 @@ void bm_map_classic( /* Outputs */ struct wiimote_extension_data_format_classic_t *classic); -void bm_calculate_ir( +void bm_map_ir_direct( /* Inputs */ int num_coordinates, const u16 *x, const u16 *y, u16 max_x, u16 max_y, /* Outputs */ struct ir_dot_t ir_dots[static IR_MAX_DOTS]); +void bm_map_ir_analog_axis( + /* Inputs */ + enum bm_ir_emulation_mode_e mode, + struct bm_ir_emulation_state_t *state, + int num_analog_axis, const u8 *analog_axis, + const u8 *ir_analog_axis_map, + /* Outputs */ + struct ir_dot_t ir_dots[static IR_MAX_DOTS]); + static inline bool bm_check_switch_mapping(u32 buttons, bool *switch_mapping, u32 switch_mapping_combo) { bool switch_pressed = (buttons & switch_mapping_combo) == switch_mapping_combo; @@ -108,4 +136,17 @@ static inline void bm_classic_format(struct wiimote_extension_data_format_classi out->bt.hex = (~buttons) & CLASSIC_CTRL_BUTTON_ALL; } +static inline void bm_ir_emulation_state_reset(struct bm_ir_emulation_state_t *state) +{ + state->position[BM_IR_AXIS_X - 1] = IR_CENTER_X; + state->position[BM_IR_AXIS_Y - 1] = IR_CENTER_Y; +} + +static inline void bm_ir_dots_set_out_of_screen(struct ir_dot_t ir_dots[static IR_MAX_DOTS]) +{ + for (int i = 0; i < IR_MAX_DOTS; i++) + ir_dots[i].y = 1023; +} + + #endif diff --git a/include/usb_hid.h b/include/usb_hid.h index 6a496bb..0459a31 100644 --- a/include/usb_hid.h +++ b/include/usb_hid.h @@ -5,7 +5,7 @@ #include "types.h" #include "fake_wiimote_mgr.h" -#define USB_INPUT_DEVICE_PRIVATE_DATA_SIZE 32 +#define USB_INPUT_DEVICE_PRIVATE_DATA_SIZE 64 typedef struct usb_device_driver_t usb_device_driver_t; diff --git a/source/button_map.c b/source/button_map.c index 4a8588c..32f26c8 100644 --- a/source/button_map.c +++ b/source/button_map.c @@ -71,7 +71,17 @@ void bm_map_classic( bm_classic_format(classic, classic_buttons, classic_analog_axis); } -void bm_calculate_ir( +static inline void map_ir_dot(struct ir_dot_t ir_dots[static IR_MAX_DOTS], const struct ir_dot_t *dot) +{ + s16 vert_offset = g_sensor_bar_position_top ? IR_VERTICAL_OFFSET : -IR_VERTICAL_OFFSET; + + ir_dots[0].x = (IR_HIGH_X - dot->x) - IR_HORIZONTAL_OFFSET; + ir_dots[0].y = dot->y + vert_offset; + ir_dots[1].x = (IR_HIGH_X - dot->x) + IR_HORIZONTAL_OFFSET; + ir_dots[1].y = dot->y + vert_offset; +} + +void bm_map_ir_direct( /* Inputs */ int num_coordinates, const u16 *x, const u16 *y, u16 max_x, u16 max_y, @@ -79,22 +89,54 @@ void bm_calculate_ir( struct ir_dot_t ir_dots[static IR_MAX_DOTS]) { struct ir_dot_t dot; - s16 vert_offset = g_sensor_bar_position_top ? IR_VERTICAL_OFFSET : -IR_VERTICAL_OFFSET; /* TODO: For now we only care about 1 reported coordinate... */ if (num_coordinates == 0) { - ir_dots[0].x = 1023; - ir_dots[0].y = 1023; - ir_dots[1].x = 1023; - ir_dots[1].y = 1023; + bm_ir_dots_set_out_of_screen(ir_dots); return; } dot.x = IR_DOT_CENTER_MIN_X + (x[0] * (IR_DOT_CENTER_MAX_X - IR_DOT_CENTER_MIN_X)) / max_x; dot.y = IR_DOT_CENTER_MIN_Y + (y[0] * (IR_DOT_CENTER_MAX_Y - IR_DOT_CENTER_MIN_Y)) / max_y; + map_ir_dot(ir_dots, &dot); +} + +void bm_map_ir_analog_axis( + /* Inputs */ + enum bm_ir_emulation_mode_e mode, + struct bm_ir_emulation_state_t *state, + int num_analog_axis, const u8 *analog_axis, + const u8 *ir_analog_axis_map, + /* Outputs */ + struct ir_dot_t ir_dots[static IR_MAX_DOTS]) +{ + struct ir_dot_t dot; + + for (int i = 0; i < num_analog_axis; i++) { + if (ir_analog_axis_map[i]) { + s16 val = (s16)analog_axis[i] - 128; + + if (mode == BM_IR_EMULATION_MODE_RELATIVE_ANALOG_AXIS) { + state->position[ir_analog_axis_map[i] - 1] += val / 16; + } else if (mode == BM_IR_EMULATION_MODE_ABSOLUTE_ANALOG_AXIS) { + u16 center = (ir_analog_axis_map[i] == BM_IR_AXIS_X) ? IR_CENTER_X : IR_CENTER_Y; + state->position[ir_analog_axis_map[i] - 1] = center + val; + } + } + } + + if (state->position[BM_IR_AXIS_X - 1] < IR_DOT_CENTER_MIN_X) + state->position[BM_IR_AXIS_X - 1] = IR_DOT_CENTER_MIN_X; + else if (state->position[BM_IR_AXIS_X - 1] > IR_DOT_CENTER_MAX_X) + state->position[BM_IR_AXIS_X - 1] = IR_DOT_CENTER_MAX_X; + + if (state->position[BM_IR_AXIS_Y - 1] < IR_DOT_CENTER_MIN_Y) + state->position[BM_IR_AXIS_Y - 1] = IR_DOT_CENTER_MIN_Y; + else if (state->position[BM_IR_AXIS_Y - 1] > IR_DOT_CENTER_MAX_Y) + state->position[BM_IR_AXIS_Y - 1] = IR_DOT_CENTER_MAX_Y; + - ir_dots[0].x = (IR_HIGH_X - dot.x) - IR_HORIZONTAL_OFFSET; - ir_dots[0].y = dot.y + vert_offset; - ir_dots[1].x = (IR_HIGH_X - dot.x) + IR_HORIZONTAL_OFFSET; - ir_dots[1].y = dot.y + vert_offset; + dot.x = state->position[BM_IR_AXIS_X - 1]; + dot.y = IR_HIGH_Y - state->position[BM_IR_AXIS_Y - 1]; + map_ir_dot(ir_dots, &dot); } diff --git a/source/usb_driver_ds3.c b/source/usb_driver_ds3.c index b11b8b1..3591a0c 100644 --- a/source/usb_driver_ds3.c +++ b/source/usb_driver_ds3.c @@ -114,14 +114,19 @@ struct ds3_private_data_t { u8 analog_axis[DS3_ANALOG_AXIS__NUM]; s16 acc_x, acc_y, acc_z; } input; + enum bm_ir_emulation_mode_e ir_emu_mode; + struct bm_ir_emulation_state_t ir_emu_state; u8 mapping; + u8 ir_emu_mode_idx; u8 leds; bool rumble_on; bool switch_mapping; + bool switch_ir_emu_mode; }; static_assert(sizeof(struct ds3_private_data_t) <= USB_INPUT_DEVICE_PRIVATE_DATA_SIZE); -#define SWITCH_MAPPING_COMBO (BIT(DS3_BUTTON_R3)) +#define SWITCH_MAPPING_COMBO (BIT(DS3_BUTTON_L1) | BIT(DS3_BUTTON_L3)) +#define SWITCH_IR_EMU_MODE_COMBO (BIT(DS3_BUTTON_R1) | BIT(DS3_BUTTON_R3)) static const struct { enum wiimote_ext_e extension; @@ -183,6 +188,17 @@ static const struct { }, }; +static const u8 ir_analog_axis_map[DS3_ANALOG_AXIS__NUM] = { + [DS3_ANALOG_AXIS_RIGHT_X] = BM_IR_AXIS_X, + [DS3_ANALOG_AXIS_RIGHT_Y] = BM_IR_AXIS_Y, +}; + +static const enum bm_ir_emulation_mode_e ir_emu_modes[] = { + BM_IR_EMULATION_MODE_ABSOLUTE_ANALOG_AXIS, + BM_IR_EMULATION_MODE_RELATIVE_ANALOG_AXIS, + BM_IR_EMULATION_MODE_NONE, +}; + static inline void ds3_get_buttons(const struct ds3_input_report *report, u32 *buttons) { u32 mask = 0; @@ -306,10 +322,13 @@ int ds3_driver_ops_init(usb_input_device_t *device, u16 vid, u16 pid) return ret; /* Init private state */ + priv->ir_emu_mode_idx = 0; + bm_ir_emulation_state_reset(&priv->ir_emu_state); + priv->mapping = 0; priv->leds = 0; priv->rumble_on = false; - priv->mapping = 0; priv->switch_mapping = false; + priv->switch_ir_emu_mode = false; /* Set initial extension */ fake_wiimote_set_extension(device->wiimote, input_mappings[priv->mapping].extension); @@ -355,11 +374,16 @@ bool ds3_report_input(usb_input_device_t *device) u16 wiimote_buttons = 0; u16 acc_x, acc_y, acc_z; union wiimote_extension_data_t extension_data; + struct ir_dot_t ir_dots[IR_MAX_DOTS]; + enum bm_ir_emulation_mode_e ir_emu_mode; if (bm_check_switch_mapping(priv->input.buttons, &priv->switch_mapping, SWITCH_MAPPING_COMBO)) { priv->mapping = (priv->mapping + 1) % ARRAY_SIZE(input_mappings); fake_wiimote_set_extension(device->wiimote, input_mappings[priv->mapping].extension); return false; + } else if (bm_check_switch_mapping(priv->input.buttons, &priv->switch_ir_emu_mode, SWITCH_IR_EMU_MODE_COMBO)) { + priv->ir_emu_mode_idx = (priv->ir_emu_mode_idx + 1) % ARRAY_SIZE(ir_emu_modes); + bm_ir_emulation_state_reset(&priv->ir_emu_state); } bm_map_wiimote(DS3_BUTTON__NUM, priv->input.buttons, @@ -373,6 +397,17 @@ bool ds3_report_input(usb_input_device_t *device) fake_wiimote_report_accelerometer(device->wiimote, acc_x, acc_y, acc_z); + ir_emu_mode = ir_emu_modes[priv->ir_emu_mode_idx]; + if (ir_emu_mode == BM_IR_EMULATION_MODE_NONE) { + bm_ir_dots_set_out_of_screen(ir_dots); + } else { + bm_map_ir_analog_axis(ir_emu_mode, &priv->ir_emu_state, + DS3_ANALOG_AXIS__NUM, priv->input.analog_axis, + ir_analog_axis_map, ir_dots); + } + + fake_wiimote_report_ir_dots(device->wiimote, ir_dots); + if (input_mappings[priv->mapping].extension == WIIMOTE_EXT_NONE) { fake_wiimote_report_input(device->wiimote, wiimote_buttons); } else if (input_mappings[priv->mapping].extension == WIIMOTE_EXT_NUNCHUK) { diff --git a/source/usb_driver_ds4.c b/source/usb_driver_ds4.c index e980c0f..eb37be1 100644 --- a/source/usb_driver_ds4.c +++ b/source/usb_driver_ds4.c @@ -127,14 +127,19 @@ struct ds4_private_data_t { } fingers[2]; u8 num_fingers; } input; + enum bm_ir_emulation_mode_e ir_emu_mode; + struct bm_ir_emulation_state_t ir_emu_state; u8 mapping; + u8 ir_emu_mode_idx; u8 leds; bool rumble_on; bool switch_mapping; + bool switch_ir_emu_mode; }; static_assert(sizeof(struct ds4_private_data_t) <= USB_INPUT_DEVICE_PRIVATE_DATA_SIZE); -#define SWITCH_MAPPING_COMBO (BIT(DS4_BUTTON_R3)) +#define SWITCH_MAPPING_COMBO (BIT(DS4_BUTTON_L1) | BIT(DS4_BUTTON_L3)) +#define SWITCH_IR_EMU_MODE_COMBO (BIT(DS4_BUTTON_R1) | BIT(DS4_BUTTON_R3)) static const struct { enum wiimote_ext_e extension; @@ -198,6 +203,17 @@ static const struct { }, }; +static const u8 ir_analog_axis_map[DS4_ANALOG_AXIS__NUM] = { + [DS4_ANALOG_AXIS_RIGHT_X] = BM_IR_AXIS_X, + [DS4_ANALOG_AXIS_RIGHT_Y] = BM_IR_AXIS_Y, +}; + +static const enum bm_ir_emulation_mode_e ir_emu_modes[] = { + BM_IR_EMULATION_MODE_DIRECT, + BM_IR_EMULATION_MODE_RELATIVE_ANALOG_AXIS, + BM_IR_EMULATION_MODE_ABSOLUTE_ANALOG_AXIS, +}; + static inline void ds4_get_buttons(const struct ds4_input_report *report, u32 *buttons) { u32 mask = 0; @@ -303,10 +319,13 @@ int ds4_driver_ops_init(usb_input_device_t *device, u16 vid, u16 pid) struct ds4_private_data_t *priv = (void *)device->private_data; /* Init private state */ + priv->ir_emu_mode_idx = 0; + bm_ir_emulation_state_reset(&priv->ir_emu_state); + priv->mapping = 0; priv->leds = 0; priv->rumble_on = false; - priv->mapping = 0; priv->switch_mapping = false; + priv->switch_ir_emu_mode = false; /* Set initial extension */ fake_wiimote_set_extension(device->wiimote, input_mappings[priv->mapping].extension); @@ -349,11 +368,15 @@ bool ds4_report_input(usb_input_device_t *device) u16 acc_x, acc_y, acc_z; union wiimote_extension_data_t extension_data; struct ir_dot_t ir_dots[IR_MAX_DOTS]; + enum bm_ir_emulation_mode_e ir_emu_mode; if (bm_check_switch_mapping(priv->input.buttons, &priv->switch_mapping, SWITCH_MAPPING_COMBO)) { priv->mapping = (priv->mapping + 1) % ARRAY_SIZE(input_mappings); fake_wiimote_set_extension(device->wiimote, input_mappings[priv->mapping].extension); return false; + } else if (bm_check_switch_mapping(priv->input.buttons, &priv->switch_ir_emu_mode, SWITCH_IR_EMU_MODE_COMBO)) { + priv->ir_emu_mode_idx = (priv->ir_emu_mode_idx + 1) % ARRAY_SIZE(ir_emu_modes); + bm_ir_emulation_state_reset(&priv->ir_emu_state); } bm_map_wiimote(DS4_BUTTON__NUM, priv->input.buttons, @@ -367,9 +390,21 @@ bool ds4_report_input(usb_input_device_t *device) fake_wiimote_report_accelerometer(device->wiimote, acc_x, acc_y, acc_z); - bm_calculate_ir(priv->input.num_fingers, &priv->input.fingers[0].x, &priv->input.fingers[0].y, - DS4_TOUCHPAD_W - 1, DS4_TOUCHPAD_H - 1, - ir_dots); + ir_emu_mode = ir_emu_modes[priv->ir_emu_mode_idx]; + if (ir_emu_mode == BM_IR_EMULATION_MODE_NONE) { + bm_ir_dots_set_out_of_screen(ir_dots); + } else { + if (ir_emu_mode == BM_IR_EMULATION_MODE_DIRECT) { + bm_map_ir_direct(priv->input.num_fingers, + &priv->input.fingers[0].x, &priv->input.fingers[0].y, + DS4_TOUCHPAD_W - 1, DS4_TOUCHPAD_H - 1, + ir_dots); + } else { + bm_map_ir_analog_axis(ir_emu_mode, &priv->ir_emu_state, + DS4_ANALOG_AXIS__NUM, priv->input.analog_axis, + ir_analog_axis_map, ir_dots); + } + } fake_wiimote_report_ir_dots(device->wiimote, ir_dots);