Game Link base code (without integration)

This commit is contained in:
jwalt
2022-12-13 18:02:50 +01:00
parent 982c44176e
commit edbbe60217
11 changed files with 1584 additions and 3 deletions

View File

@@ -420,6 +420,17 @@ else
fi
fi
AH_TEMPLATE(C_GAMELINK,[Define to 1 to enable game link headless mode])
AC_CHECK_LIB(rt, main, have_rt_lib=yes, have_rt_lib=no , )
AC_ARG_ENABLE(gamelink,AC_HELP_STRING([--disable-gamelink],[Disable headless game link output mode]),,enable_gamelink=yes)
AC_MSG_CHECKING(whether gamelink is enabled)
if test x$enable_gamelink = xyes; then
AC_MSG_RESULT(yes)
AC_DEFINE(C_GAMELINK,1)
else
AC_MSG_RESULT(no)
fi
dnl FEATURE: Whether to use OpenGL
AH_TEMPLATE(C_OPENGL,[Define to 1 to use opengl display output support])
AC_ARG_ENABLE(opengl,AC_HELP_STRING([--disable-opengl],[Disable opengl support]),enable_opengl=$enableval,enable_opengl=yes)
@@ -1226,6 +1237,7 @@ src/cpu/core_normal/Makefile
src/debug/Makefile
src/dos/Makefile
src/fpu/Makefile
src/gamelink/Makefile
src/gui/Makefile
src/hardware/Makefile
src/hardware/mame/Makefile

View File

@@ -5,7 +5,7 @@ if EMSCRIPTEN
dosbox_x_LDFLAGS = -s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$$ERRNO_CODES']
endif
SUBDIRS = cpu debug dos fpu gui hardware libs ints misc shell builtin platform aviwriter output
SUBDIRS = cpu debug dos fpu gui hardware libs ints misc shell builtin platform aviwriter output gamelink
bin_PROGRAMS = dosbox-x
@@ -32,7 +32,7 @@ dosbox_x_LDADD = debug/libdebug.a dos/libdos.a shell/libshell.a builtin/libbuilt
ints/libints.a hardware/serialport/libserial.a hardware/parport/libparallel.a \
libs/porttalk/libporttalk.a gui/libgui.a libs/gui_tk/libgui_tk.a hardware/libhardware.a \
cpu/libcpu.a hardware/reSID/libresid.a fpu/libfpu.a gui/libgui.a \
misc/libmisc.a output/liboutput.a hardware/mame/libmame.a libs/zmbv/libzmbv.a libs/decoders/internal/libopusint.a
misc/libmisc.a output/liboutput.a gamelink/libgamelink.a hardware/mame/libmame.a libs/zmbv/libzmbv.a libs/decoders/internal/libopusint.a
if !EMSCRIPTEN
dosbox_x_LDADD += aviwriter/libaviwriter.a

4
src/gamelink/Makefile.am Normal file
View File

@@ -0,0 +1,4 @@
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src
noinst_LIBRARIES = libgamelink.a
libgamelink_a_SOURCES = gamelink.cpp gamelink_term.cpp

737
src/gamelink/gamelink.cpp Normal file
View File

