1
0
mirror of synced 2024-11-27 19:10:48 +01:00

Add initial files

This commit is contained in:
Bobby Dilley 2022-07-07 13:34:29 +01:00
commit 9611a34dfc
21 changed files with 1596 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

18
Makefile Normal file
View File

@ -0,0 +1,18 @@
CC=gcc -m32
CFLAGS = -g -O0 -fPIC -m32 -D_GNU_SOURCE -Wall -Werror -Wno-unused-variable -Wno-unused-function
LD = g++ -m32
LDFLAGS = -Wl,-z,defs -rdynamic -static-libstdc++ -static-libgcc -lc -ldl -lGL -lglut -lX11 -lm -lpthread -shared -nostdlib
BUILD = build
OBJS := $(patsubst %.c,%.o,$(wildcard src/lindbergh/*.c))
all: lindbergh.so
lindbergh.so: $(OBJS)
mkdir -p $(BUILD)
$(LD) $(OBJS) $(LDFLAGS) $(CFLAGS) -o $(BUILD)/lindbergh.so
rm -f src/lindbergh/*.o
clean:
rm -rf $(BUILD)

44
README.md Normal file
View File

@ -0,0 +1,44 @@
# SEGA Lindbergh Loader
## Supported Titles
The loader can currently run the following titles:
- The House Of The Dead 4
- The House Of The Dead 4 Special
- SEGA Race TV
- The House Of The Dead EX
- Outrun 2 SP SDX
- 2Spicy
- Rambo
## Building & Running
To build simply run the makefile, and then copy the contents of the build directory into your game directory and run.
```
make
cp build/* ~/the-house-of-the-dead-4/disk0/elf/.
cd ~/the-house-of-the-dead-4/disk0/elf
LD_PRELOAD=$(pwd)/lindbergh.so LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./hod4M.elf
```
## Components
This section lists the components of the emulator and what they do
### Rideboard
This is a high level emulation of the ride control board used in The House Of The Dead 4 Special and Let's Go Jungle Special.
### Driveboard
This is a high level emulation of various force feedback drive boards used in Lindbergh games
### Motion Board
This is a high level emulation of the motion control board used in Outrun 2 SP SDX
### libsegaapi.so
This is an emulation of the driver that games use to route sound out of the Creative Labs soundcard. This routes sound using OpenAL.

242
src/lindbergh/baseboard.c Normal file
View File

@ -0,0 +1,242 @@
#include <stdio.h>
#include <sys/types.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "baseboard.h"
#include "config.h"
#include "jvs.h"
#define SERIAL_STRING "FE11-X018012022X"
#define BASEBOARD_INIT 0x300
#define BASEBOARD_GET_VERSION 0x8004BC02
#define BASEBOARD_SEEK_SHM 0x400
#define BASEBOARD_READ_SRAM 0x601
#define BASEBOARD_WRITE_SRAM 0x600
#define BASEBOARD_REQUEST 0xC020BC06
#define BASEBOARD_RECEIVE 0xC020BC07
#define BASEBOARD_GET_SERIAL 0x120
#define BASEBOARD_WRITE_FLASH 0x180
#define BASEBOARD_GET_SENSE_LINE 0x210
#define BASEBOARD_PROCESS_JVS 0x220
#define BASEBOARD_READY 0x201
typedef struct
{
uint32_t srcAddress;
uint32_t srcSize;
uint32_t destAddress;
uint32_t destSize;
} BaseboardCommand;
BaseboardCommand jvsCommand = {0};
BaseboardCommand serialCommand = {0};
typedef struct
{
uint32_t *data;
uint32_t offset;
uint32_t size;
} readData_t;
typedef struct
{
uint32_t offset;
uint32_t *data;
uint32_t size;
} writeData_t;
FILE *sram = NULL;
unsigned int sharedMemoryIndex = 0;
uint8_t sharedMemory[1024 * 32] = {0};
int selectReply = -1;
int initBaseboard()
{
char *sramPath = getConfig()->sramPath;
sram = fopen(sramPath, "a");
// Create file if it doesn't exist
if (sram == NULL)
{
printf("Error: Cannot open %s\n", sramPath);
return 1;
}
fclose(sram);
sram = fopen(sramPath, "rb+");
fseek(sram, 0, SEEK_SET);
return 0;
}
ssize_t baseboardRead(int fd, void *buf, size_t count)
{
memcpy(buf, &sharedMemory[sharedMemoryIndex], count);
return count;
}
ssize_t baseboardWrite(int fd, const void *buf, size_t count)
{
memcpy(&sharedMemory[sharedMemoryIndex], buf, count);
return count;
}
int baseboardSelect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout)
{
return selectReply;
}
int baseboardIoctl(int fd, unsigned int request, void *data)
{
switch (request)
{
case BASEBOARD_GET_VERSION:
{
uint8_t versionData[4] = {0x00, 0x19, 0x20, 0x07};
memcpy(data, versionData, 4);
return 0;
}
break;
case BASEBOARD_INIT:
{
// selectReply = -1; Considering adding this in
return 0;
}
break;
case BASEBOARD_READY: // Not sure this is what it should be called
{
selectReply = 0;
return 0;
}
break;
case BASEBOARD_SEEK_SHM:
{
sharedMemoryIndex = (unsigned int)data;
return 0;
}
break;
case BASEBOARD_READ_SRAM:
{
readData_t *_data = data;
fseek(sram, _data->offset, SEEK_SET);
fread(_data->data, 1, _data->size, sram);
return 0;
}
break;
case BASEBOARD_WRITE_SRAM:
{
writeData_t *_data = data;
fseek(sram, _data->offset, SEEK_SET);
fwrite(_data->data, 1, _data->size, sram);
return 0;
}
break;
case BASEBOARD_REQUEST:
{
uint32_t *_data = data;
switch (_data[0])
{
case BASEBOARD_GET_SERIAL:
{
serialCommand.destAddress = _data[1];
serialCommand.destSize = _data[2];
}
break;
case BASEBOARD_WRITE_FLASH:
{
printf("Warning: The game attempted to write to the baseboard flash\n");
}
break;
case BASEBOARD_PROCESS_JVS:
{
jvsCommand.srcAddress = _data[1];
jvsCommand.srcSize = _data[2];
jvsCommand.destAddress = _data[3];
jvsCommand.destSize = _data[4];
// memcpy(inputBuffer, &shm[jvsCommand.srcAddress], jvsCommand.srcSize);
// processPacket(&io);
}
break;
case BASEBOARD_GET_SENSE_LINE:
break;
default:
printf("Error: Unknown baseboard command %X\n", _data[0]);
}
// Acknowledge the command
_data[0] |= 0xF0000000;
return 0;
}
break;
case BASEBOARD_RECEIVE:
{
uint32_t *_data = data;
switch (_data[0] & 0xFFF)
{
case BASEBOARD_GET_SERIAL:
{
memcpy(&sharedMemory[serialCommand.destAddress + 96], SERIAL_STRING, strlen(SERIAL_STRING));
}
break;
case BASEBOARD_GET_SENSE_LINE:
{
// _data[2] = getSenseLine();
_data[2] = 3;
}
break;
case BASEBOARD_PROCESS_JVS:
{
// memcpy(&sharedMemory[jvsCommand.destAddress], outputBuffer, outputPacket.length + 3);
_data[2] = jvsCommand.destAddress;
// dp[3] = outputPacket.length + 3;
}
break;
default:
printf("Error: Unknown baseboard receive command %X\n", _data[0] & 0xFFF);
}
// Acknowledge the command
_data[0] |= 0xF0000000;
// Set the status to success
_data[1] = 1;
return 0;
}
break;
default:
printf("Error: Unknown baseboard ioctl %X\n", request);
}
return 0;
}

View File

@ -0,0 +1,9 @@
#include <stdio.h>
int initBaseboard();
ssize_t baseboardRead(int fd, void *buf, size_t count);
ssize_t baseboardWrite(int fd, const void *buf, size_t count);
int baseboardIoctl(int fd, unsigned int request, void *data);
int baseboardSelect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout);

17
src/lindbergh/config.c Normal file
View File

@ -0,0 +1,17 @@
#include <string.h>
#include "config.h"
EmulatorConfig config = {0};
int initConfig() {
config.emulateRideboard = 0;
strcpy(config.eepromPath, "eeprom.bin");
strcpy(config.sramPath, "sram.bin");
return 0;
}
EmulatorConfig *getConfig() {
return &config;
}

16
src/lindbergh/config.h Normal file
View File

@ -0,0 +1,16 @@
#include <stdint.h>
#define MAX_PATH_LENGTH 1024
typedef struct
{
int emulateRideboard;
char eepromPath[MAX_PATH_LENGTH];
char sramPath[MAX_PATH_LENGTH];
} EmulatorConfig;
int initConfig();
EmulatorConfig *getConfig();

View File

View File

111
src/lindbergh/eeprom.c Normal file
View File

@ -0,0 +1,111 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "eeprom.h"
#include "config.h"
#define I2C_SMBUS_BLOCK_MAX 32
#define I2C_GET_FUNCTIONS 0x705
#define I2C_SMBUS_TRANSFER 0x720
#define I2C_SET_SLAVE_MODE 0x703
#define I2C_BUFFER_CLEAR 0x1261
#define I2C_READ 1
#define I2C_SEEK 2
#define I2C_WRITE 3
union i2c_smbus_data {
uint8_t byte;
uint16_t word;
uint8_t block[I2C_SMBUS_BLOCK_MAX + 2];
};
struct i2c_smbus_ioctl_data {
uint8_t read_write;
uint8_t command;
uint32_t size;
union i2c_smbus_data *data;
};
FILE *eeprom = NULL;
int initEeprom()
{
char *eepromPath = getConfig()->eepromPath;
eeprom = fopen(eepromPath, "a");
// Create file if it doesn't exist
if (eeprom == NULL)
{
printf("Error: Cannot open %s\n", eepromPath);
return 1;
}
fclose(eeprom);
eeprom = fopen(eepromPath, "rb+");
fseek(eeprom, 0, SEEK_SET);
return 0;
}
int eepromIoctl(int fd, unsigned int request, void *data)
{
switch (request)
{
case I2C_GET_FUNCTIONS:
{
// The below is copied from what SEGABOOT expects
uint32_t *functions = data;
functions[0] = 0x20000 | 0x100000 | 0x400000 | 0x8000000;
}
break;
case I2C_SMBUS_TRANSFER:
{
struct i2c_smbus_ioctl_data *_data = data;
switch (_data->size)
{
case I2C_READ:
{
fread(&_data->data->byte, 1, sizeof(char), eeprom);
}
break;
case I2C_SEEK:
{
uint16_t address = (_data->command & 0xFF) << 8 | (_data->data->byte & 0xFF);
fseek(eeprom, address, SEEK_SET);
}
break;
case I2C_WRITE:
{
uint16_t address = (_data->command & 0xFF) << 8 | (_data->data->byte & 0xFF);
char val = _data->data->word >> 8 & 0xFF;
fseek(eeprom, address, SEEK_SET);
fwrite(&val, 1, sizeof(char), eeprom);
}
break;
default:
printf("Error: Incorrect I2C transfer size\n");
}
}
break;
case I2C_SET_SLAVE_MODE:
case I2C_BUFFER_CLEAR:
break;
default:
printf("Error: Unkown I2C ioctl %X\n", request);
}
return 0;
}

2
src/lindbergh/eeprom.h Normal file
View File

@ -0,0 +1,2 @@
int initEeprom();
int eepromIoctl(int fd, unsigned int request, void *data);

14
src/lindbergh/graphics.c Normal file
View File

@ -0,0 +1,14 @@
#include <GL/freeglut.h>
#include <dlfcn.h>
FGAPI int FGAPIENTRY glutEnterGameMode()
{
glutCreateWindow("SEGA Lindbergh");
return 1;
}
FGAPI void FGAPIENTRY glutLeaveGameMode()
{
glutDestroyWindow(glutGetWindow());
return;
}

0
src/lindbergh/graphics.h Normal file
View File

200
src/lindbergh/hook.c Normal file
View File

@ -0,0 +1,200 @@
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <linux/sockios.h>
#include "hook.h"
#include "baseboard.h"
#include "config.h"
#include "rideboard.h"
#include "eeprom.h"
#include "jvs.h"
#define HOOK_FILE_NAME "/dev/zero"
#define BASEBOARD 0
#define EEPROM 1
#define SERIAL0 2
#define SERIAL1 3
int hooks[4] = {-1, -1, -1, -1};
void __attribute__((constructor)) hook_init()
{
printf("SEGA Lindbergh Loader\nRobert Dilley 2022\nNot for public consumption\n\n");
initConfig();
if (initEeprom() != 0)
exit(1);
if (initBaseboard() != 0)
exit(1);
if (initJVS() != 0)
exit(1);
}
int open(const char *pathname, int flags)
{
int (*_open)(const char *pathname, int flags) = dlsym(RTLD_NEXT, "open");
if (strcmp(pathname, "/dev/lbb") == 0)
{
hooks[BASEBOARD] = open(HOOK_FILE_NAME, flags);
return hooks[BASEBOARD];
}
if (strcmp(pathname, "/dev/i2c/0") == 0)
{
hooks[EEPROM] = open(HOOK_FILE_NAME, flags);
return hooks[EEPROM];
}
if (strcmp(pathname, "/dev/ttyS0") == 0 || strcmp(pathname, "/dev/tts/0") == 0)
{
hooks[SERIAL0] = open(HOOK_FILE_NAME, flags);
return hooks[SERIAL0];
}
if (strcmp(pathname, "/dev/ttyS1") == 0 || strcmp(pathname, "/dev/tts/1") == 0)
{
hooks[SERIAL1] = open(HOOK_FILE_NAME, flags);
return hooks[SERIAL1];
}
return _open(pathname, flags);
}
int openat(int dirfd, const char *pathname, int flags)
{
int (*_openat)(int dirfd, const char *pathname, int flags) = dlsym(RTLD_NEXT, "openat");
if (strcmp(pathname, "/dev/ttyS0") == 0 || strcmp(pathname, "/dev/ttyS1") == 0 || strcmp(pathname, "/dev/tts/0") == 0 || strcmp(pathname, "/dev/tts/1") == 0)
{
return open(pathname, flags);
}
return _openat(dirfd, pathname, flags);
}
int close(int fd)
{
int (*_close)(int fd) = dlsym(RTLD_NEXT, "close");
for (int i = 0; i < (sizeof hooks / sizeof hooks[0]); i++)
{
if (hooks[i] == fd)
{
hooks[i] = -1;
return 0;
}
}
return _close(fd);
}
ssize_t read(int fd, void *buf, size_t count)
{
int (*_read)(int fd, void *buf, size_t count) = dlsym(RTLD_NEXT, "read");
if (fd == hooks[BASEBOARD])
{
return baseboardRead(fd, buf, count);
}
if (fd == hooks[SERIAL1] && getConfig()->emulateRideboard)
{
return rideboardRead(fd, buf, count);
}
return _read(fd, buf, count);
}
ssize_t write(int fd, const void *buf, size_t count)
{
int (*_write)(int fd, const void *buf, size_t count) = dlsym(RTLD_NEXT, "write");
if (fd == hooks[BASEBOARD])
{
return baseboardWrite(fd, buf, count);
}
if (fd == hooks[SERIAL1] && getConfig()->emulateRideboard)
{
return rideboardWrite(fd, buf, count);
}
return _write(fd, buf, count);
}
int ioctl(int fd, unsigned int request, void *data)
{
int (*_ioctl)(int fd, int request, void *data) = dlsym(RTLD_NEXT, "ioctl");
// Attempt to stop access to the ethernet ports
if ((request == SIOCSIFADDR) || (request == SIOCSIFFLAGS) || (request == SIOCSIFHWADDR) || (request == SIOCSIFHWBROADCAST) || (request == SIOCDELRT) || (request == SIOCADDRT) || (request == SIOCSIFNETMASK))
{
errno = ENXIO;
return -1;
}
if (fd == hooks[EEPROM])
{
return eepromIoctl(fd, request, data);
}
if (fd == hooks[BASEBOARD])
{
return baseboardIoctl(fd, request, data);
}
// Just accept any IOCTL on serial ports and ignore it
if (fd == hooks[SERIAL0] || fd == hooks[SERIAL1])
{
return 0;
}
return _ioctl(fd, request, data);
}
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout)
{
int (*_select)(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout) = dlsym(RTLD_NEXT, "select");
if (readfds != NULL && FD_ISSET(hooks[BASEBOARD], readfds))
{
return baseboardSelect(nfds, readfds, writefds, exceptfds, timeout);
}
if (writefds != NULL && FD_ISSET(hooks[BASEBOARD], writefds))
{
return baseboardSelect(nfds, readfds, writefds, exceptfds, timeout);
}
return _select(nfds, readfds, writefds, exceptfds, timeout);
}
/**
* Hook for the only function provided by kswapapi.so
* @param p No idea this gets discarded
*/
void kswap_collect(void *p)
{
return;
}
/**
* Hook for function used by Primevil
* @param base The number to raise to the exponent
* @param exp The exponent to raise the number to
* @return The result of raising the number to the exponent
*/
float powf(float base, float exponent)
{
return (float)pow((double)base, (double)exponent);
}

0
src/lindbergh/hook.h Normal file
View File

657
src/lindbergh/jvs.c Normal file
View File

@ -0,0 +1,657 @@
#include "jvs.h"
#include <time.h>
#include <string.h>
#include <math.h>
/* The in and out packets used to read and write to and from*/
JVSPacket inputPacket, outputPacket;
/* The in and out buffer used to read and write to and from */
unsigned char outputBuffer[JVS_MAX_PACKET_SIZE], inputBuffer[JVS_MAX_PACKET_SIZE];
/* Holds the status of the sense line */
int senseLine = 3;
JVSIO io = {0};
/**
* Initialise the JVS emulation
*
* Setup the JVS emulation on a specific device path with an
* IO mapping provided.
*
* @param devicePath The linux filepath for the RS485 adapter
* @param capabilitiesSetup The representation of the IO to emulate
* @returns 1 if the device was initialised successfully, 0 otherwise.
*/
int initJVS()
{
io.capabilities.switches = 14;
io.capabilities.coins = 2;
io.capabilities.players = 2;
io.capabilities.analogueInBits = 8;
io.capabilities.rightAlignBits = 0;
io.capabilities.analogueInChannels = 20;
io.capabilities.generalPurposeOutputs = 20;
io.capabilities.commandVersion = 19;
io.capabilities.jvsVersion = 48;
io.capabilities.commsVersion = 16;
strcpy(io.capabilities.name, "SEGA CORPORATION;I/O BD JVS;837-14572;Ver1.00;2005/10");
if (!io.capabilities.rightAlignBits)
{
io.analogueRestBits = 16 - io.capabilities.analogueInBits;
io.gunXRestBits = 16 - io.capabilities.gunXBits;
io.gunYRestBits = 16 - io.capabilities.gunYBits;
}
for (int player = 0; player < (io.capabilities.players + 1); player++)
io.state.inputSwitch[player] = 0;
for (int analogueChannels = 0; analogueChannels < io.capabilities.analogueInChannels; analogueChannels++)
io.state.analogueChannel[analogueChannels] = 0;
for (int rotaryChannels = 0; rotaryChannels < io.capabilities.rotaryChannels; rotaryChannels++)
io.state.rotaryChannel[rotaryChannels] = 0;
for (int player = 0; player < io.capabilities.coins; player++)
io.state.coinCount[player] = 0;
io.analogueMax = pow(2, io.capabilities.analogueInBits) - 1;
io.gunXMax = pow(2, io.capabilities.gunXBits) - 1;
io.gunYMax = pow(2, io.capabilities.gunYBits) - 1;
/* Float the sense line ready for connection */
senseLine = 3;
return 0;
}
/**
* Writes a single feature to an output packet
*
* Writes a single JVS feature, which are specified
* in the JVS spec, to the output packet.
*
* @param outputPacket The packet to write to.
* @param capability The specific capability to write
* @param arg0 The first argument of the capability
* @param arg1 The second argument of the capability
* @param arg2 The final argument of the capability
*/
void writeFeature(JVSPacket *outputPacket, char capability, char arg0, char arg1, char arg2)
{
outputPacket->data[outputPacket->length] = capability;
outputPacket->data[outputPacket->length + 1] = arg0;
outputPacket->data[outputPacket->length + 2] = arg1;
outputPacket->data[outputPacket->length + 3] = arg2;
outputPacket->length += 4;
}
/**
* Write the entire set of features to an output packet
*
* Writes the set of features specified in the JVSCapabilities
* struct to the specified output packet.
*
* @param outputPacket The packet to write to.
* @param capabilities The capabilities object to read from
*/
void writeFeatures(JVSPacket *outputPacket, JVSCapabilities *capabilities)
{
outputPacket->data[outputPacket->length] = REPORT_SUCCESS;
outputPacket->length += 1;
/* Input Functions */
if (capabilities->players)
writeFeature(outputPacket, CAP_PLAYERS, capabilities->players, capabilities->switches, 0x00);
if (capabilities->coins)
writeFeature(outputPacket, CAP_COINS, capabilities->coins, 0x00, 0x00);
if (capabilities->analogueInChannels)
writeFeature(outputPacket, CAP_ANALOG_IN, capabilities->analogueInChannels, capabilities->analogueInBits, 0x00);
if (capabilities->rotaryChannels)
writeFeature(outputPacket, CAP_ROTARY, capabilities->rotaryChannels, 0x00, 0x00);
if (capabilities->keypad)
writeFeature(outputPacket, CAP_KEYPAD, 0x00, 0x00, 0x00);
if (capabilities->gunChannels)
writeFeature(outputPacket, CAP_LIGHTGUN, capabilities->gunXBits, capabilities->gunYBits, capabilities->gunChannels);
if (capabilities->generalPurposeInputs)
writeFeature(outputPacket, CAP_GPI, 0x00, capabilities->generalPurposeInputs, 0x00);
/* Output Functions */
if (capabilities->card)
writeFeature(outputPacket, CAP_CARD, capabilities->card, 0x00, 0x00);
if (capabilities->hopper)
writeFeature(outputPacket, CAP_HOPPER, capabilities->hopper, 0x00, 0x00);
if (capabilities->generalPurposeOutputs)
writeFeature(outputPacket, CAP_GPO, capabilities->generalPurposeOutputs, 0x00, 0x00);
if (capabilities->analogueOutChannels)
writeFeature(outputPacket, CAP_ANALOG_OUT, capabilities->analogueOutChannels, 0x00, 0x00);
if (capabilities->displayOutColumns)
writeFeature(outputPacket, CAP_DISPLAY, capabilities->displayOutColumns, capabilities->displayOutRows, capabilities->displayOutEncodings);
/* Other */
if (capabilities->backup)
writeFeature(outputPacket, CAP_BACKUP, 0x00, 0x00, 0x00);
outputPacket->data[outputPacket->length] = CAP_END;
outputPacket->length += 1;
}
/**
* Processes and responds to an entire JVS packet
*
* Follows the JVS spec and proceses and responds
* to a single entire JVS packet.
*
* @returns The status of the entire operation
*/
JVSStatus processPacket(JVSIO *jvsIO)
{
readPacket(&inputPacket);
/* Check if the packet is for us */
if (inputPacket.destination != BROADCAST && inputPacket.destination != jvsIO->deviceID)
return JVS_STATUS_NOT_FOR_US;
/* Setup the output packet */
outputPacket.length = 0;
outputPacket.destination = BUS_MASTER;
int index = 0;
/* Set the entire packet success line */
outputPacket.data[outputPacket.length++] = STATUS_SUCCESS;
while (index < inputPacket.length - 1)
{
int size = 1;
switch (inputPacket.data[index])
{
/* The arcade hardware sends a reset command and we clear our memory */
case CMD_RESET:
{
size = 2;
io.deviceID = -1;
senseLine = 3;
// printf("CMD_RESET %d\n", senseLine);
}
break;
/* The arcade hardware assigns an address to our IO */
case CMD_ASSIGN_ADDR:
{
size = 2;
io.deviceID = inputPacket.data[index + 1];
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
senseLine = 1;
// printf("CMD_ASSIGN_ADDR %d\n", senseLine);
}
break;
/* Ask for the name of the IO board */
case CMD_REQUEST_ID:
{
// printf("CMD_REQUEST_ID\n");
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
memcpy(&outputPacket.data[outputPacket.length + 1], jvsIO->capabilities.name, strlen(jvsIO->capabilities.name) + 1);
outputPacket.length += strlen(jvsIO->capabilities.name) + 2;
}
break;
/* Asks for version information */
case CMD_COMMAND_VERSION:
{
// printf("CMD_COMMAND_VERSION\n");
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = jvsIO->capabilities.commandVersion;
outputPacket.length += 2;
}
break;
/* Asks for version information */
case CMD_JVS_VERSION:
{
////printf("CMD_JVS_VERSION\n");
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = jvsIO->capabilities.jvsVersion;
outputPacket.length += 2;
}
break;
/* Asks for version information */
case CMD_COMMS_VERSION:
{
////printf("CMD_COMMS_VERSION\n");
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = jvsIO->capabilities.commsVersion;
outputPacket.length += 2;
}
break;
/* Asks what our IO board supports */
case CMD_CAPABILITIES:
{
// printf("CMD_CAPABILITIES\n");
writeFeatures(&outputPacket, &jvsIO->capabilities);
}
break;
/* Asks for the status of our IO boards switches */
case CMD_READ_SWITCHES:
{
// printf("CMD_READ_SWITCHES\n");
size = 3;
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = jvsIO->state.inputSwitch[0];
outputPacket.length += 2;
for (int i = 0; i < inputPacket.data[index + 1]; i++)
{
for (int j = 0; j < inputPacket.data[index + 2]; j++)
{
outputPacket.data[outputPacket.length++] = jvsIO->state.inputSwitch[i + 1] >> (8 - (j * 8));
}
}
}
break;
case CMD_READ_COINS:
{
////printf("CMD_READ_COINS\n");
size = 2;
int numberCoinSlots = inputPacket.data[index + 1];
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
for (int i = 0; i < numberCoinSlots; i++)
{
outputPacket.data[outputPacket.length] = (jvsIO->state.coinCount[i] << 8) & 0x1F;
outputPacket.data[outputPacket.length + 1] = jvsIO->state.coinCount[i] & 0xFF;
outputPacket.length += 2;
}
}
break;
case CMD_READ_ANALOGS:
{
// printf("CMD_READ_ANALOGS\n");
size = 2;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
for (int i = 0; i < inputPacket.data[index + 1]; i++)
{
/* By default left align the data */
int analogueData = jvsIO->state.analogueChannel[i] << jvsIO->analogueRestBits;
outputPacket.data[outputPacket.length] = analogueData >> 8;
outputPacket.data[outputPacket.length + 1] = analogueData;
outputPacket.length += 2;
}
}
break;
case CMD_READ_ROTARY:
{
////printf("CMD_READ_ROTARY\n");
size = 2;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
for (int i = 0; i < inputPacket.data[index + 1]; i++)
{
outputPacket.data[outputPacket.length] = jvsIO->state.rotaryChannel[i] >> 8;
outputPacket.data[outputPacket.length + 1] = jvsIO->state.rotaryChannel[i];
outputPacket.length += 2;
}
}
break;
case CMD_READ_KEYPAD:
{
////printf("CMD_READ_KEYPAD\n");
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = 0x00;
outputPacket.length += 2;
}
break;
case CMD_READ_GPI:
{
////printf("CMD_READ_GPI\n");
size = 2;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
for (int i = 0; i < inputPacket.data[index + 1]; i++)
{
outputPacket.data[outputPacket.length++] = 0x00;
}
}
break;
case CMD_REMAINING_PAYOUT:
{
////printf("CMD_REMAINING_PAYOUT\n");
size = 2;
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = 0;
outputPacket.data[outputPacket.length + 2] = 0;
outputPacket.data[outputPacket.length + 3] = 0;
outputPacket.data[outputPacket.length + 4] = 0;
outputPacket.length += 5;
}
break;
case CMD_SET_PAYOUT:
{
////printf("CMD_SET_PAYOUT\n");
size = 4;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
}
break;
case CMD_WRITE_GPO:
{
////printf("CMD_WRITE_GPO\n");
size = 2 + inputPacket.data[index + 1];
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.length += 1;
}
break;
case CMD_WRITE_GPO_BYTE:
{
////printf("CMD_WRITE_GPO_BYTE\n");
size = 3;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
}
break;
case CMD_WRITE_GPO_BIT:
{
////printf("CMD_WRITE_GPO_BIT\n");
size = 3;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
}
break;
case CMD_WRITE_ANALOG:
{
////printf("CMD_WRITE_ANALOG\n");
size = inputPacket.data[index + 1] * 2 + 2;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
}
break;
case CMD_SUBTRACT_PAYOUT:
{
////printf("CMD_SUBTRACT_PAYOUT\n");
size = 3;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
}
break;
case CMD_WRITE_COINS:
{
////printf("CMD_WRITE_COINS\n");
size = 4;
// - 1 because JVS is 1-indexed, but our array is 0-indexed
int slot_index = inputPacket.data[index + 1] - 1;
int coin_increment = ((int)(inputPacket.data[index + 3]) | ((int)(inputPacket.data[index + 2]) << 8));
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
/* Prevent overflow of coins */
if (coin_increment + jvsIO->state.coinCount[slot_index] > 16383)
coin_increment = 16383 - jvsIO->state.coinCount[slot_index];
jvsIO->state.coinCount[slot_index] += coin_increment;
}
break;
case CMD_WRITE_DISPLAY:
{
////printf("CMD_WRITE_DISPLAY\n");
size = (inputPacket.data[index + 1] * 2) + 2;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
}
break;
case CMD_DECREASE_COINS:
{
////printf("CMD_DECREASE_COINS\n");
size = 4;
// - 1 because JVS is 1-indexed, but our array is 0-indexed
int slot_index = inputPacket.data[index + 1] - 1;
int coin_decrement = ((int)(inputPacket.data[index + 3]) | ((int)(inputPacket.data[index + 2]) << 8));
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
/* Prevent underflow of coins */
if (coin_decrement > jvsIO->state.coinCount[slot_index])
coin_decrement = jvsIO->state.coinCount[slot_index];
jvsIO->state.coinCount[slot_index] -= coin_decrement;
}
break;
case CMD_CONVEY_ID:
{
////printf("CMD_CONVEY_ID\n");
size = 1;
outputPacket.data[outputPacket.length++] = REPORT_SUCCESS;
char idData[100];
for (int i = 1; i < 100; i++)
{
idData[i] = (char)inputPacket.data[index + i];
size++;
if (!inputPacket.data[index + i])
break;
}
printf("CMD_CONVEY_ID = %s\n", idData);
}
break;
/* The touch screen and light gun input, simply using analogue channels */
case CMD_READ_LIGHTGUN:
{
////printf("CMD_READ_LIGHTGUN\n");
size = 2;
int analogueXData = jvsIO->state.gunChannel[0] << jvsIO->gunXRestBits;
int analogueYData = jvsIO->state.gunChannel[1] << jvsIO->gunYRestBits;
outputPacket.data[outputPacket.length] = REPORT_SUCCESS;
outputPacket.data[outputPacket.length + 1] = analogueXData >> 8;
outputPacket.data[outputPacket.length + 2] = analogueXData;
outputPacket.data[outputPacket.length + 3] = analogueYData >> 8;
outputPacket.data[outputPacket.length + 4] = analogueYData;
outputPacket.length += 5;
}
break;
default:
{
printf("Error: JVS command not supported [0x%02hhX]\n", inputPacket.data[index]);
}
}
index += size;
}
writePacket(&outputPacket);
return JVS_STATUS_SUCCESS;
}
/**
* Read a JVS Packet
*
* A single JVS packet is read into the packet pointer
* after it has been received, unescaped and checked
* for any checksum errors.
*
* @param packet The packet to read into
*/
JVSStatus readPacket(JVSPacket *packet)
{
int escape = 0, phase = 0, index = 0, dataIndex = 0, finished = 0;
unsigned char checksum = 0x00;
while (!finished)
{
/* If we encounter a SYNC start again */
if (!escape && (inputBuffer[index] == SYNC))
{
phase = 0;
dataIndex = 0;
index++;
continue;
}
/* If we encounter an ESCAPE byte escape the next byte */
if (!escape && inputBuffer[index] == ESCAPE)
{
escape = 1;
index++;
continue;
}
/* Escape next byte by adding 1 to it */
if (escape)
{
inputBuffer[index]++;
escape = 0;
}
/* Deal with the main bulk of the data */
switch (phase)
{
case 0: // If we have not yet got the address
packet->destination = inputBuffer[index];
checksum = packet->destination & 0xFF;
phase++;
break;
case 1: // If we have not yet got the length
packet->length = inputBuffer[index];
checksum = (checksum + packet->length) & 0xFF;
phase++;
break;
case 2: // If there is still data to read
if (dataIndex == (packet->length - 1))
{
if (checksum != inputBuffer[index])
return JVS_STATUS_ERROR_CHECKSUM;
finished = 1;
break;
}
packet->data[dataIndex++] = inputBuffer[index];
checksum = (checksum + inputBuffer[index]) & 0xFF;
break;
default:
return JVS_STATUS_ERROR;
}
index++;
}
return JVS_STATUS_SUCCESS;
}
/**
* Write a JVS Packet
*
* A single JVS Packet is written to the arcade
* system after it has been escaped and had
* a checksum calculated.
*
* @param packet The packet to send
*/
JVSStatus writePacket(JVSPacket *packet)
{
/* Don't return anything if there isn't anything to write! */
if (packet->length < 2)
return JVS_STATUS_SUCCESS;
/* Get pointer to raw data in packet */
unsigned char *packetPointer = (unsigned char *)packet;
/* Add SYNC and reset buffer */
int checksum = 0;
int outputIndex = 1;
outputBuffer[0] = SYNC;
packet->length++;
/* Write out entire packet */
for (int i = 0; i < packet->length + 1; i++)
{
if (packetPointer[i] == SYNC || packetPointer[i] == ESCAPE)
{
outputBuffer[outputIndex++] = ESCAPE;
outputBuffer[outputIndex++] = (packetPointer[i] - 1);
}
else
{
outputBuffer[outputIndex++] = (packetPointer[i]);
}
checksum = (checksum + packetPointer[i]) & 0xFF;
}
/* Write out escaped checksum */
if (checksum == SYNC || checksum == ESCAPE)
{
outputBuffer[outputIndex++] = ESCAPE;
outputBuffer[outputIndex++] = (checksum - 1);
}
else
{
outputBuffer[outputIndex++] = checksum;
}
return JVS_STATUS_SUCCESS;
}
int getSenseLine()
{
return senseLine;
}
int setSwitch(JVSIO *io, JVSPlayer player, JVSInput switchNumber, int value)
{
if (player > io->capabilities.players)
return 0;
if (value)
{
io->state.inputSwitch[player] |= switchNumber;
}
else
{
io->state.inputSwitch[player] &= ~switchNumber;
}
return 1;
}
int incrementCoin(JVSIO *io, JVSPlayer player, int amount)
{
if (player == SYSTEM)
return 0;
io->state.coinCount[player - 1] = io->state.coinCount[player - 1] + amount;
return 1;
}
int setAnalogue(JVSIO *io, JVSInput channel, int value)
{
io->state.analogueChannel[channel] = value;
return 1;
}

247
src/lindbergh/jvs.h Normal file
View File

@ -0,0 +1,247 @@
#ifndef JVS_H_
#define JVS_H_
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <stdarg.h>
#define JVS_RETRY_COUNT 3
#define JVS_MAX_PACKET_SIZE 255
#define DEVICE_ID 0x01
#define SYNC 0xE0
#define ESCAPE 0xD0
#define BROADCAST 0xFF
#define BUS_MASTER 0x00
#define DEVICE_ADDR_START 0x01
/* Status for the entire packet */
#define STATUS_SUCCESS 0x01
#define STATUS_UNSUPPORTED 0x02 // an unsupported command was sent
#define STATUS_CHECKSUM_FAILURE 0x03 // the checksum on the command packet did not match a computed checksum
#define STATUS_OVERFLOW 0x04 // an overflow occurred while processing the command
/* Reporting for each individual command */
#define REPORT_SUCCESS 0x01 // all went well
#define REPORT_PARAMETER_ERROR1 0x02 // TODO: work out difference between this one and the next
#define REPORT_PARAMETER_ERROR2 0x03
#define REPORT_BUSY 0x04 // some attached hardware was busy, causing the request to fail
/* All of the commands */
#define CMD_RESET 0xF0 // reset bus
#define CMD_RESET_ARG 0xD9 // fixed argument to reset command
#define CMD_ASSIGN_ADDR 0xF1 // assign address to slave
#define CMD_SET_COMMS_MODE 0xF2 // switch communications mode for devices that support it, for compatibility
#define CMD_REQUEST_ID 0x10 // requests an ID string from a device
#define CMD_COMMAND_VERSION 0x11 // gets command format version as two BCD digits packed in a byte
#define CMD_JVS_VERSION 0x12 // gets JVS version as two BCD digits packed in a byte
#define CMD_COMMS_VERSION 0x13 // gets communications version as two BCD digits packed in a byte
#define CMD_CAPABILITIES 0x14 // gets a special capability structure from the device
#define CMD_CONVEY_ID 0x15 // convey ID of main board to device
#define CMD_READ_SWITCHES 0x20 // read switch inputs
#define CMD_READ_COINS 0x21 // read coin inputs
#define CMD_READ_ANALOGS 0x22 // read analog inputs
#define CMD_READ_ROTARY 0x23 // read rotary encoder inputs
#define CMD_READ_KEYPAD 0x24 // read keypad inputs
#define CMD_READ_LIGHTGUN 0x25 // read light gun inputs
#define CMD_READ_GPI 0x26 // read general-purpose inputs
#define CMD_RETRANSMIT 0x2F // ask device to retransmit data
#define CMD_DECREASE_COINS 0x30 // decrease number of coins
#define CMD_WRITE_GPO 0x32 // write to general-purpose outputs
#define CMD_WRITE_ANALOG 0x33 // write to analog outputs
#define CMD_WRITE_DISPLAY 0x34 // write to an alphanumeric display
#define CMD_WRITE_COINS 0x35 // add to coins
#define CMD_REMAINING_PAYOUT 0x2E // read remaining payout
#define CMD_SET_PAYOUT 0x31 // write remaining payout
#define CMD_SUBTRACT_PAYOUT 0x36 // subtract from remaining payout
#define CMD_WRITE_GPO_BYTE 0x37 // write single gpo byte
#define CMD_WRITE_GPO_BIT 0x38 // write single gpo bit
/* Manufacturer specific commands */
#define CMD_MANUFACTURER_START 0x60 // start of manufacturer-specific commands
#define CMD_NAMCO_SPECIFIC 0x70
#define CMD_MANUFACTURER_END 0x7F // end of manufacturer-specific commands
/* Capabilities of the IO board */
#define CAP_END 0x00 // end of structure
#define CAP_PLAYERS 0x01 // player/switch info
#define CAP_COINS 0x02 // coin slot info
#define CAP_ANALOG_IN 0x03 // analog info
#define CAP_ROTARY 0x04 // rotary encoder info
#define CAP_KEYPAD 0x05 // keypad info
#define CAP_LIGHTGUN 0x06 // light gun info
#define CAP_GPI 0x07 // general purpose input info
#define CAP_CARD 0x10 // card system info
#define CAP_HOPPER 0x11 // token hopper info
#define CAP_GPO 0x12 // general purpose output info
#define CAP_ANALOG_OUT 0x13 // analog output info
#define CAP_DISPLAY 0x14 // character display info
#define CAP_BACKUP 0x15 // backup memory
#define ENCODINGS [ "unknown", "ascii numeric", "ascii alphanumeric", "alphanumeric/katakana", "alphanumeric/SHIFT-JIS" ]
#define JVS_MAX_STATE_SIZE 100
#define MAX_JVS_NAME_SIZE 2048
typedef enum
{
BUTTON_TEST = 1 << 7, // System Buttons
BUTTON_TILT_1 = 1 << 6,
BUTTON_TILT_2 = 1 << 5,
BUTTON_TILT_3 = 1 << 4,
BUTTON_TILT_4 = 1 << 3,
BUTTON_TILT_5 = 1 << 2,
BUTTON_TILT_6 = 1 << 1,
BUTTON_TILT_7 = 1 << 0,
BUTTON_START = 1 << 15, // Player Buttons
BUTTON_SERVICE = 1 << 14,
BUTTON_UP = 1 << 13,
BUTTON_DOWN = 1 << 12,
BUTTON_LEFT = 1 << 11,
BUTTON_RIGHT = 1 << 10,
BUTTON_1 = 1 << 9,
BUTTON_2 = 1 << 8,
BUTTON_3 = 1 << 7,
BUTTON_4 = 1 << 6,
BUTTON_5 = 1 << 5,
BUTTON_6 = 1 << 4,
BUTTON_7 = 1 << 3,
BUTTON_8 = 1 << 2,
BUTTON_9 = 1 << 1,
BUTTON_10 = 1 << 0,
ANALOGUE_1 = 0, // Analogue Inputs
ANALOGUE_2 = 1,
ANALOGUE_3 = 2,
ANALOGUE_4 = 3,
ANALOGUE_5 = 4,
ANALOGUE_6 = 5,
ANALOGUE_7 = 6,
ANALOGUE_8 = 7,
ANALOGUE_9 = 8,
ANALOGUE_10 = 9,
ROTARY_1 = 0, // Rotary Inputs
ROTARY_2 = 1,
ROTARY_3 = 2,
ROTARY_4 = 3,
ROTARY_5 = 4,
ROTARY_6 = 5,
ROTARY_7 = 6,
ROTARY_8 = 7,
ROTARY_9 = 8,
ROTARY_10 = 9,
/* Things that aren't actually doable */
COIN = 98,
NONE = 99,
} JVSInput;
typedef enum
{
SYSTEM = 0,
PLAYER_1 = 1,
PLAYER_2 = 2,
PLAYER_3 = 3,
PLAYER_4 = 4,
} JVSPlayer;
typedef struct
{
int coinCount[JVS_MAX_STATE_SIZE];
int inputSwitch[JVS_MAX_STATE_SIZE];
int analogueChannel[JVS_MAX_STATE_SIZE];
int gunChannel[JVS_MAX_STATE_SIZE];
int rotaryChannel[JVS_MAX_STATE_SIZE];
} JVSState;
typedef struct
{
char name[MAX_JVS_NAME_SIZE];
unsigned char commandVersion;
unsigned char jvsVersion;
unsigned char commsVersion;
unsigned char players;
unsigned char switches;
unsigned char coins;
unsigned char analogueInChannels;
unsigned char analogueInBits;
unsigned char rotaryChannels;
unsigned char keypad;
unsigned char gunChannels;
unsigned char gunXBits;
unsigned char gunYBits;
unsigned char generalPurposeInputs;
unsigned char card;
unsigned char hopper;
unsigned char generalPurposeOutputs;
unsigned char analogueOutChannels;
unsigned char displayOutRows;
unsigned char displayOutColumns;
unsigned char displayOutEncodings;
unsigned char backup;
unsigned char rightAlignBits;
char displayName[MAX_JVS_NAME_SIZE];
} JVSCapabilities;
typedef struct JVSIO
{
int deviceID;
int analogueRestBits;
int gunXRestBits;
int gunYRestBits;
int analogueMax;
int gunXMax;
int gunYMax;
JVSState state;
JVSCapabilities capabilities;
struct JVSIO *chainedIO;
} JVSIO;
typedef struct
{
unsigned char destination;
unsigned char length;
unsigned char data[JVS_MAX_PACKET_SIZE];
} JVSPacket;
typedef enum
{
JVS_STATUS_SUCCESS,
JVS_STATUS_NOT_FOR_US,
JVS_STATUS_ERROR,
JVS_STATUS_ERROR_TIMEOUT,
JVS_STATUS_ERROR_CHECKSUM,
JVS_STATUS_ERROR_WRITE_FAIL,
JVS_STATUS_ERROR_UNSUPPORTED_COMMAND,
} JVSStatus;
int initJVS();
JVSStatus processPacket(JVSIO *jvsIO);
JVSStatus readPacket(JVSPacket *packet);
JVSStatus writePacket(JVSPacket *packet);
/* The in and out packets used to read and write to and from*/
extern JVSPacket inputPacket, outputPacket;
/* The in and out buffer used to read and write to and from */
extern unsigned char outputBuffer[JVS_MAX_PACKET_SIZE], inputBuffer[JVS_MAX_PACKET_SIZE];
int getSenseLine();
JVSCapabilities *getCapabilities();
JVSState *getState();
int initIO(JVSIO *io);
int setSwitch(JVSIO *io, JVSPlayer player, JVSInput switchNumber, int value);
int incrementCoin(JVSIO *io, JVSPlayer player, int amount);
int setAnalogue(JVSIO *io, JVSInput channel, int value);
#endif // JVS_H_

View File

View File

14
src/lindbergh/rideboard.c Normal file
View File

@ -0,0 +1,14 @@
#include <stdio.h>
#include <sys/types.h>
#include <stdint.h>
#include "rideboard.h"
ssize_t rideboardRead(int fd, void *buf, size_t count) {
return 0;
}
ssize_t rideboardWrite(int fd, const void *buf, size_t count) {
return 0;
}

View File

@ -0,0 +1,4 @@
#include <stdio.h>
ssize_t rideboardRead(int fd, void *buf, size_t count);
ssize_t rideboardWrite(int fd, const void *buf, size_t count);