Add initial files
This commit is contained in:
commit
9611a34dfc
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
18
Makefile
Normal file
18
Makefile
Normal 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
44
README.md
Normal 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
242
src/lindbergh/baseboard.c
Normal 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;
|
||||
}
|
9
src/lindbergh/baseboard.h
Normal file
9
src/lindbergh/baseboard.h
Normal 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
17
src/lindbergh/config.c
Normal 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
16
src/lindbergh/config.h
Normal 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();
|
||||
|
||||
|
||||
|
0
src/lindbergh/driveboard.c
Normal file
0
src/lindbergh/driveboard.c
Normal file
0
src/lindbergh/driveboard.h
Normal file
0
src/lindbergh/driveboard.h
Normal file
111
src/lindbergh/eeprom.c
Normal file
111
src/lindbergh/eeprom.c
Normal 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
2
src/lindbergh/eeprom.h
Normal file
@ -0,0 +1,2 @@
|
||||
int initEeprom();
|
||||
int eepromIoctl(int fd, unsigned int request, void *data);
|
14
src/lindbergh/graphics.c
Normal file
14
src/lindbergh/graphics.c
Normal 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
0
src/lindbergh/graphics.h
Normal file
200
src/lindbergh/hook.c
Normal file
200
src/lindbergh/hook.c
Normal 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
0
src/lindbergh/hook.h
Normal file
657
src/lindbergh/jvs.c
Normal file
657
src/lindbergh/jvs.c
Normal 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
247
src/lindbergh/jvs.h
Normal 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_
|
0
src/lindbergh/motionboard.c
Normal file
0
src/lindbergh/motionboard.c
Normal file
0
src/lindbergh/motionboard.h
Normal file
0
src/lindbergh/motionboard.h
Normal file
14
src/lindbergh/rideboard.c
Normal file
14
src/lindbergh/rideboard.c
Normal 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;
|
||||
}
|
||||
|
4
src/lindbergh/rideboard.h
Normal file
4
src/lindbergh/rideboard.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user