diff --git a/configure.ac b/configure.ac index 409531c4e..c5bde554f 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index aaee94dd3..c49fbe7a0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/gamelink/Makefile.am b/src/gamelink/Makefile.am new file mode 100644 index 000000000..c2bf51dd5 --- /dev/null +++ b/src/gamelink/Makefile.am @@ -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 diff --git a/src/gamelink/gamelink.cpp b/src/gamelink/gamelink.cpp new file mode 100644 index 000000000..e5e93042d --- /dev/null +++ b/src/gamelink/gamelink.cpp @@ -0,0 +1,737 @@ + +// Game Link + +//------------------------------------------------------------------------------ +// Dependencies +//------------------------------------------------------------------------------ + +#include "config.h" + +#if C_GAMELINK + +// External Dependencies +#ifdef WIN32 +#include +#else // WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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: +// + +// 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 + diff --git a/src/gamelink/gamelink.h b/src/gamelink/gamelink.h new file mode 100644 index 000000000..325473d98 --- /dev/null +++ b/src/gamelink/gamelink.h @@ -0,0 +1,175 @@ +#ifndef __GAMELINK_H___ +#define __GAMELINK_H___ + +#include "dosbox.h" + +#ifdef WIN32 +#include +#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__ diff --git a/src/gamelink/gamelink_term.cpp b/src/gamelink/gamelink_term.cpp new file mode 100644 index 000000000..7906caeab --- /dev/null +++ b/src/gamelink/gamelink_term.cpp @@ -0,0 +1,260 @@ + +// Game Link - Console + +//------------------------------------------------------------------------------ +// Dependencies +//------------------------------------------------------------------------------ + +#include "config.h" + +#if C_GAMELINK + +// External Dependencies +#ifdef WIN32 +#else // WIN32 +#include +#include +#include +#endif // WIN32 + +// Local Dependencies +#include "dosbox.h" +#include "gamelink.h" +#include "sdlmain.h" +#include "../resource.h" +#include +#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 + diff --git a/src/output/Makefile.am b/src/output/Makefile.am index 0094427b0..fa44aa69b 100644 --- a/src/output/Makefile.am +++ b/src/output/Makefile.am @@ -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 diff --git a/src/output/output_gamelink.cpp b/src/output/output_gamelink.cpp new file mode 100644 index 000000000..fc6c30dce --- /dev/null +++ b/src/output/output_gamelink.cpp @@ -0,0 +1,287 @@ +#include +#include +#include + +#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(&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(static_cast(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(); +} diff --git a/src/output/output_gamelink.h b/src/output/output_gamelink.h new file mode 100644 index 000000000..b5295bbd0 --- /dev/null +++ b/src/output/output_gamelink.h @@ -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*/ \ No newline at end of file diff --git a/src/output/scancodes_windows.h b/src/output/scancodes_windows.h new file mode 100644 index 000000000..2ddd8e46a --- /dev/null +++ b/src/output/scancodes_windows.h @@ -0,0 +1,79 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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 + +/* 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* */ diff --git a/vs/config.h b/vs/config.h index ba16ac69c..257a6e8d0 100644 --- a/vs/config.h +++ b/vs/config.h @@ -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