Add roel's code
This commit is contained in:
parent
fdd590a2f8
commit
70b7824419
7
Makefile
7
Makefile
@ -1,5 +1,5 @@
|
||||
CC=gcc -m32
|
||||
CFLAGS = -g -O0 -fPIC -m32 -D_GNU_SOURCE -Wall -Werror -Wno-unused-variable -Wno-unused-function
|
||||
CFLAGS = -g -O0 -fPIC -m32 -D_GNU_SOURCE -Wall -Werror -Wno-unused-variable -Wno-unused-function -DLOG_USE_COLOR
|
||||
LD = g++ -m32
|
||||
LDFLAGS = -Wl,-z,defs -rdynamic -static-libstdc++ -static-libgcc -lc -ldl -lGL -lglut -lX11 -lm -lpthread -shared -nostdlib
|
||||
|
||||
@ -7,7 +7,7 @@ BUILD = build
|
||||
|
||||
OBJS := $(patsubst %.c,%.o,$(wildcard src/lindbergh/*.c))
|
||||
|
||||
all: lindbergh.so libsegaapi.so libkswapapi.so
|
||||
all: lindbergh.so libsegaapi.so
|
||||
|
||||
lindbergh.so: $(OBJS)
|
||||
mkdir -p $(BUILD)
|
||||
@ -21,8 +21,5 @@ libsegaapi.so: src/libsegaapi/segaapi.o
|
||||
$(LIBSEGA_LD) $(LIBSEGA_LDFLAGS) src/libsegaapi/segaapi.o -L/usr/lib/i386-linux-gnu -lalut -fPIC -shared -o $(BUILD)/libsegaapi.so
|
||||
rm -f src/libsegaapi/*.o
|
||||
|
||||
libkswapapi.so: src/libkswapapi/libkswapapi.o
|
||||
$(LIBSEGA_LD) $(LIBSEGA_LDFLAGS) src/libkswapapi/libkswapapi.o -L/usr/lib/i386-linux-gnu -fPIC -shared -o $(BUILD)/libkswapapi.so
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD)
|
||||
|
11
README.md
11
README.md
@ -23,13 +23,6 @@ sudo apt install libstdc++5:i386
|
||||
|
||||
## Building & Running
|
||||
|
||||
This emulator will need access to the input devices and serial devices on Linux. Before running this emulator you should add your user account to the following groups and then _restart your computer_.
|
||||
|
||||
```
|
||||
sudo addgroup $USER dialout
|
||||
sudo addgroup $USER input
|
||||
```
|
||||
|
||||
To build, run the makefile, and then copy the contents of the build directory into your game directory and run.
|
||||
|
||||
```
|
||||
@ -39,12 +32,10 @@ cd ~/the-house-of-the-dead-4/disk0/elf
|
||||
LD_PRELOAD=lindbergh.so LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./hod4M.elf
|
||||
```
|
||||
|
||||
Some games will require extra libraries like `libposixtime.so`. These can be found in any dumps of the Lindbergh CF image.
|
||||
It is likely that the games will require various other libraries from the Lindbergh system such as `libkswapapi.so` and `libposixtime.so`. These can be found in any dumps of the Lindbergh CF image.
|
||||
|
||||
A default configuration file is provided in `docs/lindbergh.conf`. It should be placed in the same folder as the game is run from. If no config file is present a default setting will be used.
|
||||
|
||||
I recomend that you do not run this as root, and instead use the usergroups for input/dialout to give the emulator access to what it needs. The Lindbergh games expect full control of the Linux OS and with root privilages it is possible that they could cause damage to your computer.
|
||||
|
||||
## Controls
|
||||
|
||||
Currently the controls are set up for The House of the Dead 4.
|
||||
|
@ -1,10 +0,0 @@
|
||||
#include "libkswapapi.h"
|
||||
|
||||
/**
|
||||
* Hook for the only function provided by kswapapi.so
|
||||
* @param p No idea this gets discarded
|
||||
*/
|
||||
void kswap_collect(void *p)
|
||||
{
|
||||
return;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
#ifndef __LIBKSWAP_H
|
||||
#define __LIBKSWAP_H
|
||||
|
||||
/**
|
||||
* Hook for the only function provided by kswapapi.so
|
||||
* @param p No idea this gets discarded
|
||||
*/
|
||||
void kswap_collect(void *p);
|
||||
|
||||
#endif /* __LIBKSWAP_H */
|
Binary file not shown.
@ -1,27 +1,16 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <linux/serial.h>
|
||||
#include <termios.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/serial.h>
|
||||
#include <stdio.h> /* Standard input/output definitions */
|
||||
#include <string.h> /* String function definitions */
|
||||
#include <unistd.h> /* UNIX standard function definitions */
|
||||
#include <fcntl.h> /* File control definitions */
|
||||
#include <errno.h> /* Error number definitions */
|
||||
#include <stdlib.h> /* Standard library functions like malloc, free, exit, and atoi */
|
||||
|
||||
#include "log.h"
|
||||
#include "baseboard.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "jvs.h"
|
||||
#include "serial.h"
|
||||
#include "jvsserial.h"
|
||||
|
||||
|
||||
#define SERIAL_STRING "FE11-X018012022X"
|
||||
|
||||
@ -38,29 +27,26 @@
|
||||
#define BASEBOARD_PROCESS_JVS 0x220
|
||||
#define BASEBOARD_READY 0x201
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t srcAddress;
|
||||
uint32_t srcSize;
|
||||
uint32_t destAddress;
|
||||
uint32_t destSize;
|
||||
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;
|
||||
typedef struct {
|
||||
uint32_t *data;
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
} readData_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t *data;
|
||||
uint32_t size;
|
||||
typedef struct {
|
||||
uint32_t offset;
|
||||
uint32_t *data;
|
||||
uint32_t size;
|
||||
} writeData_t;
|
||||
|
||||
FILE *sram = NULL;
|
||||
@ -71,231 +57,251 @@ uint8_t sharedMemory[1024 * 32] = {0};
|
||||
int selectReply = -1;
|
||||
int jvsFileDescriptor = -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);
|
||||
|
||||
if (getConfig()->emulateJVS == 0 && strcmp(getConfig()->jvsPath, "none") != 0)
|
||||
{
|
||||
jvsFileDescriptor = open(getConfig()->jvsPath, O_RDWR | O_NOCTTY | O_SYNC | O_NDELAY);
|
||||
if (jvsFileDescriptor < 0)
|
||||
{
|
||||
printf("Error: Failed to open %s for JVS\n", getConfig()->jvsPath);
|
||||
/**
|
||||
* Set up the fake Lindy baseboard
|
||||
* @return
|
||||
*/
|
||||
int initBaseboard() {
|
||||
// TODO: move SRAM part to a dedicated function
|
||||
char *sramPath = getConfig()->sramPath;
|
||||
sram = fopen(sramPath, "a");
|
||||
// Create file if it doesn't exist
|
||||
if (sram == NULL) {
|
||||
log_error("Cannot open %s", sramPath);
|
||||
return 1;
|
||||
}
|
||||
setSerialAttributes(jvsFileDescriptor, B115200);
|
||||
}
|
||||
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: // bcCmdSysInfoGetReq
|
||||
{
|
||||
serialCommand.destAddress = _data[1];
|
||||
serialCommand.destSize = _data[2];
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_WRITE_FLASH: // bcCmdSysFlashWrite
|
||||
{
|
||||
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, &sharedMemory[jvsCommand.srcAddress], jvsCommand.srcSize);
|
||||
|
||||
if (getConfig()->emulateJVS)
|
||||
{
|
||||
processPacket();
|
||||
}
|
||||
else if (jvsFileDescriptor >= 0)
|
||||
{
|
||||
|
||||
write(jvsFileDescriptor, inputBuffer, jvsCommand.srcSize);
|
||||
|
||||
for (int i = 0; i < jvsCommand.srcSize; i++)
|
||||
{
|
||||
if (inputBuffer[i] == 0xF0)
|
||||
{
|
||||
setSenseLine(3);
|
||||
}
|
||||
else if (inputBuffer[i] == 0xF1)
|
||||
{
|
||||
setSenseLine(1);
|
||||
}
|
||||
// Config disable JVS emulation and jvsPath is set, we open serial port for JVS interface
|
||||
if (getConfig()->emulateJVS == 0 && strcmp(getConfig()->jvsPath, "none") != 0) {
|
||||
jvsFileDescriptor = openJVSSerial(getConfig()->jvsPath);
|
||||
if (jvsFileDescriptor < 0) {
|
||||
log_error("Failed to open '%s' for JVS", getConfig()->jvsPath);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the serial port settings, so it acts as a JVS interface
|
||||
initJVSSerial(jvsFileDescriptor);
|
||||
|
||||
// Start a thread to listen for serial port
|
||||
startJVSFrameThread(&jvsFileDescriptor);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void baseboardIoctlRequest(uint32_t *_data) {
|
||||
|
||||
switch (_data[0]) {
|
||||
|
||||
case BASEBOARD_GET_SERIAL: // bcCmdSysInfoGetReq
|
||||
{
|
||||
serialCommand.destAddress = _data[1];
|
||||
serialCommand.destSize = _data[2];
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_WRITE_FLASH: // bcCmdSysFlashWrite
|
||||
{
|
||||
log_warn("The game attempted to write to the baseboard flash.");
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_PROCESS_JVS: {
|
||||
jvsCommand.srcAddress = _data[1];
|
||||
jvsCommand.srcSize = _data[2];
|
||||
jvsCommand.destAddress = _data[3];
|
||||
jvsCommand.destSize = _data[4];
|
||||
memcpy(inputBuffer, &sharedMemory[jvsCommand.srcAddress], jvsCommand.srcSize);
|
||||
|
||||
char hex_string[(jvsCommand.srcSize * 3) + 1];
|
||||
int hex_string_len = 0;
|
||||
for (int i = 0; i < jvsCommand.srcSize; i++) {
|
||||
hex_string_len += sprintf(hex_string+hex_string_len, "%02X ", (&sharedMemory[jvsCommand.destAddress])[i]);
|
||||
}
|
||||
log_trace("JVS: Writing %s", hex_string);
|
||||
|
||||
if (getConfig()->emulateJVS) {
|
||||
processPacket();
|
||||
} else if (jvsFileDescriptor >= 0) {
|
||||
// F0 D9 Command: Reset (RESET)
|
||||
for (int i = 0; i < jvsCommand.srcSize; i++) {
|
||||
write(jvsFileDescriptor, &inputBuffer[i], 1);
|
||||
if (inputBuffer[i] == 0xF0) {
|
||||
log_trace("JVS: got 0xF0 (Reset), soft senseLine is set to 3.");
|
||||
setSenseLine(3);
|
||||
}
|
||||
|
||||
if (inputBuffer[i] == 0xF1) {
|
||||
log_trace("JVS: got 0xF1 (Set Address), soft senseLine is set to 1.");
|
||||
setSenseLine(1);
|
||||
}
|
||||
}
|
||||
|
||||
log_trace("JVS: Hardware Control lines: CTS %02X - DSR %02X - DCD %02X", getCTS(jvsFileDescriptor), getDSR(jvsFileDescriptor), getDCD(jvsFileDescriptor));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_GET_SENSE_LINE:
|
||||
log_trace("JVS DEBUG: GetSenseLine");
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("Unknown baseboard command %X", _data[0]);
|
||||
}
|
||||
|
||||
// Acknowledge the command
|
||||
_data[0] |= 0xF0000000;
|
||||
}
|
||||
|
||||
|
||||
void baseboardIoctlReceive(uint32_t *_data) {
|
||||
switch (_data[0] & 0xFFF) {
|
||||
|
||||
case BASEBOARD_GET_SERIAL: {
|
||||
memcpy(&sharedMemory[serialCommand.destAddress + 96], SERIAL_STRING, strlen(SERIAL_STRING));
|
||||
_data[1] = 1; // Set the status to success
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_GET_SENSE_LINE: {
|
||||
/*
|
||||
* Values are:
|
||||
* 3 = no device, after a RESET
|
||||
* 1 = address assigned
|
||||
*/
|
||||
if (getConfig()->emulateJVS) {
|
||||
_data[2] = getSenseLine();
|
||||
_data[1] = 1; // Set the status to success
|
||||
} else {
|
||||
_data[2] = getSenseLine();
|
||||
_data[1] = 1;
|
||||
}
|
||||
log_trace("JVS: GetSenseLine: %02X - %02X", _data[2], _data[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_PROCESS_JVS: {
|
||||
if (getConfig()->emulateJVS) {
|
||||
memcpy(&sharedMemory[jvsCommand.destAddress], outputBuffer, outputPacket.length + 3);
|
||||
_data[2] = jvsCommand.destAddress;
|
||||
_data[3] = outputPacket.length + 3;
|
||||
_data[1] = 1; // Set the status to success
|
||||
} else if (jvsFileDescriptor >= 0) {
|
||||
jvsFrame frame = readJVSFrameFromThread();
|
||||
memcpy(&sharedMemory[jvsCommand.destAddress], frame.buffer, frame.size);
|
||||
|
||||
_data[2] = jvsCommand.destAddress;
|
||||
_data[3] = frame.size;
|
||||
_data[1] = frame.ready;
|
||||
}
|
||||
|
||||
if (_data[3] > 0) {
|
||||
log_trace("JVS: Data extraction: Ready: %d - Address: %d - Length: %d", _data[1], _data[2], _data[3]);
|
||||
log_trace("JVS: Hardware Control lines: CTS %02X - DSR %02X - DCD %02X", getCTS(jvsFileDescriptor), getDSR(jvsFileDescriptor), getDCD(jvsFileDescriptor));
|
||||
char hex_string[(_data[3] * 3) + 1];
|
||||
int hex_string_len = 0;
|
||||
for (int i = 0; i < _data[3]; i++) {
|
||||
hex_string_len += sprintf(hex_string+hex_string_len, "%02X ", (&sharedMemory[jvsCommand.destAddress])[i]);
|
||||
}
|
||||
log_trace("JVS: Reading %s", hex_string);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("Unknown baseboard receive command %X", _data[0] & 0xFFF);
|
||||
}
|
||||
|
||||
// Acknowledge the command
|
||||
_data[0] |= 0xF0000000;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
baseboardIoctlRequest(_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_RECEIVE: {
|
||||
uint32_t *_data = data;
|
||||
baseboardIoctlReceive(_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("Unknown baseboard ioctl %X", request);
|
||||
}
|
||||
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));
|
||||
_data[1] = 1; // Set the status to success
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_GET_SENSE_LINE:
|
||||
{
|
||||
_data[2] = getSenseLine();
|
||||
_data[1] = 1; // Set the status to success
|
||||
}
|
||||
break;
|
||||
|
||||
case BASEBOARD_PROCESS_JVS:
|
||||
{
|
||||
if (getConfig()->emulateJVS)
|
||||
{
|
||||
memcpy(&sharedMemory[jvsCommand.destAddress], outputBuffer, outputPacket.length + 3);
|
||||
_data[2] = jvsCommand.destAddress;
|
||||
_data[3] = outputPacket.length + 3;
|
||||
_data[1] = 1; // Set the status to success
|
||||
}
|
||||
else if (jvsFileDescriptor >= 0)
|
||||
{
|
||||
int count = readBytes(jvsFileDescriptor, &sharedMemory[jvsCommand.destAddress], 255);
|
||||
|
||||
if (count == -1)
|
||||
count = 0;
|
||||
|
||||
_data[2] = jvsCommand.destAddress;
|
||||
_data[3] = count;
|
||||
_data[1] = (count > 0); // Success if we receive any sort of data back
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Error: Unknown baseboard receive command %X\n", _data[0] & 0xFFF);
|
||||
}
|
||||
|
||||
// Acknowledge the command
|
||||
_data[0] |= 0xF0000000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Error: Unknown baseboard ioctl %X\n", request);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
EmulatorConfig config = {0};
|
||||
|
||||
@ -149,7 +150,7 @@ int readConfig(FILE *configFile, EmulatorConfig *config)
|
||||
}
|
||||
|
||||
else
|
||||
printf("Error: Unknown settings command %s\n", command);
|
||||
log_error("Unknown settings command %s", command);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -173,14 +174,14 @@ int initConfig()
|
||||
config.height = 768;
|
||||
if (detectGame() != 0)
|
||||
{
|
||||
printf("Warning: Unsure what game this is, using default configuration values");
|
||||
log_warn("Unsure what game this is, using default configuration values");
|
||||
}
|
||||
|
||||
configFile = fopen(CONFIG_PATH, "r");
|
||||
|
||||
if (configFile == NULL)
|
||||
{
|
||||
printf("Warning: Cannot open %s, using default values\n", CONFIG_PATH);
|
||||
log_warn("Cannot open %s, using default values", CONFIG_PATH);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "eeprom.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
#define I2C_SMBUS_BLOCK_MAX 32
|
||||
#define I2C_GET_FUNCTIONS 0x705
|
||||
@ -38,7 +39,7 @@ int initEeprom()
|
||||
// Create file if it doesn't exist
|
||||
if (eeprom == NULL)
|
||||
{
|
||||
printf("Error: Cannot open %s\n", eepromPath);
|
||||
log_error("Cannot open %s", eepromPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -97,7 +98,7 @@ int eepromIoctl(int fd, unsigned int request, void *data)
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Error: Incorrect I2C transfer size\n");
|
||||
log_error("Incorrect I2C transfer size");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -107,7 +108,7 @@ int eepromIoctl(int fd, unsigned int request, void *data)
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Error: Unkown I2C ioctl %X\n", request);
|
||||
log_error("Unkown I2C ioctl %X", request);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "config.h"
|
||||
#include "jvs.h"
|
||||
#include "securityboard.h"
|
||||
#include "log.h"
|
||||
|
||||
int gameModeWidth = -1;
|
||||
int gameModeHeight = -1;
|
||||
@ -51,7 +52,7 @@ FGAPI void FGAPIENTRY glutSetCursor(int cursor)
|
||||
|
||||
FGAPI void FGAPIENTRY glutGameModeString(const char *string)
|
||||
{
|
||||
printf("glutGameModeString: %s\n", string);
|
||||
log_info("glutGameModeString: %s", string);
|
||||
|
||||
char gameModeString[1024];
|
||||
strcpy(gameModeString, string);
|
||||
@ -79,7 +80,7 @@ FGAPI void FGAPIENTRY glutGameModeString(const char *string)
|
||||
|
||||
if (getConfig()->width != width || getConfig()->height != height)
|
||||
{
|
||||
printf("Warning: Game is overriding resolution settings to %dX%d\n", width, height);
|
||||
log_warn("Game is overriding resolution settings to %dX%d", width, height);
|
||||
getConfig()->width = width;
|
||||
getConfig()->height = height;
|
||||
}
|
||||
@ -98,7 +99,7 @@ Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int
|
||||
// attributes->override_redirect = False;
|
||||
|
||||
Window window = _XCreateWindow(display, parent, x, y, width, height, border_width, depth, class, visual, valueMask, attributes);
|
||||
printf("XCreateWindow Resolution %d %d %d %d\n", x, y, width, height);
|
||||
log_info("XCreateWindow Resolution %d %d %d %d", x, y, width, height);
|
||||
|
||||
if (getConfig()->fullscreen)
|
||||
{
|
||||
@ -200,8 +201,8 @@ int XNextEvent(Display *display, XEvent *event_return)
|
||||
|
||||
case MotionNotify:
|
||||
{
|
||||
setAnalogue(ANALOGUE_1, ((double)event_return->xmotion.x / (double)getConfig()->width) * 255);
|
||||
setAnalogue(ANALOGUE_2, ((double)event_return->xmotion.y / (double)getConfig()->height) * 255);
|
||||
setAnalogue(ANALOGUE_1, ((double)event_return->xmotion.x / (double)getConfig()->width) * 1024.0);
|
||||
setAnalogue(ANALOGUE_2, ((double)event_return->xmotion.y / (double)getConfig()->height) * 1024.0);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "rideboard.h"
|
||||
#include "securityboard.h"
|
||||
#include "patch.h"
|
||||
#include "log.h"
|
||||
|
||||
#define HOOK_FILE_NAME "/dev/zero"
|
||||
|
||||
@ -41,16 +42,24 @@ int fileRead[2] = {0, 0};
|
||||
|
||||
uint16_t basePortAddress = 0xFFFF;
|
||||
|
||||
/**
|
||||
* Signal handler for the SIGSEGV signal, which is triggered when a process tries to access an illegal memory location.
|
||||
* @param signal
|
||||
* @param info
|
||||
* @param ptr
|
||||
*/
|
||||
static void handleSegfault(int signal, siginfo_t *info, void *ptr)
|
||||
{
|
||||
ucontext_t *ctx = ptr;
|
||||
|
||||
// Get the address of the instruction causing the segfault
|
||||
uint8_t *code = (uint8_t *)ctx->uc_mcontext.gregs[REG_EIP];
|
||||
|
||||
switch (*code)
|
||||
{
|
||||
case 0xED:
|
||||
{
|
||||
// Get the port number from the EDX register
|
||||
uint16_t port = ctx->uc_mcontext.gregs[REG_EDX] & 0xFFFF;
|
||||
|
||||
// The first port called is usually random, but everything after that
|
||||
@ -60,9 +69,11 @@ static void handleSegfault(int signal, siginfo_t *info, void *ptr)
|
||||
if (basePortAddress == 0xFFFF)
|
||||
basePortAddress = port;
|
||||
|
||||
// Adjust the port number if necessary
|
||||
if (port > 0x38)
|
||||
port = port - basePortAddress;
|
||||
|
||||
// Call the security board input function with the port number and data
|
||||
securityBoardIn(port, (uint32_t *)&(ctx->uc_mcontext.gregs[REG_EAX]));
|
||||
|
||||
ctx->uc_mcontext.gregs[REG_EIP]++;
|
||||
@ -70,15 +81,17 @@ static void handleSegfault(int signal, siginfo_t *info, void *ptr)
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xE7: // OUT IMMIDIATE
|
||||
case 0xE7: // OUT IMMEDIATE
|
||||
{
|
||||
// Increment the instruction pointer by two to skip over this instruction
|
||||
ctx->uc_mcontext.gregs[REG_EIP] += 2;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xE6: // OUT IMMIDIATE
|
||||
case 0xE6: // OUT IMMEDIATE
|
||||
{
|
||||
// Increment the instruction pointer by two to skip over this instruction
|
||||
ctx->uc_mcontext.gregs[REG_EIP] += 2;
|
||||
return;
|
||||
}
|
||||
@ -102,7 +115,7 @@ static void handleSegfault(int signal, siginfo_t *info, void *ptr)
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Warning: Skipping SEGFAULT %X\n", *code);
|
||||
log_warn("Skipping SEGFAULT %X", *code);
|
||||
ctx->uc_mcontext.gregs[REG_EIP]++;
|
||||
// abort();
|
||||
}
|
||||
@ -111,6 +124,8 @@ static void handleSegfault(int signal, siginfo_t *info, void *ptr)
|
||||
|
||||
void __attribute__((constructor)) hook_init()
|
||||
{
|
||||
// "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
|
||||
log_set_level(1);
|
||||
printf("SEGA Lindbergh Loader\nRobert Dilley 2022\nNot for public consumption\n\n");
|
||||
|
||||
// Implement SIGSEGV handler
|
||||
@ -150,7 +165,7 @@ void __attribute__((constructor)) hook_init()
|
||||
|
||||
securityBoardSetDipResolution(getConfig()->width, getConfig()->height);
|
||||
|
||||
printf("Loader init success\n");
|
||||
log_info("Loader init success");
|
||||
}
|
||||
|
||||
int open(const char *pathname, int flags)
|
||||
@ -162,14 +177,14 @@ int open(const char *pathname, int flags)
|
||||
if (strcmp(pathname, "/dev/lbb") == 0)
|
||||
{
|
||||
hooks[BASEBOARD] = _open(HOOK_FILE_NAME, flags);
|
||||
printf("Baseboard opened %d\n", hooks[BASEBOARD]);
|
||||
log_info("Baseboard opened %d", hooks[BASEBOARD]);
|
||||
return hooks[BASEBOARD];
|
||||
}
|
||||
|
||||
if (strcmp(pathname, "/dev/i2c/0") == 0)
|
||||
{
|
||||
hooks[EEPROM] = _open(HOOK_FILE_NAME, flags);
|
||||
printf("EEPROM opened %d\n", hooks[EEPROM]);
|
||||
log_info("EEPROM opened %d", hooks[EEPROM]);
|
||||
return hooks[EEPROM];
|
||||
}
|
||||
|
||||
@ -179,7 +194,7 @@ int open(const char *pathname, int flags)
|
||||
return -1;
|
||||
|
||||
hooks[SERIAL0] = _open(HOOK_FILE_NAME, flags);
|
||||
printf("SERIAL0 Opened %d\n", hooks[SERIAL0]);
|
||||
log_info("SERIAL0 Opened %d", hooks[SERIAL0]);
|
||||
return hooks[SERIAL0];
|
||||
}
|
||||
|
||||
@ -189,7 +204,7 @@ int open(const char *pathname, int flags)
|
||||
return -1;
|
||||
|
||||
hooks[SERIAL1] = _open(HOOK_FILE_NAME, flags);
|
||||
printf("SERIAL1 opened %d\n", hooks[SERIAL1]);
|
||||
log_info("SERIAL1 opened %d", hooks[SERIAL1]);
|
||||
return hooks[SERIAL1];
|
||||
}
|
||||
|
||||
@ -215,7 +230,6 @@ int sem_wait(sem_t *sem)
|
||||
FILE *fopen(const char *restrict pathname, const char *restrict mode)
|
||||
{
|
||||
FILE *(*_fopen)(const char *restrict pathname, const char *restrict mode) = dlsym(RTLD_NEXT, "fopen");
|
||||
// printf("fopen %s\n", pathname);
|
||||
|
||||
if (strcmp(pathname, "/root/lindbergrc") == 0)
|
||||
{
|
||||
@ -480,6 +494,10 @@ int sem_wait(sem_t *sem)
|
||||
/**
|
||||
* Hook function used by Harley Davidson to change IPs to localhost
|
||||
* Currently does nothing.
|
||||
* @param sockfd
|
||||
* @param addr
|
||||
* @param addrlen
|
||||
* @return
|
||||
*/
|
||||
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
@ -490,7 +508,7 @@ int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
// Change the IP to connect to to 127.0.0.1
|
||||
// in_pointer->sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
char *some_addr = inet_ntoa(in_pointer->sin_addr);
|
||||
printf("Connecting to %s\n", some_addr);
|
||||
log_info("Connecting to %s", some_addr);
|
||||
|
||||
return _connect(sockfd, addr, addrlen);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ int initJVS()
|
||||
io.capabilities.switches = 14;
|
||||
io.capabilities.coins = 2;
|
||||
io.capabilities.players = 2;
|
||||
io.capabilities.analogueInBits = 8;
|
||||
io.capabilities.analogueInBits = 10;
|
||||
io.capabilities.rightAlignBits = 0;
|
||||
io.capabilities.analogueInChannels = 20;
|
||||
io.capabilities.generalPurposeOutputs = 20;
|
||||
|
274
src/lindbergh/jvsserial.c
Normal file
274
src/lindbergh/jvsserial.c
Normal file
@ -0,0 +1,274 @@
|
||||
#include <stdio.h> /* Standard input/output definitions */
|
||||
#include <string.h> /* String function definitions */
|
||||
#include <unistd.h> /* UNIX standard function definitions */
|
||||
#include <fcntl.h> /* File control definitions */
|
||||
#include <errno.h> /* Error number definitions */
|
||||
#include <termios.h> /* POSIX terminal control definitions */
|
||||
#include <sys/ioctl.h> /* Ioctl function to control device drivers in the kernel */
|
||||
#include <stdlib.h> /* Standard library functions like malloc, free, exit, and atoi */
|
||||
#include <pthread.h> /* POSIX threads API to create and manage threads in the program */
|
||||
|
||||
#include "jvsserial.h"
|
||||
#include "log.h"
|
||||
|
||||
#define TIMEOUT_SELECT 200
|
||||
#define CTS_ON_RETRY 20
|
||||
|
||||
// Used to read JVS frame in a non-blocking way
|
||||
jvsFrame jvsFrameBuffer;
|
||||
pthread_mutex_t jvsBuffer_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
/**
|
||||
* Open the serial interface and return a file descriptor
|
||||
* @param jvsPath The serial port path. Ex: "/dev/ttyS3"
|
||||
* @return A file descriptor
|
||||
*/
|
||||
int openJVSSerial(char *jvsPath) {
|
||||
int jvsFileDescriptor = -1;
|
||||
|
||||
// TODO: check O_NOCTTY declaration
|
||||
jvsFileDescriptor = open(jvsPath, O_RDWR | O_NOCTTY);
|
||||
if (jvsFileDescriptor < 0) {
|
||||
log_error("Failed to open '%s' for JVS.", jvsPath);
|
||||
}
|
||||
|
||||
return jvsFileDescriptor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init a serial port (using file descriptor) so it behaves correctly for JVS usage
|
||||
* @param fd
|
||||
* @return 0|1
|
||||
*/
|
||||
int initJVSSerial(int fd) {
|
||||
struct termios options;
|
||||
int status;
|
||||
|
||||
// Get the current options
|
||||
if (tcgetattr(fd, &options) != 0) {
|
||||
log_error("Error %i from tcgetattr: %s\n", errno, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set rates
|
||||
cfsetispeed(&options, B115200);
|
||||
cfsetospeed(&options, B115200);
|
||||
|
||||
/* From doc:
|
||||
* If the CLOCAL flag for a line is off, the hardware carrier detect (DCD) signal is significant,
|
||||
* an open(2) of the corresponding terminal will block until DCD is asserted, unless the O_NONBLOCK
|
||||
* flag is given. If CLOCAL is set, the line behaves as if DCD is always asserted. The software
|
||||
* carrier flag is usually turned on for local devices, and is off for lines with modems.
|
||||
*/
|
||||
// options.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines
|
||||
options.c_cflag |= CREAD; // Turn on READ, let ctrl lines work
|
||||
|
||||
options.c_cflag &= ~PARENB; // Clear parity bit & disable parity
|
||||
options.c_cflag &= ~CSTOPB; // Clear stop field, 1 stop bit
|
||||
options.c_cflag &= ~CSIZE; // Clear all bits that set the data size
|
||||
options.c_cflag |= CS8; // 8 bits
|
||||
options.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control
|
||||
|
||||
options.c_lflag &= ~ICANON; // Disable canonical mode, so no input processing is performed
|
||||
options.c_lflag &= ~ECHO; // Disable echo
|
||||
options.c_lflag &= ~ECHOE; // Disable erasure
|
||||
options.c_lflag &= ~ECHONL; // Disable new-line echo
|
||||
options.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
|
||||
options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
|
||||
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR |
|
||||
ICRNL); // Disable any special handling of received bytes
|
||||
|
||||
options.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
|
||||
options.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
|
||||
|
||||
// Set VMIN and VTIME to 0, so it returns immediately when no data are present
|
||||
// options.c_cc[VMIN] = 0;
|
||||
// options.c_cc[VTIME] = 0;
|
||||
|
||||
// With threaded serial read we should rely on a blocking read() function so the loop doesn't run crazy => read() could block indefinitely.
|
||||
// options.c_cc[VMIN] = 1;
|
||||
// options.c_cc[VTIME] = 0;
|
||||
|
||||
// Block until either VMIN characters have been received or VTIME **after first character** has been received
|
||||
options.c_cc[VMIN] = 0;
|
||||
options.c_cc[VTIME] = 1;
|
||||
|
||||
|
||||
tcsetattr(fd, TCSANOW, &options);
|
||||
|
||||
/* No use ? Save it for later
|
||||
// Set the serial port to non-blocking mode
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The DCD (Data Carrier Detect) status of a serial port indicates whether a carrier is present on the line, meaning that a connection has been established with another device.
|
||||
* @param fd File descriptor of JVS (serial) port
|
||||
* @return 0|1 According to control line status
|
||||
*/
|
||||
int getDCD(int fd) {
|
||||
int status;
|
||||
ioctl(fd, TIOCMGET, &status);
|
||||
return (status & TIOCM_CAR) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DSR (Data Set Ready) status of a serial port indicates whether the device at the other end of the connection is ready to receive data.
|
||||
* @param fd File descriptor of JVS (serial) port
|
||||
* @return 0|1 According to control line status
|
||||
*/
|
||||
int getDSR(int fd) {
|
||||
int status;
|
||||
ioctl(fd, TIOCMGET, &status);
|
||||
return (status & TIOCM_DSR) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The CTS (Clear To Send) status of a serial port indicates whether the device at the other end of the connection is ready to accept data.
|
||||
* @param fd File descriptor of JVS (serial) port
|
||||
* @return 0|1 According to control line status
|
||||
*/
|
||||
int getCTS(int fd) {
|
||||
int status;
|
||||
ioctl(fd, TIOCMGET, &status);
|
||||
return (status & TIOCM_CTS) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* In charge of reading serial port and bufferize JVS frame
|
||||
* @param arg
|
||||
* @return
|
||||
*/
|
||||
void *readJVSFrameThread(void * arg)
|
||||
{
|
||||
int fd = *((int *) arg);
|
||||
int byteCount, bytesRead, ackSize, waitForEnd;
|
||||
int ctsRetry = CTS_ON_RETRY;
|
||||
char localBuffer[JVSBUFFER_SIZE];
|
||||
|
||||
while (1)
|
||||
{
|
||||
// Reset local variable
|
||||
byteCount = 0;
|
||||
ackSize = 0;
|
||||
waitForEnd = 0;
|
||||
|
||||
do {
|
||||
// printf("SERIAL thread debug: trying to read byte.\n");
|
||||
// Try to read a byte from serial, this call will be blocking if VMIN > 0 and VTIME = 0
|
||||
bytesRead = read(fd, &localBuffer[byteCount], 1);
|
||||
|
||||
// If nothing on serial and CTS is ON, we try to read "CTS_ON_RETRY" times
|
||||
ctsRetry = CTS_ON_RETRY;
|
||||
while (bytesRead < 1 && --ctsRetry > 0 && jvsFrameBuffer.ready == 0) {
|
||||
bytesRead = read(fd, &localBuffer[byteCount], 1);
|
||||
log_trace("SERIAL: Retry number %d.\n", ctsRetry);
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
// Sync byte, we will stick in the loop
|
||||
if (byteCount == 0 && localBuffer[byteCount] == (char) 0xE0) {
|
||||
waitForEnd = 1;
|
||||
}
|
||||
|
||||
// Size byte
|
||||
if (byteCount == 2) {
|
||||
ackSize = localBuffer[byteCount] + 3;
|
||||
}
|
||||
|
||||
// Start counting bytes only if SYNC has been found
|
||||
if (waitForEnd) {
|
||||
byteCount++;
|
||||
}
|
||||
|
||||
// Reached the end of the message
|
||||
if (byteCount == ackSize) {
|
||||
waitForEnd = 0;
|
||||
}
|
||||
}
|
||||
} while (waitForEnd);
|
||||
|
||||
|
||||
// Lock the buffer while we write to it
|
||||
pthread_mutex_lock(&jvsBuffer_lock);
|
||||
|
||||
// Fake a response if CTS is ON but no message from JVS board, not sure about this...
|
||||
if (ctsRetry == 0) {
|
||||
char hexData[] = {0xE0, 0x00, 0x02, 0x01, 0x03};
|
||||
byteCount = sizeof(hexData);
|
||||
memcpy(localBuffer, hexData, byteCount);
|
||||
}
|
||||
|
||||
// We copy local buffer and length to mutexed buffer
|
||||
if (/* ctsRetry == 0 || */ byteCount > 0) {
|
||||
memcpy(jvsFrameBuffer.buffer, localBuffer, byteCount);
|
||||
jvsFrameBuffer.size = byteCount;
|
||||
jvsFrameBuffer.ready = 1;
|
||||
|
||||
// Reset local variable
|
||||
byteCount = 0;
|
||||
ctsRetry = CTS_ON_RETRY;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&jvsBuffer_lock);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the thread in charge of reading serial port
|
||||
* @param fd
|
||||
* @return 0|1
|
||||
*/
|
||||
int startJVSFrameThread(int * fd) {
|
||||
int fdlocal = *((int *) fd);
|
||||
log_trace("SERIAL: starting thread.\n");
|
||||
|
||||
// Clean shared JVS frame buffer
|
||||
jvsFrameBuffer.ready = 0;
|
||||
jvsFrameBuffer.size = 0;
|
||||
memset(jvsFrameBuffer.buffer, 0, JVSBUFFER_SIZE);
|
||||
|
||||
pthread_t jvsFrameThread;
|
||||
int ret = pthread_create(&jvsFrameThread, NULL, readJVSFrameThread, fd);
|
||||
if (ret != 0)
|
||||
{
|
||||
log_error("SERIAL: Failed to create reader thread");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a jvsFrame structure with empty or full data, no in between
|
||||
* @return
|
||||
*/
|
||||
jvsFrame readJVSFrameFromThread() {
|
||||
|
||||
jvsFrame frame;
|
||||
// Lock while reading/writing to shared frame
|
||||
pthread_mutex_lock(&jvsBuffer_lock);
|
||||
|
||||
// Check if we have a valid frame
|
||||
if (jvsFrameBuffer.ready == 1) {
|
||||
frame = jvsFrameBuffer;
|
||||
// It has been red, we disable this frame
|
||||
jvsFrameBuffer.ready = 0;
|
||||
} else {
|
||||
frame.ready = 0;
|
||||
frame.size = 0;
|
||||
memset(frame.buffer, 0, JVSBUFFER_SIZE);
|
||||
}
|
||||
pthread_mutex_unlock(&jvsBuffer_lock);
|
||||
|
||||
return frame;
|
||||
}
|
21
src/lindbergh/jvsserial.h
Normal file
21
src/lindbergh/jvsserial.h
Normal file
@ -0,0 +1,21 @@
|
||||
#define JVSBUFFER_SIZE 1024
|
||||
|
||||
typedef struct {
|
||||
int ctsCounter;
|
||||
int ready;
|
||||
int size;
|
||||
char buffer[JVSBUFFER_SIZE];
|
||||
} jvsFrame;
|
||||
|
||||
int getDCD(int fd);
|
||||
int getDSR(int fd);
|
||||
int getCTS(int fd);
|
||||
|
||||
jvsFrame readJVSFrameFromThread();
|
||||
int startJVSFrameThread(int * fd);
|
||||
void * readJVSFrameThread(void * arg);
|
||||
|
||||
int openJVSSerial(char *jvsPath);
|
||||
int initJVSSerial(int fd);
|
||||
int readJVSFrame(int fd, unsigned char *buffer);
|
||||
|
168
src/lindbergh/log.c
Normal file
168
src/lindbergh/log.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define MAX_CALLBACKS 32
|
||||
|
||||
typedef struct {
|
||||
log_LogFn fn;
|
||||
void *udata;
|
||||
int level;
|
||||
} Callback;
|
||||
|
||||
static struct {
|
||||
void *udata;
|
||||
log_LockFn lock;
|
||||
int level;
|
||||
bool quiet;
|
||||
Callback callbacks[MAX_CALLBACKS];
|
||||
} L;
|
||||
|
||||
|
||||
static const char *level_strings[] = {
|
||||
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
|
||||
};
|
||||
|
||||
#ifdef LOG_USE_COLOR
|
||||
static const char *level_colors[] = {
|
||||
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
static void stdout_callback(log_Event *ev) {
|
||||
char buf[16];
|
||||
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
|
||||
#ifdef LOG_USE_COLOR
|
||||
fprintf(
|
||||
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
|
||||
buf, level_colors[ev->level], level_strings[ev->level],
|
||||
ev->file, ev->line);
|
||||
#else
|
||||
fprintf(
|
||||
ev->udata, "%s %-5s %s:%d: ",
|
||||
buf, level_strings[ev->level], ev->file, ev->line);
|
||||
#endif
|
||||
vfprintf(ev->udata, ev->fmt, ev->ap);
|
||||
fprintf(ev->udata, "\n");
|
||||
fflush(ev->udata);
|
||||
}
|
||||
|
||||
|
||||
static void file_callback(log_Event *ev) {
|
||||
char buf[64];
|
||||
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
|
||||
fprintf(
|
||||
ev->udata, "%s %-5s %s:%d: ",
|
||||
buf, level_strings[ev->level], ev->file, ev->line);
|
||||
vfprintf(ev->udata, ev->fmt, ev->ap);
|
||||
fprintf(ev->udata, "\n");
|
||||
fflush(ev->udata);
|
||||
}
|
||||
|
||||
|
||||
static void lock(void) {
|
||||
if (L.lock) { L.lock(true, L.udata); }
|
||||
}
|
||||
|
||||
|
||||
static void unlock(void) {
|
||||
if (L.lock) { L.lock(false, L.udata); }
|
||||
}
|
||||
|
||||
|
||||
const char* log_level_string(int level) {
|
||||
return level_strings[level];
|
||||
}
|
||||
|
||||
|
||||
void log_set_lock(log_LockFn fn, void *udata) {
|
||||
L.lock = fn;
|
||||
L.udata = udata;
|
||||
}
|
||||
|
||||
|
||||
void log_set_level(int level) {
|
||||
L.level = level;
|
||||
}
|
||||
|
||||
|
||||
void log_set_quiet(bool enable) {
|
||||
L.quiet = enable;
|
||||
}
|
||||
|
||||
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level) {
|
||||
for (int i = 0; i < MAX_CALLBACKS; i++) {
|
||||
if (!L.callbacks[i].fn) {
|
||||
L.callbacks[i] = (Callback) { fn, udata, level };
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int log_add_fp(FILE *fp, int level) {
|
||||
return log_add_callback(file_callback, fp, level);
|
||||
}
|
||||
|
||||
|
||||
static void init_event(log_Event *ev, void *udata) {
|
||||
if (!ev->time) {
|
||||
time_t t = time(NULL);
|
||||
ev->time = localtime(&t);
|
||||
}
|
||||
ev->udata = udata;
|
||||
}
|
||||
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...) {
|
||||
log_Event ev = {
|
||||
.fmt = fmt,
|
||||
.file = file,
|
||||
.line = line,
|
||||
.level = level,
|
||||
};
|
||||
|
||||
lock();
|
||||
|
||||
if (!L.quiet && level >= L.level) {
|
||||
init_event(&ev, stderr);
|
||||
va_start(ev.ap, fmt);
|
||||
stdout_callback(&ev);
|
||||
va_end(ev.ap);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) {
|
||||
Callback *cb = &L.callbacks[i];
|
||||
if (level >= cb->level) {
|
||||
init_event(&ev, cb->udata);
|
||||
va_start(ev.ap, fmt);
|
||||
cb->fn(&ev);
|
||||
va_end(ev.ap);
|
||||
}
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
49
src/lindbergh/log.h
Normal file
49
src/lindbergh/log.h
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the MIT license. See `log.c` for details.
|
||||
*/
|
||||
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_VERSION "0.1.0"
|
||||
|
||||
typedef struct {
|
||||
va_list ap;
|
||||
const char *fmt;
|
||||
const char *file;
|
||||
struct tm *time;
|
||||
void *udata;
|
||||
int line;
|
||||
int level;
|
||||
} log_Event;
|
||||
|
||||
typedef void (*log_LogFn)(log_Event *ev);
|
||||
typedef void (*log_LockFn)(bool lock, void *udata);
|
||||
|
||||
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
|
||||
|
||||
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
const char* log_level_string(int level);
|
||||
void log_set_lock(log_LockFn fn, void *udata);
|
||||
void log_set_level(int level);
|
||||
void log_set_quiet(bool enable);
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level);
|
||||
int log_add_fp(FILE *fp, int level);
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
||||
|
||||
#endif
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "patch.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
static void setVariable(uint32_t address, uint32_t value)
|
||||
{
|
||||
@ -17,7 +18,7 @@ static void setVariable(uint32_t address, uint32_t value)
|
||||
int prot = mprotect(toModify, pagesize, PROT_WRITE);
|
||||
if (prot != 0)
|
||||
{
|
||||
printf("Error: Cannot unprotect memory region to change variable (%d)\n", prot);
|
||||
log_error("Cannot unprotect memory region to change variable (%d)", prot);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -33,7 +34,7 @@ static void detourFunction(uint32_t address, void *function)
|
||||
int prot = mprotect(toModify, pagesize, PROT_EXEC | PROT_WRITE);
|
||||
if (prot != 0)
|
||||
{
|
||||
printf("Error: Cannot detour memory region to change variable (%d)\n", prot);
|
||||
log_error("Cannot detour memory region to change variable (%d)", prot);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -107,9 +108,6 @@ int initPatch()
|
||||
setVariable(0x0a737f1c, 2); // amSysDataDebugLevel
|
||||
setVariable(0x0a737f20, 2); // bcLibDebugLevel
|
||||
setVariable(0x0a737f24, 0x0FFFFFFF); // s_logMask
|
||||
detourFunction(0x08320178, amDongleInit);
|
||||
detourFunction(0x08320459, amDongleIsAvailable);
|
||||
detourFunction(0x083203c0, amDongleUpdate);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "securityboard.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
#define SECURITY_BOARD_FRONT_PANEL 0x38
|
||||
#define SECURITY_BOARD_FRONT_PANEL_NON_ROOT 0x1038
|
||||
@ -53,7 +54,7 @@ int securityBoardSetDipResolution(int width, int height)
|
||||
else if (width == 1360 && height == 768)
|
||||
setResolutionDips(1, 1, 1);
|
||||
else
|
||||
printf("Warning: Resolution not compatible, using 640 x 480\n");
|
||||
log_warn("Warning: Resolution not compatible, using 640 x 480");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -68,7 +69,7 @@ int securityBoardSetDipSwitch(int switchNumber, int value)
|
||||
{
|
||||
if (switchNumber == 0)
|
||||
{
|
||||
printf("Error: Dip Switch index starts at 1\n");
|
||||
log_error("Dip Switch index starts at 1");
|
||||
return 1;
|
||||
}
|
||||
securityBoard.dipSwitch[switchNumber] = value;
|
||||
@ -86,7 +87,7 @@ int securityBoardSetSwitch(JVSInput switchNumber, int value)
|
||||
securityBoard.serviceSwitch = value;
|
||||
break;
|
||||
default:
|
||||
printf("Error: Attempted to set a security board switch incorrectly");
|
||||
log_error("Attempted to set a security board switch incorrectly");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -1,89 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <linux/serial.h>
|
||||
#include <termios.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/serial.h>
|
||||
|
||||
#include "serial.h"
|
||||
|
||||
#define TIMEOUT_SELECT 200
|
||||
|
||||
int setSerialAttributes(int fd, int myBaud)
|
||||
{
|
||||
struct termios options;
|
||||
int status;
|
||||
tcgetattr(fd, &options);
|
||||
|
||||
cfmakeraw(&options);
|
||||
cfsetispeed(&options, myBaud);
|
||||
cfsetospeed(&options, myBaud);
|
||||
|
||||
options.c_cflag |= (CLOCAL | CREAD);
|
||||
options.c_cflag &= ~PARENB;
|
||||
options.c_cflag &= ~CSTOPB;
|
||||
options.c_cflag &= ~CSIZE;
|
||||
options.c_cflag |= CS8;
|
||||
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
||||
options.c_oflag &= ~OPOST;
|
||||
|
||||
options.c_cc[VMIN] = 0;
|
||||
options.c_cc[VTIME] = 0; // One seconds (10 deciseconds)
|
||||
|
||||
tcsetattr(fd, TCSANOW, &options);
|
||||
|
||||
ioctl(fd, TIOCMGET, &status);
|
||||
|
||||
status |= TIOCM_DTR;
|
||||
status |= TIOCM_RTS;
|
||||
|
||||
ioctl(fd, TIOCMSET, &status);
|
||||
|
||||
usleep(100 * 1000); // 10mS
|
||||
|
||||
struct serial_struct serial_settings;
|
||||
|
||||
ioctl(fd, TIOCGSERIAL, &serial_settings);
|
||||
|
||||
serial_settings.flags |= ASYNC_LOW_LATENCY;
|
||||
ioctl(fd, TIOCSSERIAL, &serial_settings);
|
||||
|
||||
tcflush(fd, TCIOFLUSH);
|
||||
usleep(100 * 1000); // Required to make flush work, for some reason
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readBytes(int fd, unsigned char *buffer, int amount)
|
||||
{
|
||||
fd_set fd_serial;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&fd_serial);
|
||||
FD_SET(fd, &fd_serial);
|
||||
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = TIMEOUT_SELECT * 1000;
|
||||
|
||||
int filesReadyToRead = select(fd + 1, &fd_serial, NULL, NULL, &tv);
|
||||
|
||||
if (filesReadyToRead < 1)
|
||||
return 0;
|
||||
|
||||
if (!FD_ISSET(fd, &fd_serial))
|
||||
return 0;
|
||||
|
||||
return read(fd, buffer, amount);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
|
||||
int setSerialAttributes(int fd, int myBaud);
|
||||
int readBytes(int fd, unsigned char *buffer, int amount);
|
Loading…
Reference in New Issue
Block a user