/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * Joystick driver for PSX controllers. * * By Richard Davies. * * Based on sample code by Earle F. Philhower, III. from DirectPad * Pro 4.9, for use with the DirectPad Pro parallel port interface. * * See for interface details. * * Original parallel port interface and code by Juan Berrocal. * * Digital, Analog, Dual Force (control), NegCon and Mouse * information by T. Fujita. Dual Shock (vibrate) information by * Dark Fader. Multi tap, G-con45 and Jogcon information by me * (the weird stuff ;) * * This driver recognises Digital, Analog, Dual Shock, Mouse, negCon, * G-con45, Jogcon, Konami Lightgun and Multi tap devices. * * Digital, Dual Shock, neGcon, G-con45, Jogcon and Multi tap devices * have all been tested. The Analog (green mode or joystick), Mouse * and Konami Lightgun devices have not. The position data is likely * to be broken for the Konami Lightgun, and may also be broken for * the Mouse. * * The G-con45 needs to be connected to (and pointed at) a TV type * monitor connected to your computer. The composite video out on my * video card works fine for this. * * The Sony Dual Shock or Namco Jogcon will reset themselves (to * digital mode) after not being polled for 5 seconds. This is normal, * the same thing happens on a Playstation, it's meant to stop any * vibration in case the host machine crashes. However, if this * happens to a Jogcon controller the mode button is disabled. To * reenable the mode button on the Jogcon hold down the Start and * Select buttons at the same time. Other mode switching controllers * may have similar quirks. * * Some people may have problems with the psx poll delay set at 3 * causing them twitchy controls (this depends on the controllers * more than anything else). * * It may be worthwhile to add calibration to some of the analog * controls, although most controller types aren't really meant to * be calibrated: * * - My Dual Shock controller centres really badly; most of them do. * - My neGcon centres really well (+/- 1). * - The G-con45 needs calibration for it's centre aim and velocity. * - The Jogcon calibrates itself when initialised. * * To Do List: * * - Verify Analog Joystick (Green mode) works. * - Verify Mouse position works. * - Verify MegaTap interface. * - Add calibration for the G-con45, Dual Shock and neGcon controllers. * - Implement Konami Lightgun aim. * - Implement Jogcon force feedback. * - Implement unsupported controllers (Ascii Sphere? Beat Mania Decks?) * * If you can help with any of these then please let me know. * * * ----------------------- * | o o o | o o o | o o o | Controller Port (looking into plug) * \_____________________/ * 1 2 3 4 5 6 7 8 9 * * Controller Parallel * * 1 - data 10 (conport 1, 3, 4, 5, 6), 13 (conport 2) * 2 - command 2 * 3 - 9V(shock) +9V power supply terminal for Dual Shock * 4 - gnd 18,19 also -9V and/or -5V power supply terminal * 5 - V+ 5, 6, 7, 8, 9 through diodes, or +5V power supply terminal * 6 - att 3 (conport 1, 2), 5 (conport 3), 6 (conport 4), 7 (conport 5), 8 (conport 6) * 7 - clock 4 * 9 - ack 12 (conport 1, 3, 4, 5, 6), 14 (conport 2) ** * * ** There is an error in the DirectPad Pro documentation, which states that * the second control port should have pin 9 connected to pin 15 on the * parallel port. This should actually be pin 14 on the parallel port. To * make things more confusing, this error is unlikely to prevent the * interface from working properly. It's also possible that a change to the * scanning code has been made in version 5.0 to prevent people from having * to rewire their interfaces. * * See readme.txt for copyright information. */ #include "allegro.h" #ifndef ALLEGRO_DOS #error something is wrong with the makefile #endif #define LPT1_BASE 0x378 #define LPT2_BASE 0x278 #define LPT3_BASE 0x3bc #define LPT_D0 0x01 #define LPT_D1 0x02 #define LPT_D2 0x04 #define LPT_D3 0x08 #define LPT_D4 0x10 #define LPT_D5 0x20 #define LPT_D6 0x40 #define LPT_D7 0x80 #define LPT_AUT 0x08 #define LPT_SEL 0x10 #define LPT_PAP 0x20 #define LPT_ACK 0x40 #define PSX_MAX_DATA 33 /* maximum possible length of controller packet in bytes */ #define PSX_MAX_DETECT 8 /* maximum number of controllers to install */ #define PSX_DELAY 3 /* JAP code EUR code Name */ #define PSX_MOUSE 1 /* SCPH-1030 SCPH-???? Mouse */ #define PSX_NEGCON 2 /* SLPH-0001 SLEH-0003 Namco neGcon SLPH-0007 Nasca Pachinco Handle (!) SLPH-0015 ? Volume Controller (!) SLPH-???? SLEH-0006(?)MadKatz Steering Wheel */ #define PSX_KONAMIGUN 3 /* SLPH-???? SLPH-???? Konami Lightgun */ #define PSX_DIGITAL 4 /* SCPH-1010 SCPH 1080 E Controller SCPH-1110 SCPH-???? Analog Joystick - Digital Mode SCPH-1150 SCPH-1200 E Dual Shock Analog Controller - Digital Mode SLPH-???? SLEH-0011 Ascii Resident Evil Pad SLPH-???? SLEH-0004 Namco Arcade Stick */ #define PSX_ANALOG_GREEN 5 /* SCPH-1110 SCPH-???? Analog Joystick - Analog Mode SCPH-1150 SCPH-1200 E Dual Shock Analog Controller - Analog Green Mode */ #define PSX_GUNCON 6 /* SLPH-???? SLEH-0007 Namco G-con45 */ #define PSX_ANALOG_RED 7 /* SCPH-1150 SCPH-1200 E Dual Shock Analog Controller - Analog Red Mode */ #define PSX_JOGCON 14 /* SLPH-???? SLEH-0020 Namco Jogcon */ /* PSX_MULTITAP SCPH-1070 SCPH-1070 E Multi tap */ /* driver functions */ static int psx1_init(void); static int psx2_init(void); static int psx3_init(void); static void psx_exit(int base); static void psx1_exit(void); static void psx2_exit(void); static void psx3_exit(void); static int psx1_poll(void); static int psx2_poll(void); static int psx3_poll(void); static int psx_detect(int base, int conport, int tap); JOYSTICK_DRIVER joystick_psx1 = { JOY_TYPE_PSXPAD_LPT1, NULL, NULL, "PSXpad-LPT1", psx1_init, psx1_exit, psx1_poll, NULL, NULL, NULL, NULL }; JOYSTICK_DRIVER joystick_psx2 = { JOY_TYPE_PSXPAD_LPT2, NULL, NULL, "PSXpad-LPT2", psx2_init, psx2_exit, psx2_poll, NULL, NULL, NULL, NULL }; JOYSTICK_DRIVER joystick_psx3 = { JOY_TYPE_PSXPAD_LPT3, NULL, NULL, "PSXpad-LPT3", psx3_init, psx3_exit, psx3_poll, NULL, NULL, NULL, NULL }; static unsigned char psx_parallel_out = 0xff; static int psx_first_poll = 1; typedef struct { int conport; int tap; int type; } PSX_INFO; static PSX_INFO psx_detected[PSX_MAX_DETECT]; /* psx_init: * Initialise joy[] for Allegro depending on what's detected. */ static int psx_init(int base) { int i, j, type; num_joysticks = 0; /* test for the presence of controllers on port base */ for (i=0; i<6; i++) { for (j=1; j<5; j++) { type = psx_detect(base, i, j); if (type != -1) { psx_detected[num_joysticks].conport = i; psx_detected[num_joysticks].tap = j; psx_detected[num_joysticks].type = type; num_joysticks++; } if (num_joysticks == PSX_MAX_DETECT) i = 6; } } if (!num_joysticks) return -1; for (i=0; i> 4; packet_length = 3 + (2 * (data[1] & 0x0f)); /* if the packet is of a legal length */ if (packet_length < PSX_MAX_DATA) /* return the controller type */ return psx_type; else return -1; } /* * retrieves controller data for conport:tap connected to base */ static void psx_poll(int base, int conport, int tap, int type, int joynum) { unsigned char data[PSX_MAX_DATA]; int i, psx_type, packet_length; psx_sendinit(base, conport); data[0] = psx_sendbyte(base, conport, tap, 1); data[1] = psx_sendbyte(base, conport, 0x42, 1); psx_type = (data[1] & 0xf0) >> 4; packet_length = 3 + (2 * (data[1] & 0x0f)); /* if the packet is of a legal length */ if (packet_length < PSX_MAX_DATA) { /* read the remaining packet */ for (i=2; i 64; /* I */ joy[joynum].stick[2].axis[0].pos = data[6]; joy[joynum].stick[2].axis[0].d1 = data[6] > 0x7f; /* II */ joy[joynum].stick[2].axis[1].pos = data[7]; joy[joynum].stick[2].axis[1].d1 = data[7] > 0x7f; /* L1 */ joy[joynum].stick[2].axis[2].pos = data[7]; joy[joynum].stick[2].axis[2].d1 = data[8] > 0xdf; break; case PSX_MOUSE: /* right button */ joy[joynum].button[0].b = data[4] & 0x04 ? 0 : 1; /* left button */ joy[joynum].button[1].b = data[4] & 0x08 ? 0 : 1; /* position x axis */ joy[joynum].stick[0].axis[0].pos -= (signed char)data[6]; /* position y axis */ joy[joynum].stick[0].axis[1].pos -= (signed char)data[5]; /* setup digital controls for position */ joy[joynum].stick[0].axis[0].d1 = joy[joynum].stick[1].axis[0].pos < -64; joy[joynum].stick[0].axis[0].d2 = joy[joynum].stick[1].axis[0].pos > 64; joy[joynum].stick[0].axis[1].d1 = joy[joynum].stick[1].axis[1].pos < -64; joy[joynum].stick[0].axis[1].d2 = joy[joynum].stick[1].axis[1].pos > 64; /* set position boundaries */ if (joy[joynum].stick[0].axis[0].pos > 128) joy[joynum].stick[0].axis[0].pos = 128; else if (joy[joynum].stick[0].axis[0].pos < -128) joy[joynum].stick[0].axis[0].pos = -128; if (joy[joynum].stick[0].axis[1].pos > 128) joy[joynum].stick[0].axis[1].pos = 128; else if (joy[joynum].stick[0].axis[1].pos < -128) joy[joynum].stick[0].axis[1].pos = -128; break; case PSX_GUNCON: /* trigger */ joy[joynum].button[0].b = data[4] & 0x20 ? 0 : 1; /* A */ joy[joynum].button[1].b = data[3] & 0x08 ? 0 : 1; /* B */ joy[joynum].button[2].b = data[4] & 0x40 ? 0 : 1; /* aim */ /* bad data */ if (!(data[5] == 0x01)) { /* too light and too dark */ /*if (!(data[7] == 0x05) && !(data[7] == 0x0a)) */ { /* aim X */ joy[joynum].stick[0].axis[0].pos = (((data[6] & 0x01) << 8) + data[5]) - 256; /* aim y */ joy[joynum].stick[0].axis[1].pos = data[7] - 128; } } /* setup digital controls for aim */ joy[joynum].stick[0].axis[0].d1 = joy[joynum].stick[0].axis[0].pos < -64; joy[joynum].stick[0].axis[0].d2 = joy[joynum].stick[0].axis[0].pos > 64; joy[joynum].stick[0].axis[1].d1 = joy[joynum].stick[0].axis[1].pos < -64; joy[joynum].stick[0].axis[1].d2 = joy[joynum].stick[0].axis[1].pos > 64; break; case PSX_KONAMIGUN: /* trigger */ joy[joynum].button[0].b = data[4] & 0x80 ? 0 : 1; /* back */ joy[joynum].button[1].b = data[4] & 0x40 ? 0 : 1; /* start */ joy[joynum].button[2].b = data[3] & 0x08 ? 0 : 1; /* aim X axis - probably wrong */ joy[joynum].stick[0].axis[0].pos = data[5] - 128; /* aim Y axis - probably wrong */ joy[joynum].stick[0].axis[1].pos = data[6] - 128; /* setup digital controls for aim */ joy[joynum].stick[0].axis[0].d1 = joy[joynum].stick[0].axis[0].pos < -64; joy[joynum].stick[0].axis[0].d2 = joy[joynum].stick[0].axis[0].pos > 64; joy[joynum].stick[0].axis[1].d1 = joy[joynum].stick[0].axis[1].pos < -64; joy[joynum].stick[0].axis[1].d2 = joy[joynum].stick[0].axis[1].pos > 64; break; case PSX_DIGITAL: case PSX_ANALOG_GREEN: case PSX_ANALOG_RED: case PSX_JOGCON: default: /* direction pad left */ joy[joynum].stick[2].axis[0].d1 = data[3] & 0x80 ? 0 : 1; /* direction pad down */ joy[joynum].stick[2].axis[1].d2 = data[3] & 0x40 ? 0 : 1; /* direction pad right */ joy[joynum].stick[2].axis[0].d2 = data[3] & 0x20 ? 0 : 1; /* direction pad up */ joy[joynum].stick[2].axis[1].d1 = data[3] & 0x10 ? 0 : 1; /* setup analog controls for direction pad */ for (i=0; i<2; i++) { if (joy[joynum].stick[2].axis[i].d1) joy[joynum].stick[2].axis[i].pos = -128; else if (joy[joynum].stick[2].axis[i].d2) joy[joynum].stick[2].axis[i].pos = 128; else joy[joynum].stick[2].axis[i].pos = 0; } switch (psx_type) { case PSX_DIGITAL: /* mirror direction pad for unused axes */ joy[joynum].stick[0].axis[0].pos = joy[joynum].stick[1].axis[0].pos = joy[joynum].stick[3].axis[0].pos = joy[joynum].stick[2].axis[0].pos; joy[joynum].stick[0].axis[1].pos = joy[joynum].stick[1].axis[1].pos = joy[joynum].stick[2].axis[1].pos; joy[joynum].stick[0].axis[0].d1 = joy[joynum].stick[1].axis[0].d1 = joy[joynum].stick[3].axis[0].d1 = joy[joynum].stick[2].axis[0].d1; joy[joynum].stick[0].axis[0].d2 = joy[joynum].stick[1].axis[0].d2 = joy[joynum].stick[3].axis[0].d2 = joy[joynum].stick[2].axis[0].d2; joy[joynum].stick[0].axis[1].d1 = joy[joynum].stick[1].axis[1].d1 = joy[joynum].stick[2].axis[1].d1; joy[joynum].stick[0].axis[1].d2 = joy[joynum].stick[1].axis[1].d2 = joy[joynum].stick[2].axis[1].d2; break; case PSX_JOGCON: /* dial */ joy[joynum].stick[3].axis[0].pos = ((data[6] << 7) + (data[5] >> 1)) - (data[6] & 0x80 ? 32768 : 0); /* setup digital controls for dial */ joy[joynum].stick[3].axis[0].d1 = data[7] & 0x02; joy[joynum].stick[3].axis[0].d2 = data[7] & 0x01; /* mirror direction pad for unused axes */ joy[joynum].stick[0].axis[0].pos = joy[joynum].stick[1].axis[0].pos = joy[joynum].stick[2].axis[0].pos; joy[joynum].stick[0].axis[1].pos = joy[joynum].stick[1].axis[1].pos = joy[joynum].stick[2].axis[1].pos; joy[joynum].stick[0].axis[0].d1 = joy[joynum].stick[1].axis[0].d1 = joy[joynum].stick[2].axis[0].d1; joy[joynum].stick[0].axis[0].d2 = joy[joynum].stick[1].axis[0].d2 = joy[joynum].stick[2].axis[0].d2; joy[joynum].stick[0].axis[1].d1 = joy[joynum].stick[1].axis[1].d1 = joy[joynum].stick[2].axis[1].d1; joy[joynum].stick[0].axis[1].d2 = joy[joynum].stick[1].axis[1].d2 = joy[joynum].stick[2].axis[1].d2; break; /* analog or unknown controller */ default: /* left stick X axis */ joy[joynum].stick[0].axis[0].pos = data[7] - 128; /* left stick Y axis */ joy[joynum].stick[0].axis[1].pos = data[8] - 128; /* right stick X axis */ joy[joynum].stick[1].axis[0].pos = data[5] - 128; /* right stick Y axis */ joy[joynum].stick[1].axis[1].pos = data[6] - 128; /* setup digital controls for left and right stick */ joy[joynum].stick[0].axis[0].d1 = joy[joynum].stick[0].axis[0].pos < -64; joy[joynum].stick[0].axis[0].d2 = joy[joynum].stick[0].axis[0].pos > 64; joy[joynum].stick[0].axis[1].d1 = joy[joynum].stick[0].axis[1].pos < -64; joy[joynum].stick[0].axis[1].d2 = joy[joynum].stick[0].axis[1].pos > 64; joy[joynum].stick[1].axis[0].d1 = joy[joynum].stick[1].axis[0].pos < -64; joy[joynum].stick[1].axis[0].d2 = joy[joynum].stick[1].axis[0].pos > 64; joy[joynum].stick[1].axis[1].d1 = joy[joynum].stick[1].axis[1].pos < -64; joy[joynum].stick[1].axis[1].d2 = joy[joynum].stick[1].axis[1].pos > 64; /* mirror direction pad for unused axes */ joy[joynum].stick[3].axis[0].pos = joy[joynum].stick[2].axis[0].pos; joy[joynum].stick[3].axis[0].d1 = joy[joynum].stick[2].axis[0].d1; joy[joynum].stick[3].axis[0].d2 = joy[joynum].stick[2].axis[0].d2; break; } /* cross */ joy[joynum].button[0].b = data[4] & 0x40 ? 0 : 1; /* circle */ joy[joynum].button[1].b = data[4] & 0x20 ? 0 : 1; /* square */ joy[joynum].button[2].b = data[4] & 0x80 ? 0 : 1; /* triangle */ joy[joynum].button[3].b = data[4] & 0x10 ? 0 : 1; /* L1 */ joy[joynum].button[4].b = data[4] & 0x04 ? 0 : 1; /* R1 */ joy[joynum].button[5].b = data[4] & 0x08 ? 0 : 1; /* L2 */ joy[joynum].button[6].b = data[4] & 0x01 ? 0 : 1; /* R2 */ joy[joynum].button[7].b = data[4] & 0x02 ? 0 : 1; /* select */ joy[joynum].button[8].b = data[3] & 0x01 ? 0 : 1; /* start */ joy[joynum].button[9].b = data[3] & 0x08 ? 0 : 1; /* L3 */ joy[joynum].button[10].b = data[3] & 0x02 ? 0 : 1; /* R3 */ joy[joynum].button[11].b = data[3] & 0x04 ? 0 : 1; /* fix to allow analog mode switching for Jogcon after 5 seconds without polling */ if ((joy[joynum].button[8].b) && (joy[joynum].button[9].b)) psx_vibrate_init(base, conport, tap); break; } } static int psx1_poll(void) { int joynum; /* send Jogcon init to all controllers */ if (psx_first_poll) { for (joynum=0; joynum