@@ -0,0 +1,737 @@
// Game Link
//------------------------------------------------------------------------------
// Dependencies
//------------------------------------------------------------------------------
#include "config.h"
#if C_GAMELINK
// External Dependencies
#ifdef WIN32
#include <Windows.h>
#else // WIN32
#include <sys/mman.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
#endif // WIN32
// SDL Dependencies
#include "SDL_syswm.h"
#include "SDL.h"
// Local Dependencies
#include "dosbox.h"
#include "gamelink.h"
#include "logging.h"
#include "sdlmain.h"
#include "../resource.h"
extern bool is_paused;
extern uint32_t RunningProgramLoadAddress;
//==============================================================================
//------------------------------------------------------------------------------
// Local Definitions
//------------------------------------------------------------------------------
#define SYSTEM_NAME "DOSBox"
#define PROTOCOL_VER 4
// Ugly hack: grid cartographer sends absolute addresses, but different dosbox
// versions load executables at different addresses.
// To fix those cases, this code calculates an offset from the original and
// the actual load address. To configure the original load address for a
// game, edit the corresponding grid cartographer profile XML file, search
// for the <peek> tag, add 0x1000_0000 to the address and put it at the end. For
// example, if the load address is 0x1a70 the XML tag could look like:
// <peek bytes="1299c 1299e 19c42 19c2e 19c30 19c40 19c32 17a8a 10001a70" />
// To find out the original load address, use the "gamelinksnoop" option and
// load the same game in dosbox-gridc and this dosbox, load the same save
// game/move to the same position. Console output should then contain a
// suggested original load address.
// This default offset works for some simple cases.
#define PEEK_ADDR_DEFAULT_OFFSET 0x6850
#ifdef WIN32
#define GAMELINK_MUTEX_NAME "DWD_GAMELINK_MUTEX_R4"
#else // WIN32
#define GAMELINK_MUTEX_NAME "/DWD_GAMELINK_MUTEX_R4"
#endif // WIN32
#ifdef MACOSX
#define GAMELINK_MMAP_NAME "/DWD_GAMELINK_MMAP_R4"
#else // MACOSX
#define GAMELINK_MMAP_NAME "DWD_GAMELINK_MMAP_R4"
#endif // MACOSX
//------------------------------------------------------------------------------
// Local Data
//------------------------------------------------------------------------------
#ifdef WIN32
static HANDLE g_mutex_handle;
static HANDLE g_mmap_handle;
#else // WIN32
static sem_t* g_mutex_handle;
static int g_mmap_handle; // fd!
#endif // WIN32
static bool g_trackonly_mode;
static uint32_t g_membase_size;
static GameLink::sSharedMemoryMap_R4* g_p_shared_memory;
#define MEMORY_MAP_CORE_SIZE sizeof( GameLink::sSharedMemoryMap_R4 )
//------------------------------------------------------------------------------
// Local Functions
//------------------------------------------------------------------------------
static void shared_memory_init()
{
if (sdl.gamelink.snoop) return;
// Initialise
g_p_shared_memory->version = PROTOCOL_VER;
g_p_shared_memory->flags = 0;
memset( g_p_shared_memory->system, 0, sizeof( g_p_shared_memory->system ) );
strcpy( g_p_shared_memory->system, SYSTEM_NAME );
memset( g_p_shared_memory->program, 0, sizeof( g_p_shared_memory->program ) );
g_p_shared_memory->program_hash[ 0 ] = 0;
g_p_shared_memory->program_hash[ 1 ] = 0;
g_p_shared_memory->program_hash[ 2 ] = 0;
g_p_shared_memory->program_hash[ 3 ] = 0;
// reset input
g_p_shared_memory->input.mouse_dx = 0;
g_p_shared_memory->input.mouse_dy = 0;
g_p_shared_memory->input.mouse_btn = 0;
for ( int i = 0; i < 8; ++i ) {
g_p_shared_memory->input.keyb_state[ i ] = 0;
}
// reset peek interface
g_p_shared_memory->peek.addr_count = 0;
memset( g_p_shared_memory->peek.addr, 0, GameLink::sSharedMMapPeek_R2::PEEK_LIMIT * sizeof( uint32_t ) );
memset( g_p_shared_memory->peek.data, 0, GameLink::sSharedMMapPeek_R2::PEEK_LIMIT * sizeof( uint8_t ) );
// blank frame
g_p_shared_memory->frame.seq = 0;
g_p_shared_memory->frame.image_fmt = 0; // = no frame
g_p_shared_memory->frame.width = 0;
g_p_shared_memory->frame.height = 0;
g_p_shared_memory->frame.par_x = 1;
g_p_shared_memory->frame.par_y = 1;
memset( g_p_shared_memory->frame.buffer, 0, GameLink::sSharedMMapFrame_R1::MAX_PAYLOAD );
// audio: 100%
g_p_shared_memory->audio.master_vol_l = 100;
g_p_shared_memory->audio.master_vol_r = 100;
// RAM
g_p_shared_memory->ram_size = g_membase_size;
}
//
// create_mutex
//
// Create a globally unique mutex.
//
// \returns 1 if we made one, 0 if it failed or the mutex existed already (also a failure).
//
static int create_mutex( const char* p_name )
{
#ifdef WIN32
// The mutex is already open?
g_mutex_handle = OpenMutexA( SYNCHRONIZE, FALSE, p_name );
if ( g_mutex_handle != 0 ) {
if (sdl.gamelink.snoop) return 1;
// .. didn't fail - so must already exist.
CloseHandle( g_mutex_handle );
g_mutex_handle = 0;
return 0;
}
// Actually create it.
g_mutex_handle = CreateMutexA( NULL, FALSE, p_name );
if ( g_mutex_handle ) {
return 1;
}
#else // WIN32
// The mutex is already open?
g_mutex_handle = sem_open( p_name, 0, 0666, 0 );
if ( g_mutex_handle != SEM_FAILED )
{
// .. didn't fail - so must already exist.
LOG_MSG( "GAMELINK: ERROR: MUTEX \"%s\" already exists.", p_name );
LOG_MSG( "GAMELINK: Already running Game Link, or maybe a crash?", p_name );
#ifdef MACOSX
LOG_MSG( "GAMELINK: Might need to manually reboot the system." );
#else // MACOSX
LOG_MSG( "GAMELINK: Might need to manually tidy up in /dev/shm (or reboot system)." );
#endif // MACOSX
sem_close( g_mutex_handle );
g_mutex_handle = 0;
return 0;
}
// Actually create it.
g_mutex_handle = sem_open( p_name, O_CREAT, 0666, 1 );
if ( g_mutex_handle == SEM_FAILED )
{
LOG_MSG( "GAMELINK: Creating MUTEX \"%s\" failed. [sem_open=%d, errno=%d]", p_name, (int)(size_t)g_mutex_handle, errno );
#ifdef MACOSX
LOG_MSG( "GAMELINK: Might need to manually reboot the system." );
#else // MACOSX
LOG_MSG( "GAMELINK: Might need to manually tidy up in /dev/shm (or reboot system)." );
#endif // MACOSX
g_mutex_handle = 0;
}
else
{
return 1;
}
#endif // WIN32
return 0;
}
//
// destroy_mutex
//
// Tidy up the mutex.
//
static void destroy_mutex( const char* p_name )
{
#ifdef WIN32
(void)(p_name);
if ( g_mutex_handle )
{
CloseHandle( g_mutex_handle );
g_mutex_handle = NULL;
}
#else // WIN32
if ( g_mutex_handle )
{
sem_close( g_mutex_handle );
sem_unlink( p_name );
g_mutex_handle = NULL;
}
#endif // WIN32
}
//
// create_shared_memory
//
// Create a shared memory area.
//
// \returns 1 if we made one, 0 if it failed.
//
static int create_shared_memory()
{
const int memory_map_size = MEMORY_MAP_CORE_SIZE + g_membase_size;
#ifdef WIN32
g_mmap_handle = CreateFileMappingA( INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, memory_map_size, GAMELINK_MMAP_NAME );
if ( g_mmap_handle )
{
g_p_shared_memory = reinterpret_cast< GameLink::sSharedMemoryMap_R4* >(
MapViewOfFile( g_mmap_handle, FILE_MAP_ALL_ACCESS, 0, 0, memory_map_size )
);
if ( g_p_shared_memory )
{
return 1; // Success!
}
else
{
// tidy up file mapping.
CloseHandle( g_mmap_handle );
g_mmap_handle = NULL;
}
}
#else // WIN32
g_mmap_handle = shm_open( GAMELINK_MMAP_NAME, O_CREAT
#ifndef MACOSX
| O_TRUNC
#endif // !MACOSX
| O_RDWR, 0666 );
g_p_shared_memory = NULL; // defensive
if ( g_mmap_handle < 0 )
{
LOG_MSG( "GAMELINK: shm_open( \"" GAMELINK_MMAP_NAME "\" ) failed. errno = %d", errno );
}
else
{
// set size
int r;
r = ftruncate( g_mmap_handle, memory_map_size );
if ( r < 0 ) {
LOG_MSG( "GAMELINK: ftruncate failed with %d. errno = %d", r, errno );
close( g_mmap_handle );
g_mmap_handle = -1;
shm_unlink( GAMELINK_MMAP_NAME );
g_p_shared_memory = NULL;
return 0;
}
// map to a pointer.
g_p_shared_memory = reinterpret_cast< GameLink::sSharedMemoryMap_R4* >(
mmap( 0, memory_map_size, PROT_READ | PROT_WRITE, MAP_SHARED, g_mmap_handle, 0 )
);
if ( g_p_shared_memory == MAP_FAILED )
{
LOG_MSG( "GAMELINK: mmap failed. errno = %d", errno );
close( g_mmap_handle );
g_mmap_handle = -1;
shm_unlink( GAMELINK_MMAP_NAME );
g_p_shared_memory = NULL;
return 0;
}
// success!
return 1;
}
#endif // WIN32
// Failure
return 0;
}
//
// destroy_shared_memory
//
// Destroy the shared memory area.
//
static void destroy_shared_memory()
{
#ifdef WIN32
if ( g_p_shared_memory )
{
UnmapViewOfFile( g_p_shared_memory );
g_p_shared_memory = NULL;
}
if ( g_mmap_handle )
{
CloseHandle( g_mmap_handle );
g_mmap_handle = NULL;
}
#else // WIN32
const int memory_map_size = MEMORY_MAP_CORE_SIZE + g_membase_size;
if ( g_p_shared_memory )
{
munmap( g_p_shared_memory, memory_map_size );
g_p_shared_memory = NULL;
}
if ( g_mmap_handle >= 0 )
{
close( g_mmap_handle );
g_mmap_handle = -1;
}
shm_unlink( GAMELINK_MMAP_NAME );
#endif // WIN32
}
//==============================================================================
//------------------------------------------------------------------------------
// GameLink::Init
//------------------------------------------------------------------------------
int GameLink::Init( const bool trackonly_mode )
{
//LOG_MSG("GAMELINK: Init %i", trackonly_mode);
int iresult;
// Already initialised?
if ( g_mutex_handle )
{
LOG_MSG( "GAMELINK: Ignoring re-initialisation." );
// success
return 1;
}
// Store the mode we're in.
g_trackonly_mode = trackonly_mode;
// Create a fresh mutex.
iresult = create_mutex( GAMELINK_MUTEX_NAME );
if ( iresult != 1 )
{
// failed.
return 0;
}
return 1;
}
//------------------------------------------------------------------------------
// GameLink::AllocRAM
//------------------------------------------------------------------------------
uint8_t* GameLink::AllocRAM( const uint32_t size )
{
//LOG_MSG("GAMELINK: AllocRAM %i", size);
int iresult;
g_membase_size = size;
// Create a shared memory area.
iresult = create_shared_memory();
if ( iresult != 1 )
{
destroy_mutex( GAMELINK_MUTEX_NAME );
// failed.
return 0;
}
// Initialise
shared_memory_init();
GameLink::InitTerminal();
const int memory_map_size = MEMORY_MAP_CORE_SIZE + g_membase_size;
LOG_MSG( "GAMELINK: Initialised. Allocated %d MB of shared memory.", (memory_map_size + (1024*1024) - 1) / (1024*1024) );
uint8_t *membase;
if (sdl.gamelink.snoop) {
membase = (uint8_t*)malloc(g_membase_size);
} else {
membase = ((uint8_t*)g_p_shared_memory) + MEMORY_MAP_CORE_SIZE;
}
// Return RAM base pointer.
return membase;
}
//------------------------------------------------------------------------------
// GameLink::Term
//------------------------------------------------------------------------------
void GameLink::Term()
{
//LOG_MSG("GAMELINK: Term");
// SEND ABORT CODE TO CLIENT (don't care if it fails)
if (!sdl.gamelink.snoop && g_p_shared_memory)
g_p_shared_memory->version = 0;
destroy_shared_memory();
destroy_mutex( GAMELINK_MUTEX_NAME );
g_membase_size = 0;
}
//------------------------------------------------------------------------------
// GameLink::In
//------------------------------------------------------------------------------
int GameLink::In( GameLink::sSharedMMapInput_R2* p_input,
GameLink::sSharedMMapAudio_R1* p_audio )
{
// LOG_MSG("GAMELINK: In");
int ready = 0;
if (!sdl.gamelink.snoop && g_p_shared_memory)
{
if ( g_trackonly_mode )
{
// No input.
memset( p_input, 0, sizeof( sSharedMMapInput_R2 ) );
}
else
{
if ( g_p_shared_memory->input.ready )
{
// Copy client input out of shared memory
memcpy( p_input, &( g_p_shared_memory->input ), sizeof( sSharedMMapInput_R2 ) );
// Clear remote delta, prevent counting more than once.
g_p_shared_memory->input.mouse_dx = 0;
g_p_shared_memory->input.mouse_dy = 0;
g_p_shared_memory->input.ready = 0;
ready = 1; // Got some input
}
// Volume sync, ignore if out of range.
if ( g_p_shared_memory->audio.master_vol_l <= 100 )
p_audio->master_vol_l = g_p_shared_memory->audio.master_vol_l;
if ( g_p_shared_memory->audio.master_vol_r <= 100 )
p_audio->master_vol_r = g_p_shared_memory->audio.master_vol_r;
}
}
return ready;
}
//------------------------------------------------------------------------------
// GameLink::Out
//------------------------------------------------------------------------------
void GameLink::Out( const uint16_t frame_width,
const uint16_t frame_height,
const double source_ratio,
const bool want_mouse,
const char* p_program,
const uint32_t* p_program_hash,
const uint8_t* p_frame,
const uint8_t* p_sysmem )
{
// LOG_MSG("GAMELINK: Out");
// Not initialised (or disabled) ?
if ( g_p_shared_memory == NULL ) {
return; // <=== EARLY OUT
}
// Create integer ratio
uint16_t par_x, par_y;
if ( source_ratio >= 1.0 )
{
par_x = 4096;
par_y = static_cast< uint16_t >( source_ratio * 4096.0 );
}
else
{
par_x = static_cast< uint16_t >( 4096.0 / source_ratio );
par_y = 4096;
}
// Build flags
uint8_t flags;
if ( g_trackonly_mode )
{
// Tracking Only - DOSBox handles video/input as usual.
flags = sSharedMemoryMap_R4::FLAG_NO_FRAME;
}
else
{
// External Input Mode
flags = sSharedMemoryMap_R4::FLAG_WANT_KEYB;
if ( want_mouse )
flags |= sSharedMemoryMap_R4::FLAG_WANT_MOUSE;
}
// Paused?
if ( is_paused )
flags |= sSharedMemoryMap_R4::FLAG_PAUSED;
//
// Send data?
// Message buffer
sSharedMMapBuffer_R1 proc_mech_buffer;
proc_mech_buffer.payload = 0;
#ifdef WIN32
DWORD mutex_result;
mutex_result = WaitForSingleObject( g_mutex_handle, INFINITE );
if ( mutex_result == WAIT_OBJECT_0 )
#else // WIN32
int mutex_result;
mutex_result = sem_wait( g_mutex_handle );
if ( mutex_result < 0 )
{
LOG_MSG( "GAMELINK: MUTEX lock failed with %d. errno = %d", mutex_result, errno );
}
else
#endif // WIN32
{
if (!sdl.gamelink.snoop) { // ========================
// Set version
g_p_shared_memory->version = PROTOCOL_VER;
// Set program
strncpy( g_p_shared_memory->program, p_program, 256 );
for ( int i = 0; i < 4; ++i )
{
g_p_shared_memory->program_hash[ i ] = p_program_hash[ i ];
}
// Store flags
g_p_shared_memory->flags = flags;
if ( g_trackonly_mode == false )
{
// Update the frame sequence
++g_p_shared_memory->frame.seq;
// Copy frame properties
g_p_shared_memory->frame.image_fmt = 1; // = 32-bit RGBA
g_p_shared_memory->frame.width = frame_width;
g_p_shared_memory->frame.height = frame_height;
g_p_shared_memory->frame.par_x = par_x;
g_p_shared_memory->frame.par_y = par_y;
// Frame Buffer
uint32_t payload;
payload = frame_width * frame_height * 4;
if ( frame_width <= sSharedMMapFrame_R1::MAX_WIDTH && frame_height <= sSharedMMapFrame_R1::MAX_HEIGHT )
{
memcpy( g_p_shared_memory->frame.buffer, p_frame, payload );
}
}
} else {
LOG_MSG("Hash: %x %x %x %x Flags: %x",
g_p_shared_memory->program_hash[0],
g_p_shared_memory->program_hash[1],
g_p_shared_memory->program_hash[2],
g_p_shared_memory->program_hash[3],
g_p_shared_memory->flags);
}
{
// Find peek offset
uint32_t offset = PEEK_ADDR_DEFAULT_OFFSET;
int cnt = g_p_shared_memory->peek.addr_count;
if (cnt <= sSharedMMapPeek_R2::PEEK_LIMIT
&& cnt >= 1
&& g_p_shared_memory->peek.addr[ cnt-1 ] > 0x1000'0000)
{
offset = RunningProgramLoadAddress - (g_p_shared_memory->peek.addr[ cnt-1 ]-0x1000'0000);
}
if (sdl.gamelink.snoop) offset = 0;
// Peek
for ( uint32_t pindex = 0;
pindex < g_p_shared_memory->peek.addr_count &&
pindex < sSharedMMapPeek_R2::PEEK_LIMIT;
++pindex )
{
// read address
uint32_t address;
address = g_p_shared_memory->peek.addr[ pindex ] + offset;
uint8_t data;
// valid?
if ( address < g_membase_size )
{
data = p_sysmem[ address ]; // read data
}
else
{
data = 0; // <-- safe
}
if (!sdl.gamelink.snoop) {
g_p_shared_memory->peek.data[ pindex ] = data;
} else {
uint8_t seek = g_p_shared_memory->peek.data[ pindex ];
LOG_MSG("_____ peek: %04x = %i / %i / %i", address, seek, data, address >= g_membase_size?0:*(((uint8_t*)g_p_shared_memory) + MEMORY_MAP_CORE_SIZE + address));
}
}
}
if (!sdl.gamelink.snoop) {
// Message Processing.
ExecTerminal( &(g_p_shared_memory->buf_recv),
&(g_p_shared_memory->buf_tohost),
&(proc_mech_buffer) );
} else if (RunningProgramLoadAddress && g_p_shared_memory->peek.addr_count) {
LOG_MSG("Load Address = %06x", RunningProgramLoadAddress);
for (int addr = 0; addr < g_membase_size-1024; addr++) {
bool match = true;
int base = g_p_shared_memory->peek.addr[ 0 ];
for (int i = 0; i < g_p_shared_memory->peek.addr_count; i++) {
uint8_t seek = g_p_shared_memory->peek.data[ i ];
int oaddr = g_p_shared_memory->peek.addr[ i ] - base + addr;
if (oaddr >= g_membase_size || p_sysmem[ oaddr ] != seek) {
match = false;
break;
}
}
if (match) {
LOG_MSG("Match at %06x, Offset %06x, Original Load Address %06x", addr, addr-base, RunningProgramLoadAddress - (addr-base));
break;
}
}
} // ========================
#ifdef WIN32
ReleaseMutex( g_mutex_handle );
#else // WIN32
mutex_result = sem_post( g_mutex_handle );
if ( mutex_result < 0 ) {
LOG_MSG( "GAMELINK: MUTEX unlock failed with %d. errno = %d", mutex_result, errno );
} else {
// printf( "GAMELINK: MUTEX unlock ok.\n" );
}
#endif // WIN32
// Mechanical Message Processing, out of mutex.
if (!sdl.gamelink.snoop && proc_mech_buffer.payload)
ExecTerminalMech( &proc_mech_buffer );
}
}
//==============================================================================
#endif // C_GAMELINK

175
src/gamelink/gamelink.h Normal file
View File

@@ -0,0 +1,175 @@
#ifndef __GAMELINK_H___
#define __GAMELINK_H___
#include "dosbox.h"
#ifdef WIN32
#include <Windows.h>
#endif // WIN32
//------------------------------------------------------------------------------
// Namespace Declaration
//------------------------------------------------------------------------------
namespace GameLink
{
//--------------------------------------------------------------------------
// Global Declarations
//--------------------------------------------------------------------------
#pragma pack( push, 1 )
//
// sSharedMMapFrame_R1
//
// Server -> Client Frame. 32-bit RGBA up to MAX_WIDTH x MAX_HEIGHT
//
struct sSharedMMapFrame_R1
{
uint16_t seq;
uint16_t width;
uint16_t height;
uint8_t image_fmt; // 0 = no frame; 1 = 32-bit 0xAARRGGBB
uint8_t reserved0;
uint16_t par_x; // pixel aspect ratio
uint16_t par_y;
enum { MAX_WIDTH = 1280 };
enum { MAX_HEIGHT = 1024 };
enum { MAX_PAYLOAD = MAX_WIDTH * MAX_HEIGHT * 4 };
uint8_t buffer[ MAX_PAYLOAD ];
};
//
// sSharedMMapInput_R2
//
// Client -> Server Input Data
//
struct sSharedMMapInput_R2
{
float mouse_dx;
float mouse_dy;
uint8_t ready;
uint8_t mouse_btn;
uint32_t keyb_state[ 8 ];
};
//
// sSharedMMapPeek_R2
//
// Memory reading interface.
//
struct sSharedMMapPeek_R2
{
enum { PEEK_LIMIT = 16 * 1024 };
uint32_t addr_count;
uint32_t addr[ PEEK_LIMIT ];
uint8_t data[ PEEK_LIMIT ];
};
//
// sSharedMMapBuffer_R1
//
// General buffer (64Kb)
//
struct sSharedMMapBuffer_R1
{
enum { BUFFER_SIZE = ( 64 * 1024 ) };
uint16_t payload;
uint8_t data[ BUFFER_SIZE ];
};
//
// sSharedMMapAudio_R1
//
// Audio control interface.
//
struct sSharedMMapAudio_R1
{
uint8_t master_vol_l;
uint8_t master_vol_r;
};
//
// sSharedMemoryMap_R4
//
// Memory Map (top-level object)
//
struct sSharedMemoryMap_R4
{
enum {
FLAG_WANT_KEYB = 1 << 0,
FLAG_WANT_MOUSE = 1 << 1,
FLAG_NO_FRAME = 1 << 2,
FLAG_PAUSED = 1 << 3,
};
enum {
SYSTEM_MAXLEN = 64
};
enum {
PROGRAM_MAXLEN = 260
};
uint8_t version; // = PROTOCOL_VER
uint8_t flags;
char system[ SYSTEM_MAXLEN ]; // System name.
char program[ PROGRAM_MAXLEN ]; // Program name. Zero terminated.
uint32_t program_hash[ 4 ]; // Program code hash (256-bits)
sSharedMMapFrame_R1 frame;
sSharedMMapInput_R2 input;
sSharedMMapPeek_R2 peek;
sSharedMMapBuffer_R1 buf_recv; // a message to us.
sSharedMMapBuffer_R1 buf_tohost;
sSharedMMapAudio_R1 audio;
// added for protocol v4
uint32_t ram_size;
};
#pragma pack( pop )
//--------------------------------------------------------------------------
// Global Functions
//--------------------------------------------------------------------------
extern int Init( const bool trackonly_mode );
extern uint8_t* AllocRAM( const uint32_t size );
extern void Term();
extern int In( sSharedMMapInput_R2* p_input,
sSharedMMapAudio_R1* p_audio );
extern void Out( const uint16_t frame_width,
const uint16_t frame_height,
const double source_ratio,
const bool need_mouse,
const char* p_program,
const uint32_t* p_program_hash,
const uint8_t* p_frame,
const uint8_t* p_sysmem );
extern void ExecTerminal( sSharedMMapBuffer_R1* p_inbuf,
sSharedMMapBuffer_R1* p_outbuf,
sSharedMMapBuffer_R1* p_mechbuf );
extern void ExecTerminalMech( sSharedMMapBuffer_R1* p_mechbuf );
extern void InitTerminal();
}; // namespace GameLink
//==============================================================================
#endif // __GAMELINK_HDR__

View File

@@ -0,0 +1,260 @@
// Game Link - Console
//------------------------------------------------------------------------------
// Dependencies
//------------------------------------------------------------------------------
#include "config.h"
#if C_GAMELINK
// External Dependencies
#ifdef WIN32
#else // WIN32
#include <string.h>
#include <stdlib.h>
#include <memory.h>
#endif // WIN32
// Local Dependencies
#include "dosbox.h"
#include "gamelink.h"
#include "sdlmain.h"
#include "../resource.h"
#include <stdio.h>
#include "mem.h"
typedef GameLink::sSharedMMapBuffer_R1 Buffer;
//==============================================================================
//------------------------------------------------------------------------------
// Local Data
//------------------------------------------------------------------------------
static Buffer* g_p_outbuf;
//------------------------------------------------------------------------------
// Local Functions
//------------------------------------------------------------------------------
//
// out_char
//
// Copy a string into the output buffer.
//
static void out_char( const char ch )
{
// Overflow?
if ( g_p_outbuf->payload >= Buffer::BUFFER_SIZE - 1 ) {
return;
}
// Copy character
g_p_outbuf->data[ g_p_outbuf->payload++ ] = ch;
// terminate
g_p_outbuf->data[ g_p_outbuf->payload ] = 0;
}
//
// out_strcpy
//
// Copy a string into the output buffer.
//
static void out_strcpy( const char* p )
{
while ( *p )
{
// Overflow?
if ( g_p_outbuf->payload >= Buffer::BUFFER_SIZE - 1 ) {
break;
}
// Copy character
g_p_outbuf->data[ g_p_outbuf->payload++ ] = *p++;
}
// terminate
g_p_outbuf->data[ g_p_outbuf->payload ] = 0;
}
//
// out_strint
//
// Copy an integer as plain text into the output buffer.
//
static void out_strint( const int v )
{
char buf[ 32 ];
sprintf( buf, "%d", v );
out_strcpy( buf );
}
//
// out_strhex
//
// Copy a hex value as plain text into the output buffer.
// Zero prefixed.
//
static void out_strhex( const int v, const int wide )
{
char buf[ 32 ], fmt[ 32 ] = "%08X";
if ( wide >= 1 && wide <= 9 ) {
fmt[ 2 ] = '0' + wide;
sprintf( buf, fmt, v );
out_strcpy( buf );
}
}
//
// out_strhexspc
//
// Copy a hex value as plain text into the output buffer.
//
static void out_strhexspc( const int v, const int wide )
{
char buf[ 32 ], fmt[ 32 ] = "%8X";
if ( wide >= 1 && wide <= 9 ) {
fmt[ 1 ] = '0' + wide;
sprintf( buf, fmt, v );
out_strcpy( buf );
}
}
//
// out_memcpy
//
// Copy a block of memory into the output buffer.
//
static void out_memcpy( const void* p, const uint16_t len )
{
const uint8_t* src = (const uint8_t*)p;
for ( uint16_t i = 0; i < len; ++i )
{
// Overflow?
if ( g_p_outbuf->payload >= Buffer::BUFFER_SIZE - 1 ) {
break;
}
// Copy data
g_p_outbuf->data[ g_p_outbuf->payload++ ] = *src++;
}
}
//==============================================================================
//
// proc_mech
//
// Process a mechanical command - encoded form for computer-computer communication. Minimal feedback.
//
static void proc_mech( Buffer* cmd, uint16_t payload )
{
// Ignore NULL commands.
if ( payload <= 1 || payload > 128 )
return;
cmd->payload = 0;
char* com = (char*)(cmd->data);
com[ payload ] = 0;
// printf( "command = %s; payload = %d\n", com, payload );
//
// Decode
if ( strcmp( com, ":reset" ) == 0 )
{
// printf( "do reset\n" );
ResetSystem( true );
}
else if ( strcmp( com, ":pause" ) == 0 )
{
// printf( "do pause\n" );
PauseDOSBox( true );
}
else if ( strcmp( com, ":shutdown" ) == 0 )
{
// printf( "do shutdown\n" );
DoKillSwitch();
}
}
//------------------------------------------------------------------------------
// Global Functions
//------------------------------------------------------------------------------
void GameLink::InitTerminal()
{
g_p_outbuf = 0;
}
void GameLink::ExecTerminalMech( Buffer* p_procbuf )
{
proc_mech( p_procbuf, p_procbuf->payload );
}
void GameLink::ExecTerminal( Buffer* p_inbuf,
Buffer* p_outbuf,
Buffer* p_procbuf )
{
// Nothing from the host, or host hasn't acknowledged our last message.
if ( p_inbuf->payload == 0 ) {
return;
}
if ( p_outbuf->payload > 0 ) {
return;
}
// Store output pointer
g_p_outbuf = p_outbuf;
// Process mode select ...
if ( p_inbuf->data[ 0 ] == ':' )
{
// Acknowledge now, to avoid loops.
uint16_t payload = p_inbuf->payload;
p_inbuf->payload = 0;
// Copy out.
memcpy( p_procbuf->data, p_inbuf->data, payload );
p_procbuf->payload = payload;
}
else
{
// Human command
char buf[ Buffer::BUFFER_SIZE + 1 ], *b = buf;
// Convert into printable ASCII
for ( uint32_t i = 0; i < p_inbuf->payload; ++i )
{
uint8_t u8 = p_inbuf->data[ i ];
if ( u8 < 32 || u8 > 127 ) {
*b++ = '?';
} else {
*b++ = (char)u8;
}
}
// ... terminate
*b++ = 0;
// Acknowledge
p_inbuf->payload = 0;
// proc_human( buf ); // <-- deprecated
}
}
//==============================================================================
#endif // C_GAMELINK

View File

@@ -7,4 +7,4 @@ SUBDIRS += direct3d
endif
noinst_LIBRARIES = liboutput.a
liboutput_a_SOURCES = output_direct3d.cpp output_opengl.cpp output_surface.cpp output_tools.cpp output_tools_xbrz.cpp output_ttf.cpp
liboutput_a_SOURCES = output_direct3d.cpp output_opengl.cpp output_surface.cpp output_tools.cpp output_tools_xbrz.cpp output_ttf.cpp output_gamelink.cpp

View File

@@ -0,0 +1,287 @@
#include <sys/types.h>
#include <assert.h>
#include <math.h>
#include "dosbox.h"
#include "logging.h"
#include "menudef.h"
#include "sdlmain.h"
#include "render.h"
#include "vga.h"
#include "mixer.h"
#include "mapper.h"
#include "scancodes_windows.h"
using namespace std;
extern uint32_t RunningProgramHash[4];
extern const char* RunningProgram;
extern "C" SDL_Scancode SDL_GetScancodeFromTable(int, int);
// output API below
void OUTPUT_GAMELINK_Initialize()
{
LOG_MSG("OUTPUT_GAMELINK: Initialize");
}
void OUTPUT_GAMELINK_Select()
{
LOG_MSG("OUTPUT_GAMELINK: Select");
sdl.desktop.want_type = SCREEN_GAMELINK;
render.aspectOffload = true;
sdl.desktop.fullscreen = false;
sdl.mouse.autoenable = false;
sdl.gamelink.want_mouse = false;
}
void OUTPUT_GAMELINK_InputEvent()
{
// LOG_MSG("OUTPUT_GAMELINK: InputEvent");
//
// Client Mouse & Keyboard
if ( GameLink::In( &sdl.gamelink.input, &sdl.gamelink.audio ) )
{
//
// -- Audio
float vol0 = sdl.gamelink.audio.master_vol_l / 100.0f;
float vol1 = sdl.gamelink.audio.master_vol_r / 100.0f;
MIXER_SetMaster( vol0, vol1 );
//
// -- Input
Mouse_CursorMoved(
sdl.gamelink.input.mouse_dx,
sdl.gamelink.input.mouse_dy,
0,
0, true /*emulate*/ );
// Cache old and new
const uint8_t old = sdl.gamelink.input_prev.mouse_btn;;
const uint8_t btn = sdl.gamelink.input.mouse_btn;
// Generate mouse events.
for ( uint8_t i = 0; i < 3; ++i )
{
const uint8_t mask = 1 << i;
if ( ( btn & mask ) && !( old & mask ) ) {
Mouse_ButtonPressed( i ); // L
}
if ( !( btn & mask ) && ( old & mask ) ) {
Mouse_ButtonReleased( i ); // L
}
}
// Generate key events
for ( uint8_t blk = 0; blk < 8; ++blk )
{
const uint32_t old = sdl.gamelink.input_prev.keyb_state[ blk ];
const uint32_t key = sdl.gamelink.input.keyb_state[ blk ];
for ( uint8_t bit = 0; bit < 32; ++bit )
{
const SDL_Scancode scancode = windows_scancode_table[static_cast< int >( ( blk * 32 ) + bit )];
// Build event
SDL_Event ev;
ev.key.type = 0;
ev.key.keysym.scancode = scancode;
ev.key.keysym.mod = KMOD_NONE; // todo
ev.key.keysym.sym = SDLK_UNKNOWN; // todo
const uint32_t mask = 1 << bit;
if ( ( key & mask ) && !( old & mask ) ) {
ev.key.type = SDL_KEYDOWN;
ev.key.state = SDL_PRESSED;
}
if ( !( key & mask ) && ( old & mask ) ) {
ev.key.type = SDL_KEYUP;
ev.key.state = SDL_RELEASED;
}
// Dispatch?
if ( ev.key.type != 0 )
{
MAPPER_CheckEvent( &ev );
}
}
}
// Update history state
memcpy( &sdl.gamelink.input_prev, &sdl.gamelink.input, sizeof( GameLink::sSharedMMapInput_R2 ) );
}
}
void OUTPUT_GAMELINK_TrackingMode()
{
LOG_MSG("OUTPUT_GAMELINK: TrackingMode %i", sdl.gamelink.enable);
if (!sdl.gamelink.enable) return;
bool trackonly_mode = sdl.desktop.want_type != SCREEN_GAMELINK;
// GAMELINK Init (after splash screen, so we have a HWND for tray icon on Win32)
memset(&sdl.gamelink.input_prev, 0, sizeof(GameLink::sSharedMMapInput_R2));
int iresult = GameLink::Init(trackonly_mode);
if (iresult != 1) {
#ifdef WIN32
MessageBoxA( NULL, "ERROR: Couldn't initialise inter-process communication.",
"DOSBox \"Game Link\" Error", MB_OK | MB_ICONSTOP );
#else // WIN32
LOG_MSG("GAMELINK: Couldn't initialise inter-process communication.");
#endif // WIN32
DoKillSwitch();
}
}
#if defined(C_SDL2)
Bitu OUTPUT_GAMELINK_SetSize()
{
LOG_MSG("OUTPUT_GAMELINK: SetSize");
Bitu bpp = 0;
Bitu retFlags = 0;
(void)bpp;
if (sdl.desktop.fullscreen) GFX_ForceFullscreenExit();
sdl.surface = 0;
sdl.clip.w = sdl.draw.width;
sdl.clip.h = sdl.draw.height;
sdl.clip.x = 0;
sdl.clip.y = 0;
if (sdl.gamelink.framebuf == 0 ) {
sdl.gamelink.framebuf = malloc(SCALER_MAXWIDTH * SCALER_MAXHEIGHT * 4); // 32 bit color frame buffer
}
sdl.gamelink.pitch = sdl.draw.width*4;
sdl.desktop.type = SCREEN_GAMELINK;
retFlags = GFX_CAN_32 | GFX_SCALING;
LOG_MSG("gamelink rendersize=%ux%u",
(unsigned int)sdl.draw.width,
(unsigned int)sdl.draw.height);
#if C_XBRZ
if (sdl_xbrz.enable)
{
bool old_scale_on = sdl_xbrz.scale_on;
xBRZ_SetScaleParameters(sdl.draw.width, sdl.draw.height, sdl.desktop.window.width, sdl.desktop.window.height);
if (sdl_xbrz.scale_on != old_scale_on) {
// when we are scaling, we ask render code not to do any aspect correction
// when we are not scaling, render code is allowed to do aspect correction at will
// due to this, at each scale mode change we need to schedule resize again because window size could change
PIC_AddEvent(VGA_SetupDrawing, 50); // schedule another resize here, render has already been initialized at this point and we have just changed its option
}
sdl.clip.x = sdl.clip.y = 0;
sdl.clip.w = sdl.desktop.window.width;
sdl.clip.h = sdl.desktop.window.height;
sdl.gamelink.pitch = sdl.clip.w*4;
}
#endif
if (sdl.clip.w > GameLink::sSharedMMapFrame_R1::MAX_WIDTH || sdl.clip.h > GameLink::sSharedMMapFrame_R1::MAX_HEIGHT) {
#ifdef WIN32
MessageBoxA( NULL, "ERROR: Output resolution too big (windowresolution).",
"DOSBox \"Game Link\" Error", MB_OK | MB_ICONSTOP );
#else // WIN32
LOG_MSG("GAMELINK: Output resolution too big (windowresolution).");
#endif // WIN32
DoKillSwitch();
}
sdl.deferred_resize = false;
sdl.must_redraw_all = true;
sdl.window = GFX_SetSDLWindowMode(640, 480, SCREEN_GAMELINK);
if (sdl.window == NULL)
E_Exit("Could not set windowed video mode %ix%i: %s", (int)sdl.draw.width, (int)sdl.draw.height, SDL_GetError());
sdl.surface = SDL_GetWindowSurface(sdl.window);
return retFlags;
}
#else
#error "gamelink output requires SDL2"
#endif /*!defined(C_SDL2)*/
bool OUTPUT_GAMELINK_StartUpdate(uint8_t* &pixels, Bitu &pitch)
{
// LOG_MSG("OUTPUT_GAMELINK: StartUpdate");
#if C_XBRZ
if (sdl_xbrz.enable && sdl_xbrz.scale_on)
{
sdl_xbrz.renderbuf.resize(sdl.draw.width * sdl.draw.height);
pixels = sdl_xbrz.renderbuf.empty() ? nullptr : reinterpret_cast<uint8_t*>(&sdl_xbrz.renderbuf[0]);
pitch = sdl.draw.width * sizeof(uint32_t);
}
else
#endif
{
pixels = (uint8_t *)sdl.gamelink.framebuf;
pitch = sdl.gamelink.pitch;
}
sdl.updating = true;
return true;
}
void OUTPUT_GAMELINK_Transfer()
{
// LOG_MSG("OUTPUT_GAMELINK: Transfer");
GameLink::Out( (uint16_t)sdl.desktop.window.width, (uint16_t)sdl.desktop.window.height, render.src.ratio,
sdl.gamelink.want_mouse,
RunningProgram,
RunningProgramHash,
(const uint8_t*)sdl.gamelink.framebuf,
MemBase );
}
void OUTPUT_GAMELINK_EndUpdate(const uint16_t *changedLines)
{
// LOG_MSG("OUTPUT_GAMELINK: EndUpdate");
#if C_XBRZ
if (sdl_xbrz.enable && sdl_xbrz.scale_on)
{
const uint32_t srcWidth = sdl.draw.width;
const uint32_t srcHeight = sdl.draw.height;
if (sdl_xbrz.renderbuf.size() == (unsigned int)srcWidth * (unsigned int)srcHeight && srcWidth > 0 && srcHeight > 0)
{
// please use sdl.clip to keep screen positioning consistent with the rest of the emulator
int clipWidth = sdl.clip.w;
int clipHeight = sdl.clip.h;
int clipX = sdl.clip.x;
int clipY = sdl.clip.y;
// 1. xBRZ-scale render buffer into xbrz pixel buffer
unsigned int xbrzWidth = 0;
unsigned int xbrzHeight = 0;
uint32_t* xbrzBuf;
xbrzWidth = srcWidth * (unsigned int)sdl_xbrz.scale_factor;
xbrzHeight = srcHeight * (unsigned int)sdl_xbrz.scale_factor;
sdl_xbrz.pixbuf.resize(xbrzWidth * xbrzHeight);
const uint32_t* renderBuf = &sdl_xbrz.renderbuf[0]; // help VS compiler a little + support capture by value
xbrzBuf = &sdl_xbrz.pixbuf[0];
xBRZ_Render(renderBuf, xbrzBuf, changedLines, (int)srcWidth, (int)srcHeight, sdl_xbrz.scale_factor);
// 2. nearest neighbor/bilinear scale xbrz buffer into output surface clipping area
uint32_t* clipTrg = reinterpret_cast<uint32_t*>(static_cast<char*>(sdl.gamelink.framebuf) + clipY * sdl.gamelink.pitch + (unsigned int)clipX * sizeof(uint32_t));
xBRZ_PostScale(&xbrzBuf[0], (int)xbrzWidth, (int)xbrzHeight, (int)(xbrzWidth * sizeof(uint32_t)),
&clipTrg[0], clipWidth, clipHeight, sdl.gamelink.pitch,
sdl_xbrz.postscale_bilinear, sdl_xbrz.task_granularity);
}
}
#endif /*C_XBRZ*/
if (!menu.hidecycles) frames++;
}
void OUTPUT_GAMELINK_Shutdown()
{
LOG_MSG("OUTPUT_GAMELINK: Shutdown");
GameLink::Term();
}

View File

@@ -0,0 +1,24 @@
#include "dosbox.h"
#ifndef DOSBOX_OUTPUT_GAMELINK_H
#define DOSBOX_OUTPUT_GAMELINK_H
#if C_GAMELINK
#include "../gamelink/gamelink.h"
#endif // C_GAMELINK
// output API
void OUTPUT_GAMELINK_Initialize();
void OUTPUT_GAMELINK_Select();
Bitu OUTPUT_GAMELINK_GetBestMode(Bitu flags);
Bitu OUTPUT_GAMELINK_SetSize();
bool OUTPUT_GAMELINK_StartUpdate(uint8_t* &pixels, Bitu &pitch);
void OUTPUT_GAMELINK_EndUpdate(const uint16_t *changedLines);
void OUTPUT_GAMELINK_Shutdown();
// specific additions
void OUTPUT_GAMELINK_InputEvent();
void OUTPUT_GAMELINK_Transfer();
void OUTPUT_GAMELINK_TrackingMode();
#endif /*DOSBOX_OUTPUT_GAMELINK_H*/

View File

@@ -0,0 +1,79 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <SDL_scancode.h>
/* Windows scancode to SDL scancode mapping table */
/* derived from Microsoft scan code document, http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc */
/* *INDENT-OFF* */
static const SDL_Scancode windows_scancode_table[] =
{
/* 0 1 2 3 4 5 6 7 */
/* 8 9 A B C D E F */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, /* 0 */
SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0, SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, /* 0 */
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, /* 1 */
SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, /* 1 */
SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, /* 2 */
SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, /* 2 */
SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_PRINTSCREEN,/* 3 */
SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, /* 3 */
SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_KP_7, /* 4 */
SDL_SCANCODE_KP_8, SDL_SCANCODE_KP_9, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_KP_4, SDL_SCANCODE_KP_5, SDL_SCANCODE_KP_6, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_KP_1, /* 4 */
SDL_SCANCODE_KP_2, SDL_SCANCODE_KP_3, SDL_SCANCODE_KP_0, SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_NONUSBACKSLASH,SDL_SCANCODE_F11, /* 5 */
SDL_SCANCODE_F12, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_APPLICATION, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 5 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, /* 6 */
SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 6 */
SDL_SCANCODE_INTERNATIONAL2, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL1, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 7 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL4, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL5, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL3, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 7 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 8 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 8 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 9 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RCTRL, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 9 */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* A */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* A */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_PRINTSCREEN, /* B */
SDL_SCANCODE_RALT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* B */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_HOME, /* C */
SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_END, /* C */
SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* D */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_MENU, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* D */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* E */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* E */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* F */
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN /* F */
};
/* *INDENT-ON* */

View File

@@ -148,6 +148,9 @@
/* Define to 1 to use opengl display output support */
#define C_OPENGL 1
/* Define to 1 to enable gamelink support */
#define C_GAMELINK 1
/* Set to 1 to enable XBRZ support */
#define C_XBRZ 1