diff --git a/src/lindbergh/graphics.c b/src/lindbergh/graphics.c index 05096f2..cfd8691 100644 --- a/src/lindbergh/graphics.c +++ b/src/lindbergh/graphics.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -108,7 +109,10 @@ int XDefineCursor(Display *display, Window w, Cursor cursor) int XStoreName(Display *display, Window w, const char *window_name) { int (*_XStoreName)(Display * display, Window w, const char *window_name) = dlsym(RTLD_NEXT, "XStoreName"); - return _XStoreName(display, w, getGameName()); + char gameTitle[256] = {0}; + strcat(gameTitle, getGameName()); + strcat(gameTitle, " (X11)"); + return _XStoreName(display, w, gameTitle); } int XNextEvent(Display *display, XEvent *event_return) @@ -118,32 +122,51 @@ int XNextEvent(Display *display, XEvent *event_return) int returnValue = _XNextEvent(display, event_return); switch (event_return->type) { - case KeyPress: - switch (event_return->xkey.keycode) - { - case 28: - securityBoardSetSwitch(BUTTON_TEST, 1); - break; - case 39: - securityBoardSetSwitch(BUTTON_SERVICE, 1); - break; - default: - break; - } - break; + case KeyRelease: + case KeyPress: + { switch (event_return->xkey.keycode) { case 28: - securityBoardSetSwitch(BUTTON_TEST, 0); + securityBoardSetSwitch(BUTTON_TEST, event_return->type == KeyPress); break; case 39: - securityBoardSetSwitch(BUTTON_SERVICE, 0); + securityBoardSetSwitch(BUTTON_SERVICE, event_return->type == KeyPress); + break; + case 14: + incrementCoin(PLAYER_1, event_return->type == KeyPress); + break; + case 15: + incrementCoin(PLAYER_2, event_return->type == KeyPress); + break; + case 111: + setSwitch(PLAYER_1, BUTTON_UP, event_return->type == KeyPress); + break; + case 116: + setSwitch(PLAYER_1, BUTTON_DOWN, event_return->type == KeyPress); + break; + case 113: + setSwitch(PLAYER_1, BUTTON_LEFT, event_return->type == KeyPress); + break; + case 114: + setSwitch(PLAYER_1, BUTTON_RIGHT, event_return->type == KeyPress); + break; + case 10: + setSwitch(PLAYER_1, BUTTON_START, event_return->type == KeyPress); break; default: break; } - break; + } + break; + + case MotionNotify: + { + setAnalogue(ANALOGUE_1, ((double)event_return->xmotion.x / (double)getConfig()->width) * 255.0); + setAnalogue(ANALOGUE_2, ((double)event_return->xmotion.y / (double)getConfig()->height) * 255.0); + } + break; } return returnValue; @@ -157,3 +180,8 @@ int XSetStandardProperties(Display *display, Window window, const char *window_n strcat(gameTitle, " (X11)"); return _XSetStandardProperties(display, window, gameTitle, icon_name, icon_pixmap, argv, argc, hints); } + +Bool XF86VidModeSwitchToMode(Display *display, int screen, XF86VidModeModeInfo *modeline) +{ + return 0; +} diff --git a/src/lindbergh/hook.c b/src/lindbergh/hook.c index e12e87f..3264109 100644 --- a/src/lindbergh/hook.c +++ b/src/lindbergh/hook.c @@ -94,7 +94,7 @@ static void handleSegfault(int signal, siginfo_t *info, void *ptr) default: printf("Warning: Skipping SEGFAULT %X\n", *code); ctx->uc_mcontext.gregs[REG_EIP]++; - //abort(); + // abort(); } } @@ -128,6 +128,12 @@ void __attribute__((constructor)) hook_init() exit(1); } + if (getConfig()->emulateRideboard) + { + if (initRideboard() != 0) + exit(1); + } + securityBoardSetDipResolution(getConfig()->width, getConfig()->height); printf("Loader init success\n"); @@ -222,7 +228,7 @@ ssize_t read(int fd, void *buf, size_t count) return baseboardRead(fd, buf, count); } - if (fd == hooks[SERIAL1] && getConfig()->emulateRideboard) + if (fd == hooks[SERIAL0] && getConfig()->emulateRideboard) { return rideboardRead(fd, buf, count); } @@ -244,7 +250,7 @@ ssize_t write(int fd, const void *buf, size_t count) return baseboardWrite(fd, buf, count); } - if (fd == hooks[SERIAL1] && getConfig()->emulateRideboard) + if (fd == hooks[SERIAL0] && getConfig()->emulateRideboard) { return rideboardWrite(fd, buf, count); } diff --git a/src/lindbergh/rideboard.c b/src/lindbergh/rideboard.c index 79b2485..f54d0d4 100644 --- a/src/lindbergh/rideboard.c +++ b/src/lindbergh/rideboard.c @@ -2,13 +2,391 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "rideboard.h" -ssize_t rideboardRead(int fd, void *buf, size_t count) { +RideState rideState = {0}; + +/** + * SEGA Ride Board Emulator, + * for use with the following Lindbergh games: + * + * Let's Go Jungle Special! + * The House Of The Dead 4 Special + * + * Author: bobbydilley + * Thanks to: doozer, ags, harm + */ + +/* Buffer packet sizes */ +#define OUTPUT_PACKET_SIZE 22 +#define INPUT_PACKET_SIZE 7 + +/* How long to wait for data before timing out */ +#define READ_TIMEOUT 500 + +/* Enums to represent the different states of the MotionSelectSwitch */ +#define MOTION_OFF 0 +#define MOTION_MILD 1 +#define MOTION_NORMAL 2 + +/** + * Process the input packet from the lindbergh into usable data + * + * Take the information encoded in the input packet, and set the + * state of the emulators virtual ride accordingly. + * + * @param packet The input packet to read from + * @param state The ride state to write to + * @returns 1 on success and 0 on failure + */ +int processInputPacket(char *packet, RideState *state) +{ + /* Check the checksum is correct */ + char checksum = 0; + for (int i = 1; i < INPUT_PACKET_SIZE - 1; i++) + checksum ^= packet[i]; + + if (checksum != packet[INPUT_PACKET_SIZE - 1]) + { + printf("Error: Input checksum failed\n"); + return 0; + } + + /* Command Packet */ + state->Command = packet[1]; + + /* Seat Command Packet */ + state->SeatCommand = packet[2]; + + /* Gun Reaction / Blowers */ + state->PlayerOneBlowFront = (packet[3] & 0b00000001) > 0; + state->PlayerTwoBlowFront = (packet[3] & 0b00000010) > 0; + state->PlayerOneBlowBack = (packet[3] & 0b00000100) > 0; + state->PlayerTwoBlowBack = (packet[3] & 0b00001000) > 0; + state->PlayerOneGunReaction = (packet[3] & 0b00010000) > 0; + state->PlayerTwoGunReaction = (packet[3] & 0b00100000) > 0; + + /* Billboard Lamp */ + if (packet[4] & 0b10000000) + { + if (packet[4] & 0b00000001) + state->BillboardLamp = LAMP_GREEN; + else if (packet[4] & 0b00000010) + state->BillboardLamp = LAMP_BLUE; + else + state->BillboardLamp = LAMP_RED; + } + else + { + state->BillboardLamp = LAMP_OFF; + } + + /* Other Lamp outputs */ + state->ResetLamp = (packet[5] & 0b00000001) > 0; + state->ErrorLamp = (packet[5] & 0b00000010) > 0; + state->SafetyLamp = (packet[5] & 0b00000100) > 0; + state->GameStopLamp = (packet[5] & 0b00001000) > 0; + state->FloorLamp = (packet[5] & 0b00010000) > 0; + state->SpotLamp = (packet[5] & 0b00100000) > 0; + + return 1; +} + +/** + * Get the output packet ready for sending to the Lindbergh + * + * Create the correct output packet from the ride state, taking + * into account button positions and chair reports. + * + * @param packet The output packet to fill up + * @param state The ride state to read from + * @returns 1 on success and 0 on failure + */ +int processOutputPacket(char *packet, RideState *state) +{ + /* Put the sync packet in byte 0 */ + packet[0] = 0xC0; + + /* Unknown Packet */ + packet[1] = 0x00; + + /* Unknown Packet */ + packet[2] = state->CommandReply; // Byte 0x30 (0xFF UPDATE MODE) (0x00 SERIAL CHECKING) (0x1A RIDE STOP) + + /* Ride turn Test Enable */ + packet[3] = state->SeatCommandReply; // Byte 0x31 + + /* Packet 4 contains some buttons */ + packet[4] = 0x00; + if (state->TowerGameStopButton) + packet[4] = packet[4] | 0b00000001; + + if (state->MotionSelectSwitch == MOTION_NORMAL) + packet[4] = packet[4] | 0b00000010; + + if (state->ResetButton) + packet[4] = packet[4] | 0b00000100; + + if (state->RideGameStopButton) + packet[4] = packet[4] | 0b00001000; + + if (state->InitButton) + packet[4] = packet[4] | 0b00010000; + + if (state->RearFootSensor) + packet[4] = packet[4] | 0b00100000; + + if (state->LeftFootSensor) + packet[4] = packet[4] | 0b01000000; + + /* Packet 5 contains some more buttons */ + packet[5] = 0x00; + if (state->RightFootSensor) + packet[5] = packet[5] | 0b00000001; + + if (state->FrontFootSensor) + packet[5] = packet[5] | 0b00000010; + + if (state->RightDoorSensor) + packet[5] = packet[5] | 0b00000100; + + if (state->ArmrestSensor) + packet[5] = packet[5] | 0b00001000; + + if (state->PlayerOneSeatbeltSensor) + packet[5] = packet[5] | 0b00010000; + + if (state->PlayerTwoSeatbeltSensor) + packet[5] = packet[5] | 0b00100000; + + if (state->FrontPositionSensor) + packet[5] = packet[5] | 0b01000000; + + if (state->LeftDoorSensor) + packet[5] = packet[5] | 0b10000000; + + /* Packet 6 contains some more buttons */ + packet[6] = 0x00; + if (state->RearPositionSensor) + packet[6] = packet[6] | 0b00000001; + + if (state->CWLimitSensor) + packet[6] = packet[6] | 0b00000100; + + if (state->CCWLimitSensor) + packet[6] = packet[6] | 0b00001000; + + if (state->MotorPower) + packet[6] = packet[6] | 0b00010000; + + if (state->MotionSelectSwitch == MOTION_MILD) + packet[6] = packet[6] | 0b01000000; + + if (state->MotionSelectSwitch == MOTION_OFF) + packet[6] = packet[6] | 0b10000000; + + /* Unknown Packet */ + packet[7] = 0x00; // Unknown + + /* Used for the USB Update */ + packet[8] = 0x00; // 2ND BOOT (0 = Ignore, 1 = Update Success, 2 = Update Failed, 3 = Unknown) + packet[9] = 0x00; // USB LOADER (0 = Ignore, 1 = Update Success, 2 = Update Failed, 3 = Unknown) + packet[10] = 0x00; // Application (0 = Ignore, 1 = Update Success, 2 = Update Failed, 3 = Unknown) + + packet[11] = 0x00; // 2ND BOOT SUM + packet[12] = 0x00; // USB LOADER SUM + packet[13] = 0x00; // Application SUM + + packet[14] = 0x00; // 2ND BOOT Major + packet[15] = 0x00; // 2ND BOOT Minor + packet[16] = 0x00; // USB LOADER Major + packet[17] = 0x00; // USB LOADER Minor + packet[18] = 0x00; // Application Major + packet[19] = 0x00; // Application Minor + + /* Unknown Packet */ + packet[20] = 0x00; // Unknown + + /* Calculate the checksum and place in the last space */ + char checksum = 0; + for (int i = 1; i < OUTPUT_PACKET_SIZE - 1; i++) + checksum ^= packet[i]; + + packet[OUTPUT_PACKET_SIZE - 1] = checksum; + + return 1; +} + +/** + * Process any physical emulation that should be done + * + * Process the input and output states and decide + * what should be done on each cycle + * + * @param state The ride state to read from and write to + * @returns 1 on success and 0 on failure + */ +int processEmulation(RideState *state) +{ + // Respond with the correct response command + switch (state->Command) + { + case 0x01: // Boot + state->CommandReply = 0x01; // 0xFF to show update screen + break; + case 0x02: // Init check + state->CommandReply = 0x03; + break; + case 0x03: // Reset to defaults confirmation + state->RearFootSensor = 0; + state->LeftFootSensor = 0; + state->FrontFootSensor = 0; + state->RightFootSensor = 0; + state->ArmrestSensor = 0; + state->PlayerOneSeatbeltSensor = 0; + state->PlayerTwoSeatbeltSensor = 0; + break; + case 0x04: // Reset Success + state->CommandReply = 0x05; + break; + case 0x19: // Work out what ADF means? + state->CommandReply = 0x06; + case 0x05: // Coin play, not coined up + state->CommandReply = 0x05; + break; + case 0x06: // Ticket play, waiting for init by attendant (Not sure if this is correct) + state->CommandReply = 0x08; + break; + case 0x08: // Kate Green talking about AMS + state->CommandReply = 0x0A; + break; + case 0x0A: // Intro Video Playing (toggle seatbelts) + state->CommandReply = 0x0A; + state->PlayerOneSeatbeltSensor = !state->PlayerOneSeatbeltSensor; + state->PlayerTwoSeatbeltSensor = !state->PlayerTwoSeatbeltSensor; + break; + case 0x0D: // Passed attract mode + state->CommandReply = 0x0D; + state->PlayerOneSeatbeltSensor = 0; + state->PlayerTwoSeatbeltSensor = 0; + break; + case 0x11: // After game over get out! (Speed up?) + case 0x13: // Test mode + case 0x1A: // Emergency Stop + case 0xFF: // USB Update + default: + state->CommandReply = state->Command; + } + + // Respond with the correct seat response + switch (state->SeatCommand) + { + case 0x01: + state->SeatCommandReply = 1; + break; + case 0x11: + state->SeatCommandReply = 1; + break; + case 0x21: + state->SeatCommandReply = 3; + break; + case 0x31: + state->SeatCommandReply = 5; + break; + case 0x41: + state->SeatCommandReply = 7; + break; + case 0x51: + state->SeatCommandReply = 9; + break; + case 0x61: + state->SeatCommandReply = 11; + break; + default: + break; + } + + return 1; +} + +void printStatus(RideState *state) +{ + int end = 0; + if (state->PlayerOneGunReaction && ++end) + printf("PLAYER_ONE_GUN_REACTION "); + if (state->PlayerTwoGunReaction && ++end) + printf("PLAYER_TWO_GUN_REACTION "); + if (state->PlayerOneBlowBack && ++end) + printf("PLAYER_ONE_BLOW_BACK "); + if (state->PlayerTwoBlowBack && ++end) + printf("PLAYER_TWO_BLOW_BACK "); + if (state->PlayerOneBlowFront && ++end) + printf("PLAYER_ONE_BLOW_FRONT "); + if (state->PlayerTwoBlowFront && ++end) + printf("PLAYER_TWO_BLOW_FRONT "); + if (state->GameStopLamp && ++end) + printf("GAME_STOP_LAMP "); + if (state->ResetLamp && ++end) + printf("RESET_LAMP "); + if (state->ErrorLamp && ++end) + printf("ERROR_LAMP "); + if (state->SafetyLamp && ++end) + printf("SAFETY_LAMP "); + if (state->FloorLamp && ++end) + printf("FLOOR_LAMP "); + if (state->SpotLamp && ++end) + printf("SPOT_LAMP "); + if (state->BillboardLamp != LAMP_OFF && ++end) + { + switch (state->BillboardLamp) + { + case LAMP_RED: + printf("BILLBOARD_LAMP_RED "); + break; + case LAMP_GREEN: + printf("BILLBOARD_LAMP_GREEN "); + break; + case LAMP_BLUE: + printf("BILLBOARD_LAMP_BLUE "); + break; + default: + break; + } + } + if (end) + printf("\n"); +} + +/** + * Pretend to be the ride from SEGA's Special games. + * + * Communicate with a SEGA Lindbergh via serial to + * emulate the inner working of a Special ride. + */ +int initRideboard() +{ return 0; } -ssize_t rideboardWrite(int fd, const void *buf, size_t count) { - return 0; +ssize_t rideboardRead(int fd, void *buf, size_t count) +{ + processOutputPacket((char *)buf, &rideState); + return OUTPUT_PACKET_SIZE; } +ssize_t rideboardWrite(int fd, const void *buf, size_t count) +{ + processInputPacket((char *)buf, &rideState); + processEmulation(&rideState); + return count; +} diff --git a/src/lindbergh/rideboard.h b/src/lindbergh/rideboard.h index c06cb27..88927ff 100644 --- a/src/lindbergh/rideboard.h +++ b/src/lindbergh/rideboard.h @@ -1,4 +1,64 @@ #include +/* Enum to define the colour of the billboard lamp */ +typedef enum +{ + LAMP_OFF = 0, + LAMP_GREEN, + LAMP_BLUE, + LAMP_RED +} LampState; + +/* Enums to represent the different states of the MotionSelectSwitch */ +#define MOTION_OFF 0 +#define MOTION_MILD 1 +#define MOTION_NORMAL 2 + +/* Struct to hold the internal state of the ride */ +typedef struct +{ + /* COMMANDS */ + int Command; + int CommandReply; + int SeatCommand; + int SeatCommandReply; + /* OUTPUTS */ + int PlayerOneBlowFront; + int PlayerOneBlowBack; + int PlayerTwoBlowFront; + int PlayerTwoBlowBack; + int PlayerOneGunReaction; + int PlayerTwoGunReaction; + int ResetLamp; + int ErrorLamp; + int SafetyLamp; + int GameStopLamp; + int FloorLamp; + int SpotLamp; + LampState BillboardLamp; + /* INPUTS */ + int InitButton; + int ResetButton; + int MotionSelectSwitch; // 0 Motion Off, 1 Motion Gentle, 2 Motion Normal + int TowerGameStopButton; + int RideGameStopButton; + int RearFootSensor; + int LeftFootSensor; + int FrontFootSensor; + int RightFootSensor; + int RightDoorSensor; + int LeftDoorSensor; + int ArmrestSensor; + int PlayerOneSeatbeltSensor; + int PlayerTwoSeatbeltSensor; + int RearPositionSensor; + int FrontPositionSensor; + int CCWLimitSensor; + int CWLimitSensor; + int MotorPower; +} RideState; + +int initRideboard(); + ssize_t rideboardRead(int fd, void *buf, size_t count); ssize_t rideboardWrite(int fd, const void *buf, size_t count);