diff --git a/.gitignore b/.gitignore index 25f452cfa..9ba7a851c 100644 --- a/.gitignore +++ b/.gitignore @@ -345,7 +345,8 @@ contrib/linux/com.dosbox_x.DOSBox-X.metainfo.xml *.a *.la *.deps -src/dosbox-x +/src/dosbox-x +/src/dosbox-x.exe Makefile config.h config.status @@ -374,6 +375,12 @@ vs/libpng/linux-host/* vs/libpng/linux-build vs/libpng/linux-build/* +/vs/freetype/linux-build +/vs/freetype/linux-host + +/vs/sdl2/linux-build +/vs/sdl2/linux-host + /vs/libpdcurses/wincon/xmas.exe /vs/libpdcurses/wincon/worm.exe /vs/libpdcurses/wincon/tuidemo.exe @@ -420,3 +427,8 @@ vs/sdlnet/linux-host/ # Ignore autogenerated contrib/macos/dosbox-x.plist .dirstamp + +# ignore default dosbox config files (used for manual testing) +/dosbox-x.conf +/src/dosbox-x.conf + diff --git a/CREDITS.md b/CREDITS.md index 9adb8c38c..720c55d52 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -104,3 +104,4 @@ Tiny File Dialogs (vareille; zlib licence) src/libs/tinyfiledialogs/* MAME CHD support (Romain Tisserand; BSD 3-clause) src/libs/libchdr/* +Game Link IPC protocol (David Walters, ported by Jörg Walter; GPLv2+) src/gamelink/* src/output/output_gamelink* diff --git a/configure.ac b/configure.ac index 359b52b68..4b33850ee 100644 --- a/configure.ac +++ b/configure.ac @@ -420,6 +420,24 @@ 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 (only for SDL2)]),,enable_gamelink=yes) +AC_MSG_CHECKING(whether gamelink is enabled) +if test x$enable_gamelink = xyes; then + if test "x$SDL2_LIBS" = "x"; then + AC_MSG_RESULT(no (SDL2 missing)) + else + AC_MSG_RESULT(yes) + C_GAMELINK=1 + AC_DEFINE(C_GAMELINK,1) + fi +else + AC_MSG_RESULT(no) +fi +AM_CONDITIONAL([C_GAMELINK], [test "x$C_GAMELINK" = x1]) + + 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) @@ -1229,6 +1247,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/contrib/translations/de/de_DE.lng b/contrib/translations/de/de_DE.lng index 088e2c4a5..8b6e3ba34 100644 --- a/contrib/translations/de/de_DE.lng +++ b/contrib/translations/de/de_DE.lng @@ -2636,6 +2636,9 @@ OpenGL perfect :MENU:output_ttf TrueType font . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Doublescan . diff --git a/contrib/translations/de/de_pc98.txt b/contrib/translations/de/de_pc98.txt index a068013ad..8da5d4fa0 100644 --- a/contrib/translations/de/de_pc98.txt +++ b/contrib/translations/de/de_pc98.txt @@ -2628,6 +2628,9 @@ OpenGL perfect :MENU:output_ttf TrueType font . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Doublescan . diff --git a/contrib/translations/en/en_US.lng b/contrib/translations/en/en_US.lng index 90ead4418..eff4f4128 100644 --- a/contrib/translations/en/en_US.lng +++ b/contrib/translations/en/en_US.lng @@ -2599,6 +2599,9 @@ OpenGL perfect :MENU:output_ttf TrueType font . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Doublescan . diff --git a/contrib/translations/es/es_ES.lng b/contrib/translations/es/es_ES.lng index a449c3c91..95ab2ead2 100644 --- a/contrib/translations/es/es_ES.lng +++ b/contrib/translations/es/es_ES.lng @@ -2605,6 +2605,9 @@ OpenGL perfecto :MENU:output_ttf Fuente TrueType . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Doublescan . diff --git a/contrib/translations/fr/fr_FR.lng b/contrib/translations/fr/fr_FR.lng index 9ca57a2f4..be3a0406c 100644 --- a/contrib/translations/fr/fr_FR.lng +++ b/contrib/translations/fr/fr_FR.lng @@ -2605,6 +2605,9 @@ OpenGL (Parfait) :MENU:output_ttf Police de caractères TrueType . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Doublescan . diff --git a/contrib/translations/ja/ja_JP.lng b/contrib/translations/ja/ja_JP.lng index 882db75d1..0d7a14949 100644 --- a/contrib/translations/ja/ja_JP.lng +++ b/contrib/translations/ja/ja_JP.lng @@ -2587,6 +2587,9 @@ OpenGL perfect :MENU:output_ttf TrueTypeフォント . +:MENU:output_gamelink +Game Link +. :MENU:doublescan ダブルスキャン . diff --git a/contrib/translations/ko/ko_KR.lng b/contrib/translations/ko/ko_KR.lng index 906d55918..f3bdfe72b 100644 --- a/contrib/translations/ko/ko_KR.lng +++ b/contrib/translations/ko/ko_KR.lng @@ -2600,6 +2600,9 @@ Surface :MENU:output_ttf TrueType 글꼴 . +:MENU:output_gamelink +Game Link +. :MENU:doublescan 더블 스캔 . diff --git a/contrib/translations/nl/nl_NL.lng b/contrib/translations/nl/nl_NL.lng index ea24dca07..f8875e2f8 100644 --- a/contrib/translations/nl/nl_NL.lng +++ b/contrib/translations/nl/nl_NL.lng @@ -2602,6 +2602,9 @@ OpenGL perfect :MENU:output_ttf TrueType-lettertype . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Dubbelscan . diff --git a/contrib/translations/pt/pt_BR.lng b/contrib/translations/pt/pt_BR.lng index b2831d385..774746348 100644 --- a/contrib/translations/pt/pt_BR.lng +++ b/contrib/translations/pt/pt_BR.lng @@ -2622,6 +2622,9 @@ OpenGL pixel perfeito :MENU:output_ttf Fonte TrueType . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Dupla varredura . diff --git a/contrib/translations/tr/tr_TR.lng b/contrib/translations/tr/tr_TR.lng index 7179b0e94..ad3e0453d 100644 --- a/contrib/translations/tr/tr_TR.lng +++ b/contrib/translations/tr/tr_TR.lng @@ -2600,6 +2600,9 @@ OpenGL mükemmel :MENU:output_ttf TrueType yazıtipi . +:MENU:output_gamelink +Game Link +. :MENU:doublescan Çift tarama . diff --git a/contrib/translations/zh/zh_CN.lng b/contrib/translations/zh/zh_CN.lng index 36dff4ff6..a0af257f3 100644 --- a/contrib/translations/zh/zh_CN.lng +++ b/contrib/translations/zh/zh_CN.lng @@ -2565,6 +2565,9 @@ OpenGL perfect :MENU:output_ttf TrueType 字体 . +:MENU:output_gamelink +Game Link +. :MENU:doublescan 双重扫描 . diff --git a/contrib/translations/zh/zh_TW.lng b/contrib/translations/zh/zh_TW.lng index a022a5320..1703963ba 100644 --- a/contrib/translations/zh/zh_TW.lng +++ b/contrib/translations/zh/zh_TW.lng @@ -2572,6 +2572,9 @@ OpenGL perfect :MENU:output_ttf TrueType 字型 . +:MENU:output_gamelink +Game Link +. :MENU:doublescan 雙重掃描 . diff --git a/include/mapper.h b/include/mapper.h index 706b7f970..7d9c980b4 100644 --- a/include/mapper.h +++ b/include/mapper.h @@ -42,6 +42,9 @@ void MAPPER_RunEvent(Bitu); void MAPPER_RunInternal(); void MAPPER_LosingFocus(void); +union SDL_Event; +void MAPPER_CheckEvent(SDL_Event *); + std::string mapper_event_keybind_string(const std::string &x); #define MMOD1 0x1 diff --git a/include/mixer.h b/include/mixer.h index 41adc0767..6442f5733 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -113,6 +113,8 @@ public: MixerChannel * next; }; +void MIXER_SetMaster(float vol0,float vol1); + MixerChannel * MIXER_AddChannel(MIXER_Handler handler,Bitu freq,const char * name); MixerChannel * MIXER_FindChannel(const char * name); /* Find the device you want to delete with findchannel "delchan gets deleted" */ diff --git a/include/sdlmain.h b/include/sdlmain.h index 3695c562d..3b0358868 100644 --- a/include/sdlmain.h +++ b/include/sdlmain.h @@ -12,6 +12,8 @@ #ifndef DOSBOX_SDLMAIN_H #define DOSBOX_SDLMAIN_H +#include + enum SCREEN_TYPES { SCREEN_SURFACE ,SCREEN_OPENGL // [FIXME] cannot make this conditional because somehow SDL2 code uses it while C_OPENGL is definitely disabled by C_SDL2 so SCREEN_OPENGL is unavailable @@ -19,6 +21,7 @@ enum SCREEN_TYPES { ,SCREEN_DIRECT3D #endif ,SCREEN_TTF + ,SCREEN_GAMELINK }; enum AUTOLOCK_FEEDBACK @@ -123,6 +126,19 @@ struct SDL_Block { SCREEN_TYPES type = (SCREEN_TYPES)0; SCREEN_TYPES want_type = (SCREEN_TYPES)0; } desktop; +#if C_GAMELINK + struct { + Bitu pitch; + void * framebuf; + GameLink::sSharedMMapInput_R2 input_prev; + GameLink::sSharedMMapInput_R2 input; + GameLink::sSharedMMapAudio_R1 audio; + bool want_mouse; + bool enable; + bool snoop; + Bitu loadaddr; + } gamelink; +#endif // C_GAMELINK struct { SDL_Surface * surface = NULL; #if (HAVE_DDRAW_H) && defined(WIN32) @@ -214,9 +230,14 @@ void GFX_DrawSDLMenu(DOSBoxMenu &menu, DOSBoxMenu::displaylist &dl); void GFX_LogSDLState(void); void GFX_SDL_Overscan(void); void GFX_SetIcon(void); +void GFX_ForceFullscreenExit(void); void SDL_rect_cliptoscreen(SDL_Rect &r); void UpdateWindowDimensions(void); void UpdateWindowDimensions(Bitu width, Bitu height); +void DoKillSwitch(); +void ResetSystem(bool pressed); +void PauseDOSBox(bool pressed); +bool systemmessagebox(char const * aTitle, char const * aMessage, char const * aDialogType, char const * aIconType, int aDefaultButton); #if defined(C_SDL2) SDL_Window* GFX_SetSDLWindowMode(uint16_t width, uint16_t height, SCREEN_TYPES screenType); diff --git a/src/Makefile.am b/src/Makefile.am index aaee94dd3..24922a651 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,6 +38,13 @@ if !EMSCRIPTEN dosbox_x_LDADD += aviwriter/libaviwriter.a endif +if C_GAMELINK +if !EMSCRIPTEN +SUBDIRS += gamelink +dosbox_x_LDADD += gamelink/libgamelink.a +endif +endif + if C_DIRECT3D dosbox_x_LDADD += output/direct3d/liboutputdirect3d.a endif diff --git a/src/dos/crc32.h b/src/dos/crc32.h new file mode 100644 index 000000000..e21ae8835 --- /dev/null +++ b/src/dos/crc32.h @@ -0,0 +1,64 @@ +#ifndef CRC32_H +#define CRC32_H +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +static uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +static uint32_t crc32(uint32_t crc, uint8_t* buf, Bitu len) { + const uint8_t *p; + + p = buf; + crc = crc ^ ~0U; + + while (len--) crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + + return crc ^ ~0U; +} +#endif diff --git a/src/dos/dos_execute.cpp b/src/dos/dos_execute.cpp index b60548cf9..7034eed6c 100644 --- a/src/dos/dos_execute.cpp +++ b/src/dos/dos_execute.cpp @@ -29,6 +29,10 @@ #include "debug.h" #include "cpu.h" #include "menu.h" +#include "crc32.h" + +uint32_t RunningProgramHash[4] = {0,0,0,0}; +uint32_t RunningProgramLoadAddress = 0; const char * RunningProgram="DOSBOX-X"; @@ -276,6 +280,8 @@ bool DOS_Execute(const char* name, PhysPt block_pt, uint8_t flags) { PhysPt loadaddress;RealPt relocpt; uint32_t headersize = 0, imagesize = 0; DOS_ParamBlock block(block_pt); + uint32_t checksum = 0; + uint32_t checksum_bytes = 0; block.LoadData(); //Remove the loadhigh flag for the moment! @@ -403,6 +409,7 @@ bool DOS_Execute(const char* name, PhysPt block_pt, uint8_t flags) { } else loadseg=block.overlay.loadseg; /* Load the executable */ loadaddress=PhysMake(loadseg,0); + RunningProgramLoadAddress = loadaddress; if (iscom) { /* COM Load 64k - 256 bytes max */ /* how big is the COM image? make sure it fits */ @@ -414,18 +421,24 @@ bool DOS_Execute(const char* name, PhysPt block_pt, uint8_t flags) { pos=0;DOS_SeekFile(fhandle,&pos,DOS_SEEK_SET); DOS_ReadFile(fhandle,loadbuf,&readsize); + checksum = crc32(checksum, loadbuf, readsize); + checksum_bytes += readsize; MEM_BlockWrite(loadaddress,loadbuf,readsize); } else { /* EXE Load in 32kb blocks and then relocate */ if (imagesize > (unsigned int)(memsize*0x10)) E_Exit("DOS:Not enough memory for EXE image"); pos=headersize;DOS_SeekFile(fhandle,&pos,DOS_SEEK_SET); while (imagesize>0x7FFF) { readsize=0x8000;DOS_ReadFile(fhandle,loadbuf,&readsize); + checksum = crc32(checksum, loadbuf, readsize); + checksum_bytes += readsize; MEM_BlockWrite(loadaddress,loadbuf,readsize); // if (readsize!=0x8000) LOG(LOG_EXEC,LOG_NORMAL)("Illegal header"); loadaddress+=0x8000;imagesize-=0x8000; } if (imagesize>0) { readsize=(uint16_t)imagesize;DOS_ReadFile(fhandle,loadbuf,&readsize); + checksum = crc32(checksum, loadbuf, readsize); + checksum_bytes += readsize; MEM_BlockWrite(loadaddress,loadbuf,readsize); // if (readsize!=imagesize) LOG(LOG_EXEC,LOG_NORMAL)("Illegal header"); } @@ -533,6 +546,10 @@ bool DOS_Execute(const char* name, PhysPt block_pt, uint8_t flags) { DOS_MCB pspmcb(dos.psp()-1); pspmcb.SetFileName(stripname); DOS_UpdatePSPName(); + RunningProgramHash[0] = head.checksum; + RunningProgramHash[1] = checksum_bytes; + RunningProgramHash[2] = checksum; + RunningProgramHash[3] = 0; } if (flags==LOAD) { 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/README.md b/src/gamelink/README.md new file mode 100644 index 000000000..3f257d874 --- /dev/null +++ b/src/gamelink/README.md @@ -0,0 +1,144 @@ +How to use the Game Link feature +================================ + +Game Link is a local inter-process communication protocol that lets other +tools (notably, Grid Cartographer) connect to DOSBox and inspect memory +values and manage input and output. Clients like Grid Cartographer can track +your movement through game worlds and draw a map as you go. This README +assumes you own Grid Cartographer 4, but alternate clients can easily be +written. + +This feature is based on the reference implementation found at +https://github.com/hiddenasbestos/dosbox-gridc/ and is compatible with +revision 4 of the protocol, which is the latest one as of 2022-12-01. That +reference implementation is based on DOSBox SVN, so it lacks a lot of +features that DOSBox-X has. + +GameLink can operate in two modes: fully remote controlled, and tracking +only. + + +Fully Remote / Integrated Mode +------------------------------ + +In remote-controlled mode, DOSBox transmits the screen contents to Grid +Cartographer (GC), which displays it inside its main window. Likewise, +keyboard/mouse input is sent by GC. There will still be a DOSBox window, but +it will display nothing, you can simply minimize it. + +An example config looks like this: + +``` +[sdl] +output=gamelink +gamelink master = true + +[render] +scaler=xbrz +``` + +If you use the `xbrz` scaler, you must use `windowresolution` to set the frame +buffer size that is sent to GC. For all other scalers, the native +(software-scaled) resolution is used. GC will then perform scaling of this +fixed-resolution image to the appropriate resolution, including optional +aspect correction. + +Hardware scalers are not available beyond what GC offers, but it is possible +to use a DOSBox software scaler in addition to that. See the example above +using the `xbrz` scaler. + + + +Tracking Mode +------------- + +In tracking mode, DOSBox will work as usual. Grid Cartographer (GC) or other +clients can inspect memory, but don't get input/output control. This mode is +especially useful in dual-screen setups. + +An example config looks like this: + +``` +[sdl] +output = surface +# ... or any other non-gamelink output +gamelink master = true +``` + +Just add this single option to an existing working configuration and GC can +track your movement through your game world. Everything else should work as +normal. + + + +Supported Games +--------------- + +Grid Cartographer (GC) comes with a library of game profiles. Each game needs +a custom profile, since the memory addresses of interesting values varies +from game to game. See the GC web site and the web in general for details. + + + +Compatibility +------------- + +Unfortunately, DOSBox-X loads executables at different addresses from the +reference DOSBox-GRIDC. This breaks all game profiles. There are two +workarounds in place to deal with this: + +A default offset will be added to every memory address request. This should +work with some simple programs. + +For all other programs, you need to find out its original load address and +modify the GC profile to tell DOSBox this address. This is more involved: + + +1. You need to run the game in the original DOSBox-GRIDC together with GC +itself. + +2. Then you run the same game in DOSBox-X, which has been configured with one +extra option: + +``` +[sdl] +gamelink snoop = true +``` + +3. Load the same save game in both DOSBox instances and move to the same +place. Once a match is detected, a popup dialog window should tell you two +different configuration values. + +With these two values, you can configure Game Link in two different ways, +whatever works best for you. + +The first and easiest option is to enter the first value from the dialog +window into the DOSBox-X configuration file: + +``` +[sdl] +gamelink load address = 6768 +``` + +The second option is to modify your Grid Cartographer profile: + +1. Find the game profile XML file and locate the `` tag near the start +of the file. It will contain a list of hexadecimal numbers. + +2. Add the second value from the dialog window (the one starting with `100`) +to the end of this list. + +3. Exit everything including GC, disable `gamelink snoop` and enjoy +GC+DOSBox-X! + + + + +Developer Information +--------------------- + +To write your own client, look at `src/gamelink/gamelink.cpp` to get an idea +how data is transmitted back and forth via one mutex and one shared memory +segment. `src/gamelink/gamelink.h` contains defititions of the structs that +make up the shared memory. Access to that memory is only permitted while +holding the mutex. diff --git a/src/gamelink/gamelink.cpp b/src/gamelink/gamelink.cpp new file mode 100644 index 000000000..6e12ce189 --- /dev/null +++ b/src/gamelink/gamelink.cpp @@ -0,0 +1,750 @@ + +// Game Link + +//------------------------------------------------------------------------------ +// Dependencies +//------------------------------------------------------------------------------ + +#include "config.h" + +#if C_GAMELINK + +// define this to debug memory offset detection +// #define DEBUG_SNOOP + +// Local Dependencies +#include "dosbox.h" +#include "gamelink.h" +#include "logging.h" +#include "sdlmain.h" +#include "../resource.h" + +// 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" + +extern bool is_paused; +extern uint32_t RunningProgramLoadAddress; + +//============================================================================== + +//------------------------------------------------------------------------------ +// Local Definitions +//------------------------------------------------------------------------------ + +#define SYSTEM_NAME "DOSBox" + +#define PROTOCOL_VER 4 + +#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 ) { + LOG_MSG( "GAMELINK: WARNING: MUTEX \"%s\" already exists, assuming it is left over from a crash.", p_name ); + return 1; + } + + // 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 ) + { + LOG_MSG( "GAMELINK: WARNING: MUTEX \"%s\" already exists, assuming it is left over from a crash.", p_name ); + return 1; + } + + // 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; + + // Store the mode we're in. + g_trackonly_mode = trackonly_mode; + + // Already initialised? + if ( g_mutex_handle ) + { + //LOG_MSG( "GAMELINK: Ignoring re-initialisation." ); + + // success + return 1; + } + + // 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; + uint8_t *membase; + + g_membase_size = size; + + if (!sdl.gamelink.enable) { + membase = (uint8_t*)malloc(g_membase_size); + return membase; + } + + // 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) ); + + 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; +} + +void GameLink::FreeRAM(void *membase) +{ + // free memory if we did not use shared memory + if (membase != ((uint8_t*)g_p_shared_memory) + MEMORY_MAP_CORE_SIZE) { + free(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 %i %i %i", frame_width, frame_height, g_trackonly_mode); + // 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 { +#ifdef DEBUG_SNOOP + 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); +#endif + } + + { + + // Find peek offset + uint32_t offset = RunningProgramLoadAddress - sdl.gamelink.loadaddr; + 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 { +#ifdef DEBUG_SNOOP + 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)); +#endif + } + } + } + + int found_offset = 0; + + 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) { +#ifdef DEBUG_SNOOP + LOG_MSG("Load Address = %06x", RunningProgramLoadAddress); +#endif + 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 ]; + if (g_p_shared_memory->peek.addr[ i ] > 0x1000'0000) continue; + 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) { + found_offset = (addr-base); +#ifdef DEBUG_SNOOP + LOG_MSG("Match at %06x, Offset %06x, Original Load Address %06x", addr, addr-base, RunningProgramLoadAddress - found_offset); +#else + // assume first offset is the correct one + break; +#endif + } + } + } + +#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 + + static int last_offset = 0; + if (sdl.gamelink.snoop && found_offset && found_offset != last_offset) { + last_offset = found_offset; + LOG_MSG("gamelink load address = %i", RunningProgramLoadAddress - found_offset); + LOG_MSG("Grid Cartorapher profile XML value: %x", 0x10000000+(RunningProgramLoadAddress - found_offset)); + + char result[256]; + snprintf(result, sizeof(result), "Found possible ram offset. Config values are:\ngamelink load address = %i\nGrid Cartographer profile XML : %x", + RunningProgramLoadAddress - found_offset, + 0x10000000+(RunningProgramLoadAddress - found_offset)); + systemmessagebox("Game Link Snoop Success", result, "ok", "info", 1); + + } + + // 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..c57e96e60 --- /dev/null +++ b/src/gamelink/gamelink.h @@ -0,0 +1,205 @@ +#ifndef __GAMELINK_H___ +#define __GAMELINK_H___ + +#include "config.h" + +#if C_GAMELINK + +#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 FreeRAM(void *membase); + + 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(); + + +// 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. There are two ways to configure the original load +// address. One way is to use option "gamelink load address", the other is to +// modify the Grid Cartographer profile: + +// To do so, 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, run the game in dosbox-gridc and +// connect to Grid Cartographer. Start DOSBox-X in parallel with +// the "gamelinksnoop = true" option. Load the same game in both, +// dosbox-gridc and DOSBox-X, 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 GAMELINK_LOAD_ADDRESS_DEFAULT 0x1a70 + +}; // namespace GameLink + +//============================================================================== +#endif + +#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/gamelink/scancodes_windows.h b/src/gamelink/scancodes_windows.h new file mode 100644 index 000000000..2ddd8e46a --- /dev/null +++ b/src/gamelink/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/src/gui/menu.cpp b/src/gui/menu.cpp index 336edd369..08514244c 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -381,6 +381,9 @@ static const char *def_menu_video_output[] = #endif #if defined(USE_TTF) "output_ttf", +#endif +#if C_GAMELINK + "output_gamelink", #endif "--", "doublescan", diff --git a/src/gui/menu_callback.cpp b/src/gui/menu_callback.cpp index 17a39fefd..ebef8774f 100644 --- a/src/gui/menu_callback.cpp +++ b/src/gui/menu_callback.cpp @@ -3167,6 +3167,10 @@ void AllocCallback1() { #if defined(USE_TTF) mainMenu.alloc_item(DOSBoxMenu::item_type_id,"output_ttf").set_text("TrueType font"). set_callback_function(output_menu_callback); +#endif +#if C_GAMELINK + mainMenu.alloc_item(DOSBoxMenu::item_type_id,"output_gamelink").set_text("Game Link"). + set_callback_function(output_menu_callback); #endif mainMenu.alloc_item(DOSBoxMenu::item_type_id,"doublescan").set_text("Doublescan"). set_callback_function(doublescan_menu_callback); diff --git a/src/gui/sdlmain.cpp b/src/gui/sdlmain.cpp index dfff499a0..3b7dd91a4 100644 --- a/src/gui/sdlmain.cpp +++ b/src/gui/sdlmain.cpp @@ -1336,6 +1336,11 @@ void PauseDOSBoxLoop(Bitu /*unused*/) { #if C_EMSCRIPTEN emscripten_sleep(0); SDL_PollEvent(&event); +#elif C_GAMELINK + // Keep GameLink ticking over. + SDL_Delay(100); + OUTPUT_GAMELINK_Transfer(); + SDL_PollEvent(&event); #else SDL_WaitEvent(&event); // since we're not polling, cpu usage drops to 0. #endif @@ -1907,6 +1912,12 @@ Bitu GFX_SetSize(Bitu width, Bitu height, Bitu flags, double scalex, double scal break; #endif +#if C_GAMELINK + case SCREEN_GAMELINK: + retFlags = OUTPUT_GAMELINK_SetSize(); + break; +#endif + #if C_DIRECT3D case SCREEN_DIRECT3D: retFlags = OUTPUT_DIRECT3D_SetSize(); @@ -1936,6 +1947,13 @@ Bitu GFX_SetSize(Bitu width, Bitu height, Bitu flags, double scalex, double scal LOG_MSG("SDL: Failed everything including falling back to surface in GFX_GetSize"); // completely failed it seems } +#if C_GAMELINK + if (!OUTPUT_GAMELINK_InitTrackingMode() && sdl.desktop.want_type == SCREEN_GAMELINK) { + OUTPUT_SURFACE_Select(); + retFlags = OUTPUT_SURFACE_SetSize(); + } +#endif + // we have selected an actual desktop type sdl.desktop.type = sdl.desktop.want_type; @@ -2403,7 +2421,7 @@ void CaptureMouseNotify(bool capture) } static void CaptureMouse(bool pressed) { - if (!pressed || is_paused) + if (!pressed || is_paused || sdl.desktop.want_type == SCREEN_GAMELINK) return; CaptureMouseNotify(); @@ -2750,7 +2768,7 @@ void GFX_SwitchFullScreen(void) } static void SwitchFullScreen(bool pressed) { - if (!pressed) + if (!pressed || sdl.desktop.want_type == SCREEN_GAMELINK) return; GFX_LosingFocus(); @@ -2854,6 +2872,11 @@ bool GFX_StartUpdate(uint8_t* &pixels,Bitu &pitch) return OUTPUT_OPENGL_StartUpdate(pixels, pitch); #endif +#if C_GAMELINK + case SCREEN_GAMELINK: + return OUTPUT_GAMELINK_StartUpdate(pixels, pitch); +#endif + #if C_DIRECT3D case SCREEN_DIRECT3D: return OUTPUT_DIRECT3D_StartUpdate(pixels, pitch); @@ -2938,6 +2961,12 @@ void GFX_EndUpdate(const uint16_t *changedLines) { break; #endif +#if C_GAMELINK + case SCREEN_GAMELINK: + OUTPUT_GAMELINK_EndUpdate(changedLines); + break; +#endif + #if C_DIRECT3D case SCREEN_DIRECT3D: OUTPUT_DIRECT3D_EndUpdate(changedLines); @@ -2948,6 +2977,10 @@ void GFX_EndUpdate(const uint16_t *changedLines) { break; } +#if C_GAMELINK + OUTPUT_GAMELINK_Transfer(); +#endif + if (changedLines != NULL) { sdl.must_redraw_all = false; @@ -3007,6 +3040,11 @@ Bitu GFX_GetRGB(uint8_t red, uint8_t green, uint8_t blue) { # endif #endif +#if C_GAMELINK + case SCREEN_GAMELINK: + return (((unsigned long)blue << 0ul) | ((unsigned long)green << 8ul) | ((unsigned long)red << 16ul)) | (255ul << 24ul); +#endif + #if C_DIRECT3D case SCREEN_DIRECT3D: return SDL_MapRGB(sdl.surface->format, red, green, blue); @@ -3064,6 +3102,10 @@ static void GUI_ShutDown(Section * /*sec*/) { default: break; } + + #if C_GAMELINK + OUTPUT_GAMELINK_Shutdown(); + #endif } static void SetPriority(PRIORITY_LEVELS level) { @@ -3424,7 +3466,7 @@ static void GUI_StartUp() { sdl.desktop.full.height=height; } sdl.mouse.autoenable=section->Get_bool("autolock"); - if (!sdl.mouse.autoenable) SDL_ShowCursor(SDL_DISABLE); + if (!sdl.mouse.autoenable && sdl.desktop.want_type != SCREEN_GAMELINK) SDL_ShowCursor(SDL_DISABLE); sdl.mouse.autolock=false; const std::string feedback = section->Get_string("autolock_feedback"); @@ -3504,8 +3546,10 @@ static void GUI_StartUp() { item->set_text("Quit from DOSBox-X"); #endif - MAPPER_AddHandler(CaptureMouse,MK_f10,MMOD1,"capmouse","Capture mouse", &item); /* KEEP: Most DOSBox-X users may have muscle memory for this */ - item->set_text("Capture mouse"); + if (sdl.desktop.want_type != SCREEN_GAMELINK) { + MAPPER_AddHandler(CaptureMouse,MK_f10,MMOD1,"capmouse","Capture mouse", &item); /* KEEP: Most DOSBox-X users may have muscle memory for this */ + item->set_text("Capture mouse"); + } #if defined(C_SDL2) || defined(WIN32) || defined(MACOSX) MAPPER_AddHandler(QuickEdit,MK_nothing, 0,"fastedit", "Quick edit mode", &item); @@ -3538,19 +3582,21 @@ static void GUI_StartUp() { pause_menu_item_tag = mainMenu.get_item("mapper_pause").get_master_id() + DOSBoxMenu::nsMenuMinimumID; #endif - MAPPER_AddHandler(&GUI_Run, MK_c,MMODHOST, "gui", "Configuration tool", &item); - item->set_text("Configuration tool"); + if (sdl.desktop.want_type != SCREEN_GAMELINK) { + MAPPER_AddHandler(&GUI_Run, MK_c,MMODHOST, "gui", "Configuration tool", &item); + item->set_text("Configuration tool"); - MAPPER_AddHandler(&MAPPER_Run,MK_m,MMODHOST,"mapper","Mapper editor",&item); - item->set_accelerator(DOSBoxMenu::accelerator('m')); - item->set_description("Bring up the mapper UI"); - item->set_text("Mapper editor"); + MAPPER_AddHandler(&MAPPER_Run,MK_m,MMODHOST,"mapper","Mapper editor",&item); + item->set_accelerator(DOSBoxMenu::accelerator('m')); + item->set_description("Bring up the mapper UI"); + item->set_text("Mapper editor"); - MAPPER_AddHandler(SwitchFullScreen,MK_f,MMODHOST,"fullscr","Toggle fullscreen", &item); - item->set_text("Toggle fullscreen"); + MAPPER_AddHandler(SwitchFullScreen,MK_f,MMODHOST,"fullscr","Toggle fullscreen", &item); + item->set_text("Toggle fullscreen"); - MAPPER_AddHandler(&GUI_ResetResize, MK_backspace, MMODHOST, "resetsize", "Reset window size", &item); - item->set_text("Reset window size"); + MAPPER_AddHandler(&GUI_ResetResize, MK_backspace, MMODHOST, "resetsize", "Reset window size", &item); + item->set_text("Reset window size"); + } #if defined(USE_TTF) void DBCSSBCS_mapper_shortcut(bool pressed); @@ -3606,13 +3652,20 @@ static void GUI_StartUp() { usesystemcursor = section->Get_bool("usesystemcursor"); +#if C_GAMELINK + sdl.gamelink.enable = section->Get_bool("gamelink master"); + sdl.gamelink.snoop = section->Get_bool("gamelink snoop"); + sdl.gamelink.loadaddr = section->Get_int("gamelink load address"); +#endif + + #if C_XBRZ // initialize xBRZ parameters and check output type for compatibility xBRZ_Initialize(); if (sdl_xbrz.enable) { // xBRZ requirements - if ((output != "surface") && (output != "direct3d") && (output != "opengl") && (output != "openglhq") && (output != "openglnb") && (output != "openglpp") && (output != "ttf")) + if ((output != "surface") && (output != "direct3d") && (output != "opengl") && (output != "openglhq") && (output != "openglnb") && (output != "openglpp") && (output != "ttf") && (output != "gamelink")) output = "surface"; } #endif @@ -3623,6 +3676,9 @@ static void GUI_StartUp() { #if !C_DIRECT3D || output == "direct3d" #endif +#if !C_GAMELINK + || output == "gamelink" +#endif #if !defined(USE_TTF) || output == "ttf" #endif @@ -3630,7 +3686,7 @@ static void GUI_StartUp() { if (output == "overlay" || output == "ddraw") LOG_MSG("The %s output has been removed.", output.c_str()); else - LOG_MSG("The %s output is not enabled.", output == "ttf" ? "TrueType font (TTF)":"Direct3D"); + LOG_MSG("The %s output is not enabled.", output == "ttf" ? "TrueType font (TTF)":output.c_str()); #if C_DIRECT3D output = "direct3d"; #elif C_OPENGL @@ -3660,6 +3716,12 @@ static void GUI_StartUp() { { OUTPUT_OPENGL_Select(GLPerfect); #endif +#if C_GAMELINK + } + else if (output == "gamelink") + { + OUTPUT_GAMELINK_Select(); +#endif #if C_DIRECT3D } else if (output == "direct3d") @@ -3812,6 +3874,14 @@ static void GUI_StartUp() { } void Mouse_AutoLock(bool enable) { +#if C_GAMELINK + if (sdl.desktop.type == SCREEN_GAMELINK) { + // store the 'want mouse' state + sdl.gamelink.want_mouse = enable; + return; + } +#endif // C_GAMELINK + if (sdl.mouse.autolock == enable) return; @@ -5339,6 +5409,11 @@ void GFX_Events() { } } #endif + +#if C_GAMELINK + if (sdl.desktop.type == SCREEN_GAMELINK) OUTPUT_GAMELINK_InputEvent(); +#endif + #if (defined(WIN32) && !defined(HX_DOS) || defined(LINUX) && C_X11 || defined(MACOSX)) && (defined(C_SDL2) || defined(SDL_DOSBOX_X_SPECIAL)) if(IS_PC98_ARCH) { static uint32_t poll98_delay = 0; @@ -5448,6 +5523,7 @@ void GFX_Events() { GFX_HandleVideoResize(event.window.data1, event.window.data2); continue; case SDL_WINDOWEVENT_EXPOSED: + if (sdl.desktop.type == SCREEN_GAMELINK) break; if (sdl.draw.callback) sdl.draw.callback( GFX_CallBackRedraw ); continue; case SDL_WINDOWEVENT_LEAVE: @@ -5464,6 +5540,7 @@ void GFX_Events() { #endif break; case SDL_WINDOWEVENT_FOCUS_GAINED: + if (sdl.desktop.type == SCREEN_GAMELINK) break; if (IsFullscreen() && !sdl.mouse.locked) GFX_CaptureMouse(); SetPriority(sdl.priority.focus); @@ -5473,6 +5550,7 @@ void GFX_Events() { #endif break; case SDL_WINDOWEVENT_FOCUS_LOST: + if (sdl.desktop.type == SCREEN_GAMELINK) break; if (sdl.mouse.locked) { CaptureMouseNotify(); GFX_CaptureMouse(); @@ -5555,6 +5633,7 @@ void GFX_Events() { } break; case SDL_MOUSEMOTION: + if (sdl.desktop.type == SCREEN_GAMELINK) break; #if defined(C_SDL2) if (touchscreen_finger_lock == no_finger_id && touchscreen_touch_lock == no_touch_id && @@ -5567,6 +5646,7 @@ void GFX_Events() { break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: + if (sdl.desktop.type == SCREEN_GAMELINK) break; #if defined(C_SDL2) if (touchscreen_finger_lock == no_finger_id && touchscreen_touch_lock == no_touch_id && @@ -5581,6 +5661,7 @@ void GFX_Events() { case SDL_FINGERDOWN: case SDL_FINGERUP: case SDL_FINGERMOTION: + if (sdl.desktop.type == SCREEN_GAMELINK) break; HandleTouchscreenFinger(&event.tfinger); break; #endif @@ -5588,6 +5669,7 @@ void GFX_Events() { if (CheckQuit()) throw(0); break; case SDL_MOUSEWHEEL: + if (sdl.desktop.type == SCREEN_GAMELINK) break; if (wheel_key && (wheel_guest || !dos_kernel_disabled)) { if(event.wheel.y > 0) { #if defined (WIN32) && !defined(HX_DOS) @@ -5649,9 +5731,11 @@ void GFX_Events() { break; #if defined(WIN32) && !defined(HX_DOS) || defined(LINUX) && C_X11 || defined(MACOSX) && defined(SDL_DOSBOX_X_IME) case SDL_TEXTEDITING: + if (sdl.desktop.type == SCREEN_GAMELINK) break; ime_text = event.edit.text; break; case SDL_TEXTINPUT: + if (sdl.desktop.type == SCREEN_GAMELINK) break; { int len = strlen(event.text.text); if(ime_text == event.text.text || len > 1) { @@ -5687,6 +5771,7 @@ void GFX_Events() { #endif case SDL_KEYDOWN: case SDL_KEYUP: + if (sdl.desktop.type == SCREEN_GAMELINK) break; #if defined (WIN32) || defined(MACOSX) || defined(C_SDL2) if (event.key.keysym.sym==SDLK_LALT) sdl.laltstate = event.key.type; if (event.key.keysym.sym==SDLK_RALT) sdl.raltstate = event.key.type; @@ -5736,6 +5821,7 @@ void GFX_Events() { } #endif default: + if (sdl.desktop.type == SCREEN_GAMELINK) break; gfx_in_mapper = true; if (ticksLocked && event.type == SDL_KEYDOWN && static_cast(control->GetSection("cpu"))->Get_bool("stop turbo on key")) DOSBOX_UnlockSpeed2(true); MAPPER_CheckEvent(&event); @@ -6212,6 +6298,9 @@ void SDL_SetupConfigSection() { "default", "surface", "overlay", "ttf", #if C_OPENGL "opengl", "openglnb", "openglhq", "openglpp", +#endif +#if C_GAMELINK + "gamelink", #endif "ddraw", "direct3d", 0 }; @@ -6365,6 +6454,15 @@ void SDL_SetupConfigSection() { "If set to \"auto\" (default), it is enabled when using non-US keyboards in SDL1 builds."); Pstring->SetBasic(true); +#if C_GAMELINK + Pbool = sdl_sec->Add_bool("gamelink master", Property::Changeable::OnlyAtStart, false); + Pbool->Set_help("Allow Game Link connections e.g. from Grid Cartographer; required for output=gamelink, otherwise optional."); + Pbool = sdl_sec->Add_bool("gamelink snoop", Property::Changeable::OnlyAtStart, false); + Pbool->Set_help("Connect to an existing Game Link session and output link data instead of sending own data. Compares memory contents to find a suitable memory offset of peeks."); + Pint = sdl_sec->Add_int("gamelink load address", Property::Changeable::Always, GAMELINK_LOAD_ADDRESS_DEFAULT); + Pbool->Set_help("Configure the original load address of the software (when running in plain DOSBox) so that gamelink accesses are adjusted for different load addresses."); +#endif + Pint = sdl_sec->Add_int("overscan",Property::Changeable::Always, 0); Pint->SetMinMax(0,10); Pint->Set_help("Width of the overscan border (0 to 10) for the \"surface\" output."); @@ -8731,7 +8829,7 @@ int main(int argc, char* argv[]) SDL_MAIN_NOEXCEPT { /* Some extra SDL Functions */ Section_prop* sdl_sec = static_cast(control->GetSection("sdl")); - if (control->opt_fullscreen || sdl_sec->Get_bool("fullscreen")) { + if ((control->opt_fullscreen || sdl_sec->Get_bool("fullscreen")) && sdl.desktop.want_type != SCREEN_GAMELINK) { LOG(LOG_MISC, LOG_DEBUG)("Going fullscreen immediately, during startup"); #if defined(WIN32) diff --git a/src/hardware/memory.cpp b/src/hardware/memory.cpp index 09b1a0e86..07e733f4d 100644 --- a/src/hardware/memory.cpp +++ b/src/hardware/memory.cpp @@ -41,6 +41,10 @@ #include +#if C_GAMELINK +#include "../gamelink/gamelink.h" +#endif // C_GAMELINK + static MEM_Callout_t lfb_mem_cb = MEM_Callout_t_none; static MEM_Callout_t lfb_mmio_cb = MEM_Callout_t_none; @@ -1754,7 +1758,11 @@ void Init_AddressLimitAndGateMask() { void ShutDownRAM(Section * sec) { (void)sec;//UNUSED if (MemBase != NULL) { +#if C_GAMELINK + GameLink::FreeRAM(MemBase); +#else delete [] MemBase; +#endif MemBase = NULL; } } @@ -1851,7 +1859,11 @@ void Init_RAM() { /* Allocate the RAM. We alloc as a large unsigned char array. new[] does not initialize the array, * so we then must zero the buffer. */ +#if C_GAMELINK + MemBase = GameLink::AllocRAM(memory.pages*4096); +#else // C_GAMELINK MemBase = new(std::nothrow) uint8_t[memory.pages*4096]; +#endif // C_GAMELINK if (!MemBase) E_Exit("Can't allocate main memory of %d KB",(int)memsizekb); /* Clear the memory, as new doesn't always give zeroed memory * (Visual C debug mode). We want zeroed memory though. */ diff --git a/src/hardware/mixer.cpp b/src/hardware/mixer.cpp index 11e2ab8c2..71559bee6 100644 --- a/src/hardware/mixer.cpp +++ b/src/hardware/mixer.cpp @@ -225,6 +225,11 @@ inline void MixerChannel::updateSlew(void) { max_change = 0x7FFFFFFFUL; } +void MIXER_SetMaster(float vol0, float vol1) { + mixer.mastervol[0] = vol0; + mixer.mastervol[1] = vol1; +} + MixerChannel * MIXER_AddChannel(MIXER_Handler handler,Bitu freq,const char * name) { MixerChannel * chan=new MixerChannel(); chan->freq_fslew = 0; diff --git a/src/misc/programs.cpp b/src/misc/programs.cpp index 76aa4a81e..abb0c1bec 100644 --- a/src/misc/programs.cpp +++ b/src/misc/programs.cpp @@ -722,6 +722,11 @@ void ApplySetting(std::string pvar, std::string inputline, bool quiet) { sdl.mouse.xsensitivity = p3->GetSection()->Get_int("xsens"); sdl.mouse.ysensitivity = p3->GetSection()->Get_int("ysens"); } +#if C_GAMELINK + if (!strcasecmp(inputline.substr(0, 22).c_str(), "gamelink load address=")) { + sdl.gamelink.loadaddr = section->Get_int("gamelink load address"); + } +#endif if (!strcasecmp(inputline.substr(0, 11).c_str(), "fullscreen=")) { if (section->Get_bool("fullscreen")) { if (!GFX_IsFullscreen()) {GFX_LosingFocus();GFX_SwitchFullScreen();} diff --git a/src/output/Makefile.am b/src/output/Makefile.am index 0094427b0..45da482f6 100644 --- a/src/output/Makefile.am +++ b/src/output/Makefile.am @@ -8,3 +8,7 @@ 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 + +if C_GAMELINK +liboutput_a_SOURCES += output_gamelink.cpp +endif diff --git a/src/output/output_gamelink.cpp b/src/output/output_gamelink.cpp new file mode 100644 index 000000000..57848ef9b --- /dev/null +++ b/src/output/output_gamelink.cpp @@ -0,0 +1,308 @@ +#include "config.h" + +#if C_GAMELINK + +#include +#include +#include + +#include "dosbox.h" +#include "logging.h" +#include "menudef.h" +#include "sdlmain.h" +#include "render.h" +#include "vga.h" +#include "mem.h" +#include "mixer.h" +#include "mapper.h" +#include "../gamelink/scancodes_windows.h" +#include "../output/output_surface.h" + +using namespace std; + +extern uint32_t RunningProgramHash[4]; +extern const char* RunningProgram; + +#if !defined(C_SDL2) +#error "gamelink output requires SDL2" +#endif + +void OUTPUT_GAMELINK_Select() +{ + //LOG_MSG("OUTPUT_GAMELINK: Select"); + if (!sdl.gamelink.enable) { +#ifdef WIN32 + MessageBoxA( NULL, "ERROR: Game Link output disabled.", + "DOSBox \"Game Link\" Error", MB_OK | MB_ICONSTOP ); +#else // WIN32 + LOG_MSG( "OUTPUT_GAMELINK: Not enabled via `gamelink master = true`, falling back to `output=surface`." ); +#endif // WIN32 + OUTPUT_SURFACE_Select(); + return; + } + + 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 ) ); + } +} + +bool OUTPUT_GAMELINK_InitTrackingMode() +{ + //LOG_MSG("OUTPUT_GAMELINK: Game Link %s", sdl.gamelink.enable?"enabled":"disabled"); + if (!sdl.gamelink.enable) return false; + + bool trackonly_mode = sdl.desktop.want_type != SCREEN_GAMELINK; + + 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 Game Link inter-process communication.", + "DOSBox \"Game Link\" Error", MB_OK | MB_ICONSTOP ); +#else // WIN32 + LOG_MSG("GAMELINK: Couldn't initialise inter-process communication."); +#endif // WIN32 + sdl.gamelink.enable = false; + return false; + } + return true; +} + + +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: Game Link 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 + sdl.gamelink.enable = false; + return 0; + } + + sdl.deferred_resize = false; + sdl.must_redraw_all = true; + + sdl.window = GFX_SetSDLWindowMode(640, 200, 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); + + // FIXME: find proper way to refresh the menu bar without causing infinite recursion + static bool in_setsize = false; + if (!in_setsize) { + in_setsize = true; + DOSBox_RefreshMenu(); + in_setsize = false; + } + + return retFlags; +} + +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.clip.w+2*sdl.clip.x, (uint16_t)sdl.clip.h+2*sdl.clip.y, 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++; + SDL_UpdateWindowSurface(sdl.window); +} + +void OUTPUT_GAMELINK_Shutdown() +{ + //LOG_MSG("OUTPUT_GAMELINK: Shutdown"); + GameLink::Term(); +} + +#endif diff --git a/src/output/output_gamelink.h b/src/output/output_gamelink.h new file mode 100644 index 000000000..a094f457d --- /dev/null +++ b/src/output/output_gamelink.h @@ -0,0 +1,28 @@ +#ifndef DOSBOX_OUTPUT_GAMELINK_H +#define DOSBOX_OUTPUT_GAMELINK_H + +#include "config.h" + +#if C_GAMELINK + +#include "dosbox.h" + +#include "../gamelink/gamelink.h" + +// 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(); +bool OUTPUT_GAMELINK_InitTrackingMode(); + +#endif + +#endif /*DOSBOX_OUTPUT_GAMELINK_H*/ \ No newline at end of file diff --git a/src/output/output_tools.cpp b/src/output/output_tools.cpp index ea3621836..e7621487f 100644 --- a/src/output/output_tools.cpp +++ b/src/output/output_tools.cpp @@ -157,12 +157,23 @@ void change_output(int output) { break; #endif +#if C_GAMELINK + case 12: + OUTPUT_GAMELINK_Select(); + break; +#endif + default: LOG_MSG("SDL: Unsupported output device %d, switching back to surface",output); OUTPUT_SURFACE_Select(); break; } +#if C_GAMELINK + if (!OUTPUT_GAMELINK_InitTrackingMode() && sdl.desktop.want_type == SCREEN_GAMELINK) OUTPUT_SURFACE_Select(); +#endif + + #if C_OPENGL if (sdl.desktop.want_type != SCREEN_OPENGL) mainMenu.get_item("load_glsl_shader").enable(false).refresh_item(mainMenu); #endif @@ -272,6 +283,9 @@ void OutputSettingMenuUpdate(void) { #if defined(USE_TTF) mainMenu.get_item("output_ttf").check(sdl.desktop.want_type == SCREEN_TTF).refresh_item(mainMenu); #endif +#if C_GAMELINK + mainMenu.get_item("output_gamelink").check(sdl.desktop.want_type == SCREEN_GAMELINK).refresh_item(mainMenu); +#endif } void SwitchFS(Bitu val) { @@ -388,6 +402,13 @@ bool toOutput(const char *what) { if (!control->opt_nomenu && static_cast(control->GetSection("sdl"))->Get_bool("showmenu")) DOSBox_SetMenu(); #endif } +#endif + } + else if (!strcmp(what,"gamelink")) { +#if C_GAMELINK + if (sdl.desktop.want_type == SCREEN_GAMELINK) return false; + change_output(12); + reset = true; #endif } if (reset) RENDER_Reset(); diff --git a/vs/config.h b/vs/config.h index ba16ac69c..bc9b895ab 100644 --- a/vs/config.h +++ b/vs/config.h @@ -148,6 +148,11 @@ /* Define to 1 to use opengl display output support */ #define C_OPENGL 1 +#ifdef C_SDL2 +/* Define to 1 to enable gamelink support (needs SDL2) */ +#define C_GAMELINK 1 +#endif + /* Set to 1 to enable XBRZ support */ #define C_XBRZ 1 diff --git a/vs/dosbox-x.vcxproj b/vs/dosbox-x.vcxproj index 68ceb705b..fcf9cf6ec 100644 --- a/vs/dosbox-x.vcxproj +++ b/vs/dosbox-x.vcxproj @@ -1167,6 +1167,8 @@ for /d %%i in ($(SolutionDir)\..\contrib\translations\*) do copy %%i\*.lng "$(Ou + + @@ -1532,6 +1534,7 @@ for /d %%i in ($(SolutionDir)\..\contrib\translations\*) do copy %%i\*.lng "$(Ou + @@ -1768,6 +1771,8 @@ for /d %%i in ($(SolutionDir)\..\contrib\translations\*) do copy %%i\*.lng "$(Ou + + @@ -1889,6 +1894,7 @@ for /d %%i in ($(SolutionDir)\..\contrib\translations\*) do copy %%i\*.lng "$(Ou +