From 1fccc3ba6cb9fc4c79b12f0f862f53a94c31861c Mon Sep 17 00:00:00 2001 From: Logan McNaughton Date: Tue, 21 Apr 2020 09:09:50 -0600 Subject: [PATCH] Netplay --- .travis.yml | 5 + .../Mupen64Plus-v2.0-Core-Front-End.mediawiki | 20 + .../Mupen64Plus-v2.0-Netplay-API.mediawiki | 113 +++ projects/msvc/mupen64plus-core.vcxproj | 76 +- .../msvc/mupen64plus-core.vcxproj.filters | 6 + projects/unix/Makefile | 14 + src/api/api_export.ver | 2 + src/api/config.c | 10 + src/api/frontend.c | 25 + src/api/m64p_config.h | 22 + src/api/m64p_types.h | 6 +- src/backends/file_storage.c | 13 +- .../plugins_compat/input_plugin_compat.c | 40 +- src/backends/plugins_compat/plugins_compat.h | 3 + src/device/pif/pif.c | 3 + src/main/main.c | 58 +- src/main/netplay.c | 669 ++++++++++++++++++ src/main/netplay.h | 137 ++++ src/main/version.h | 1 + 19 files changed, 1173 insertions(+), 50 deletions(-) create mode 100644 doc/emuwiki-api-doc/Mupen64Plus-v2.0-Netplay-API.mediawiki create mode 100644 src/main/netplay.c create mode 100644 src/main/netplay.h diff --git a/.travis.yml b/.travis.yml index 1926648b..92786c87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,9 @@ addons: apt: packages: - libsdl1.2-dev + - libsdl-net1.2-dev - libsdl2-dev + - libsdl2-net-dev - libfreetype6-dev - libgl1-mesa-dev - libglu1-mesa-dev @@ -30,6 +32,7 @@ env: - DBG_COUNT=1 - DBG_COMPARE=1 - COUNT_INSTR=1 + - NETPLAY=1 script: - make -C projects/unix V=1 clean && LDFLAGS="-Wl,--no-add-needed -Wl,--no-undefined" OPTFLAGS="-O2" make SDL_CONFIG=sdl-config CC="${CC}" CXX="${CXX}" -j$(nproc) -C projects/unix V=1 all - make -C projects/unix V=1 clean && LDFLAGS="-Wl,--no-add-needed -Wl,--no-undefined" OPTFLAGS="-O2" make SDL_CONFIG=sdl2-config CC="${CC}" CXX="${CXX}" -j$(nproc) -C projects/unix V=1 all @@ -46,6 +49,7 @@ matrix: - sudo apt-get update -qq - sudo apt-get -y --allow-unauthenticated install mxe-i686-w64-mingw32.shared-gcc - sudo apt-get -y --allow-unauthenticated install mxe-i686-w64-mingw32.shared-sdl2 + - sudo apt-get -y --allow-unauthenticated install mxe-i686-w64-mingw32.shared-sdl2-net - sudo apt-get -y --allow-unauthenticated install mxe-i686-w64-mingw32.shared-freeglut - sudo apt-get -y --allow-unauthenticated install mxe-i686-w64-mingw32.shared-freetype - sudo apt-get -y --allow-unauthenticated install mxe-i686-w64-mingw32.shared-libpng @@ -66,6 +70,7 @@ matrix: - sudo apt-get update -qq - sudo apt-get -y --allow-unauthenticated install mxe-x86-64-w64-mingw32.shared-gcc - sudo apt-get -y --allow-unauthenticated install mxe-x86-64-w64-mingw32.shared-sdl2 + - sudo apt-get -y --allow-unauthenticated install mxe-x86-64-w64-mingw32.shared-sdl2-net - sudo apt-get -y --allow-unauthenticated install mxe-x86-64-w64-mingw32.shared-freeglut - sudo apt-get -y --allow-unauthenticated install mxe-x86-64-w64-mingw32.shared-freetype - sudo apt-get -y --allow-unauthenticated install mxe-x86-64-w64-mingw32.shared-libpng diff --git a/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Core-Front-End.mediawiki b/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Core-Front-End.mediawiki index b501281b..4138307c 100644 --- a/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Core-Front-End.mediawiki +++ b/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Core-Front-End.mediawiki @@ -190,6 +190,26 @@ Most libmupen64plus functions return an m64p_error return code, which i |This command allow frontends to register their media (such as GameBoy cartridge or 64DD disk) loading functions. These functions will be called appropriately by the core at startup and when a new media is inserted. |'''ParamInt''' must be sizeof(m64p_media_loader).'''
ParamPtr'''A pointer to the m64p_media_loader to register, cannot be NULL. |None +|- +|M64CMD_NETPLAY_INIT +|This command will initilize the netplay subsystem. It will also attempt to make an initial connection to the netplay server. Returns M64ERR_SYSTEM_FAIL on failure, M64ERR_SUCCESS on success. +|'''ParamInt''' This is the port number the netplay server is listening on.'''
ParamPtr''' This is a string containing the IP or hostname of the netplay server. +|Emulator should be stopped. +|- +|M64CMD_NETPLAY_CONTROL_PLAYER +|This function is used for the client to request control of certain players. This function can be called more than once if a client would like to control multiple players. Local controllers are assigned in order. Returns M64ERR_INPUT_ASSERT if the server rejects the request. If this function is never called, the client is assumed to be a spectator. +|'''ParamInt''' This is the player number (from 1-4) the client would like to control.'''
ParamPtr''' This is a pointer to a uint32_t that contains a unique registration ID (for instance a random number) for the client. This number should be the same each time if this function is called multiple times. The registration ID cannot be 0. +|Emulator should be stopped. The input plugin should be started. +|- +|M64CMD_NETPLAY_GET_VERSION +|This function is used to check API and core versions, the server can use this information to make sure everyone is using the same version of the software. The function returns M64ERR_INCOMPATIBLE if the API versions don't match, and M64ERR_SUCCESS if they do. +|'''ParamInt''' This is the Netplay API version that the frontend supports.'''
ParamPtr''' This is a pointer to a uint32_t, the core fills this pointer with a core netplay version. This version number is incremented when changes are made to the core that would affect netplay sync. +|None +|- +|M64CMD_NETPLAY_CLOSE +|Closes the netplay subsystem, as well as any connections to the netplay server. +|N/A +|None |}
diff --git a/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Netplay-API.mediawiki b/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Netplay-API.mediawiki new file mode 100644 index 00000000..99f4d87d --- /dev/null +++ b/doc/emuwiki-api-doc/Mupen64Plus-v2.0-Netplay-API.mediawiki @@ -0,0 +1,113 @@ += Mupen64Plus Netplay API = + +The Mupen64Plus netplay API is based on a client/server architecture. It supports syncing core settings and save files. It also supports raw input (tested with Raphnet input plugin). +TCP is used to deal with player registration, and syncing settings and saves. +UDP is used to transfer key input data. +Data is sent in Network Byte Order (Big Endian). + +The server is responsible for maintaining healthy buffers and also for detecting desyncs. The server is the source of truth for input data (for instance, when player 1 pushes "A", it sends that information to the server via UDP. That "A" is not registered as input for player 1 until the client receives a packet from the server indicating what input frame to register that "A" in) + +== UDP Packet formats == +* Request input for a player (sent by client): +** 12 bytes +** byte[0] = 2 +** byte[1] = player number (this is the player we need key inputs for) +** byte[2-5] = client registration ID +** byte[6-9] = current event count +** byte[10] = whether client is a spectator +** byte[11] = local buffer size of the client + +* Key input data (sent by server): +** Variable length, payload cannot be larger than 512 bytes +** byte[0] = 1 +** byte[1] = player number the key inputs pertain to +** byte[2] = current status. Bit 0 indicates whether the game has desynced (1 means desync). Bits 1-4 indicate whether a player has disconnected (for instance, if bit 2 is set, that means player 2 has disconnected). Bits 5-7 are currently unused. +** byte[3] = how far we lag behind the lead player +** byte[4] = number of events in this packet +** The following items will repeat/loop for the number of events in the packet +*** byte[5-8] = event count +*** byte[9-12] = key input data +*** byte[13] = current plugin + +* Key input data (sent by client): +** 11 bytes +** byte[0] = 0 +** byte[1] = player number +** byte[2-5] = current event count +** byte[6-9] = key input data +** byte[10] = current plugin + +* Client sync data (sent by client): +** 133 bytes +** byte[0] = 4 +** byte[1-4] = current VI count +** byte[5-132] = CP0 registers + +== TCP Packet formats == +* Player disconnection notice (sent by client): +** 5 bytes +** byte[0] = 7 +** byte[1-4] = client registration ID + +* Player registration request (sent by client): +** 8 bytes +** byte[0] = 5 +** byte[1] = player the client is trying to register +** byte[2] = plugin for this player +** byte[3] = whether to use a raw input plugin +** byte[4-7] = client registration ID + +* Player registration response (sent by server): +** 2 bytes +** byte[0] = response (1 if registration was successful, 0 otherwise) +** byte[1] = local buffer target for the client + +* Send save file data (sent by client): +** Variable length +** byte[0] = 1 +** byte[1 - next '\0'] = file name +** byte[4 bytes] = size of save file in bytes +** byte[size of file] = save file data + +* Request save file data (sent by client): +** Variable length +** byte[0] = 2 +** byte[1 - next '\0'] = file name + +* Receive save file data (sent by server): +** Server responds with this data right after the above request +** Variable length +** byte[file size] = save file data + +* Send settings (sent by client): +** 21 bytes +** byte[0] = 3 +** byte[1-4] = count_per_op +** byte[5-8] = disable_extra_mem +** byte[9-12] = si_dma_duration +** byte[13-16] = emumode +** byte[17-20] = no_compiled_jump + +* Request settings (sent by client): +** 1 byte +** byte[0] = 4 + +* Send settings (sent by server): +** Server responds with this data right after the above request +** 20 bytes +** byte[0-3] = count_per_op +** byte[4-7] = disable_extra_mem +** byte[8-11] = si_dma_duration +** byte[12-15] = emumode +** byte[16-19] = no_compiled_jump + +* Request player registration data (sent by client): +** 1 byte +** byte[0] = 6 + +* Send player registration data (sent by server): +** 24 bytes +** The following bytes loop 4 times (once for each player) +*** byte[0-3] = player registration ID +*** byte[4] = player plugin +*** byte[5] = whether the player is using raw data input diff --git a/projects/msvc/mupen64plus-core.vcxproj b/projects/msvc/mupen64plus-core.vcxproj index ec304915..ebb7e7ac 100644 --- a/projects/msvc/mupen64plus-core.vcxproj +++ b/projects/msvc/mupen64plus-core.vcxproj @@ -149,6 +149,7 @@ + @@ -451,6 +452,7 @@ + @@ -930,14 +932,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_NETPLAY;M64P_PARALLEL;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x86\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) true Windows @@ -948,14 +950,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;__x86_64__;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;M64P_NETPLAY;__x86_64__;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x64\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) true Windows @@ -966,14 +968,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;NEW_DYNAREC=1;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_NETPLAY;M64P_PARALLEL;NEW_DYNAREC=1;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x86\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) true Windows @@ -984,14 +986,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;NEW_DYNAREC=1;RECOMPILER_DEBUG=1;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_NETPLAY;M64P_PARALLEL;NEW_DYNAREC=1;RECOMPILER_DEBUG=1;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x86\capstone_dll.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x86\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x86\capstone_dll.lib;%(AdditionalDependencies) true Windows @@ -1002,14 +1004,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;NEW_DYNAREC=1;RECOMPILER_DEBUG=3;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_NETPLAY;M64P_PARALLEL;NEW_DYNAREC=1;RECOMPILER_DEBUG=3;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x86\capstone_dll.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x86\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x86\capstone_dll.lib;%(AdditionalDependencies) true Windows @@ -1020,14 +1022,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;NEW_DYNAREC=2;__x86_64__;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;M64P_NETPLAY;NEW_DYNAREC=2;__x86_64__;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x64\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) true Windows @@ -1038,14 +1040,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;NEW_DYNAREC=2;__x86_64__;RECOMPILER_DEBUG=4;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;M64P_NETPLAY;NEW_DYNAREC=2;__x86_64__;RECOMPILER_DEBUG=4;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x64\capstone_dll.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x64\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x64\capstone_dll.lib;%(AdditionalDependencies) true Windows @@ -1056,14 +1058,14 @@ Disabled - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;NEW_DYNAREC=2;__x86_64__;RECOMPILER_DEBUG=2;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;..\..\..\mupen64plus-win32-deps\capstone\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;M64P_NETPLAY;NEW_DYNAREC=2;__x86_64__;RECOMPILER_DEBUG=2;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level3 - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x64\capstone_dll.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x64\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;..\..\..\mupen64plus-win32-deps\capstone\lib\x64\capstone_dll.lib;%(AdditionalDependencies) true Windows @@ -1073,8 +1075,8 @@ - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_NETPLAY;M64P_PARALLEL;%(PreprocessorDefinitions) MultiThreadedDLL Level3 MaxSpeed @@ -1082,7 +1084,7 @@ true - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x86\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) true Windows true @@ -1094,8 +1096,8 @@ - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;__x86_64__;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;M64P_NETPLAY;__x86_64__;%(PreprocessorDefinitions) MultiThreadedDLL Level3 MaxSpeed @@ -1103,7 +1105,7 @@ true - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x64\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) true Windows true @@ -1115,8 +1117,8 @@ - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;NEW_DYNAREC=1;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_NETPLAY;M64P_PARALLEL;NEW_DYNAREC=1;%(PreprocessorDefinitions) MultiThreadedDLL Level3 MaxSpeed @@ -1124,7 +1126,7 @@ true - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x86\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x86\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x86\freetype.lib;%(AdditionalDependencies) true Windows true @@ -1136,8 +1138,8 @@ - ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;NEW_DYNAREC=2;__x86_64__;%(PreprocessorDefinitions) + ..\..\src;..\..\subprojects\md5;..\..\subprojects\minizip;..\..\subprojects\oglft;..\..\subprojects\xxhash;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\include;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\include;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;M64P_NETPLAY;NEW_DYNAREC=2;__x86_64__;%(PreprocessorDefinitions) MultiThreadedDLL Level3 MaxSpeed @@ -1145,7 +1147,7 @@ true - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL2-2.0.10\lib\x64\SDL2.lib;..\..\..\mupen64plus-win32-deps\SDL2_net-2.0.1\lib\x64\SDL2_net.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.11\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.37\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.10.1\lib\x64\freetype.lib;%(AdditionalDependencies) true Windows true @@ -1158,4 +1160,4 @@ - \ No newline at end of file + diff --git a/projects/msvc/mupen64plus-core.vcxproj.filters b/projects/msvc/mupen64plus-core.vcxproj.filters index 04e2a4ac..f6982d9e 100644 --- a/projects/msvc/mupen64plus-core.vcxproj.filters +++ b/projects/msvc/mupen64plus-core.vcxproj.filters @@ -153,6 +153,9 @@ main + + main + main @@ -464,6 +467,9 @@ main + + main + main diff --git a/projects/unix/Makefile b/projects/unix/Makefile index e603c43e..2f074d5f 100755 --- a/projects/unix/Makefile +++ b/projects/unix/Makefile @@ -326,8 +326,15 @@ ifeq ($(origin SDL_CFLAGS) $(origin SDL_LDLIBS), undefined undefined) ifeq ($(shell which $(SDL_CONFIG) 2>/dev/null),) $(error No SDL development libraries found!) else + ifeq ($(NETPLAY), 1) + SDL_LDLIBS += -lSDL_net + endif $(warning Using SDL 1.2 libraries) endif + else + ifeq ($(NETPLAY), 1) + SDL_LDLIBS += -lSDL2_net + endif endif SDL_CFLAGS += $(shell $(SDL_CONFIG) --cflags) SDL_LDLIBS += $(shell $(SDL_CONFIG) --libs) @@ -581,6 +588,12 @@ SOURCE += $(SUBDIR)/oglft/OGLFT.cpp CXXFLAGS += -I$(SUBDIR)/oglft endif +# netplay +ifeq ($(NETPLAY), 1) +CFLAGS += -DM64P_NETPLAY +SOURCE += $(SRCDIR)/main/netplay.c +endif + # source files for optional features ifeq ($(DBG_COUNT), 1) CFLAGS += -DCOUNT_INSTR @@ -710,6 +723,7 @@ targets: @echo " WARNFLAGS=flag == compiler warning levels (default: -Wall)" @echo " PIC=(1|0) == Force enable/disable of position independent code" @echo " OSD=(1|0) == Enable/disable build of OpenGL On-screen display" + @echo " NETPLAY=1 == Enable netplay functionality, requires SDL2_net @echo " NEW_DYNAREC=1 == Replace dynamic recompiler with Ari64's experimental dynarec" @echo " OPENCV=1 == Enable OpenCV support" @echo " POSTFIX=name == String added to the name of the the build (default: '')" diff --git a/src/api/api_export.ver b/src/api/api_export.ver index 4cafe8f5..45eb6bb7 100644 --- a/src/api/api_export.ver +++ b/src/api/api_export.ver @@ -27,6 +27,8 @@ ConfigSetDefaultInt; ConfigSetDefaultString; ConfigSetParameter; ConfigSetParameterHelp; +ConfigSendNetplayConfig; +ConfigReceiveNetplayConfig; CoreAddCheat; CoreAttachPlugin; CoreCheatEnabled; diff --git a/src/api/config.c b/src/api/config.c index c169e5a1..e641b8b0 100644 --- a/src/api/config.c +++ b/src/api/config.c @@ -34,6 +34,7 @@ #include "m64p_config.h" #include "m64p_types.h" #include "main/util.h" +#include "main/netplay.h" #include "osal/files.h" #include "osal/preproc.h" @@ -1595,3 +1596,12 @@ EXPORT const char * CALL ConfigGetUserCachePath(void) return osal_get_user_cachepath(); } +EXPORT m64p_error CALL ConfigSendNetplayConfig(char* data, int size) +{ + return netplay_send_config(data, size); +} + +EXPORT m64p_error CALL ConfigReceiveNetplayConfig(char* data, int size) +{ + return netplay_receive_config(data, size); +} diff --git a/src/api/frontend.c b/src/api/frontend.c index 1379e2ce..702df807 100644 --- a/src/api/frontend.c +++ b/src/api/frontend.c @@ -45,6 +45,7 @@ #include "main/version.h" #include "main/workqueue.h" #include "main/screenshot.h" +#include "main/netplay.h" #include "plugin/plugin.h" #include "vidext.h" @@ -309,6 +310,30 @@ EXPORT m64p_error CALL CoreDoCommand(m64p_command Command, int ParamInt, void *P return M64ERR_INPUT_INVALID; g_media_loader = *(m64p_media_loader*)ParamPtr; return M64ERR_SUCCESS; + case M64CMD_NETPLAY_INIT: + if (ParamInt < 1 || ParamPtr == NULL) + return M64ERR_INPUT_INVALID; + return netplay_start(ParamPtr, ParamInt); + case M64CMD_NETPLAY_CONTROL_PLAYER: + if (ParamInt < 1 || ParamInt > 4 || ParamPtr == NULL) + return M64ERR_INPUT_INVALID; + if (netplay_register_player(ParamInt - 1, Controls[netplay_next_controller()].Plugin, Controls[netplay_next_controller()].RawData, *(uint32_t*)ParamPtr)) + { + netplay_set_controller(ParamInt - 1); + return M64ERR_SUCCESS; + } + else + return M64ERR_INPUT_ASSERT; // player already in use + case M64CMD_NETPLAY_GET_VERSION: + if (ParamPtr == NULL) + return M64ERR_INPUT_INVALID; + *(uint32_t*)ParamPtr = NETPLAY_CORE_VERSION; + if (ParamInt == NETPLAY_API_VERSION) + return M64ERR_SUCCESS; + else + return M64ERR_INCOMPATIBLE; + case M64CMD_NETPLAY_CLOSE: + return netplay_stop(); default: return M64ERR_INPUT_INVALID; } diff --git a/src/api/m64p_config.h b/src/api/m64p_config.h index ac073aa6..85199167 100644 --- a/src/api/m64p_config.h +++ b/src/api/m64p_config.h @@ -273,6 +273,28 @@ typedef m64p_error (*ptr_ConfigExternalGetParameter)(m64p_handle, const char *, EXPORT m64p_error CALL ConfigExternalGetParameter(m64p_handle, const char *, const char *, char *, int); #endif +/* ConfigSendNetplayConfig() + * + * This function allows plugins to take advantage of the netplay TCP connection + * to send configuration data to the netplay server. + */ + +typedef m64p_error (*ptr_ConfigSendNetplayConfig)(char*, int); +#if defined(M64P_CORE_PROTOTYPES) +EXPORT m64p_error CALL ConfigSendNetplayConfig(char*, int); +#endif + +/* ConfigReceiveNetplayConfig() + * + * This function allows plugins to take advantage of the netplay TCP connection + * to receive configuration data from the netplay server. + */ + +typedef m64p_error (*ptr_ConfigReceiveNetplayConfig)(char*, int); +#if defined(M64P_CORE_PROTOTYPES) +EXPORT m64p_error CALL ConfigReceiveNetplayConfig(char*, int); +#endif + #ifdef __cplusplus } #endif diff --git a/src/api/m64p_types.h b/src/api/m64p_types.h index 6bd551fe..f073e3f9 100644 --- a/src/api/m64p_types.h +++ b/src/api/m64p_types.h @@ -162,7 +162,11 @@ typedef enum { M64CMD_READ_SCREEN, M64CMD_RESET, M64CMD_ADVANCE_FRAME, - M64CMD_SET_MEDIA_LOADER + M64CMD_SET_MEDIA_LOADER, + M64CMD_NETPLAY_INIT, + M64CMD_NETPLAY_CONTROL_PLAYER, + M64CMD_NETPLAY_GET_VERSION, + M64CMD_NETPLAY_CLOSE } m64p_command; typedef struct { diff --git a/src/backends/file_storage.c b/src/backends/file_storage.c index b153311c..f4b6226d 100644 --- a/src/backends/file_storage.c +++ b/src/backends/file_storage.c @@ -28,6 +28,7 @@ #include "backends/api/storage_backend.h" #include "device/dd/dd_controller.h" #include "main/util.h" +#include "main/netplay.h" int open_file_storage(struct file_storage* fstorage, size_t size, const char* filename) { @@ -42,7 +43,14 @@ int open_file_storage(struct file_storage* fstorage, size_t size, const char* fi } /* try to load storage file content */ - return read_from_file(fstorage->filename, fstorage->data, fstorage->size); + if (!netplay_is_init()) + { + return read_from_file(fstorage->filename, fstorage->data, fstorage->size); + } + else + { + return netplay_read_storage(fstorage->filename, fstorage->data, fstorage->size); + } } int open_rom_file_storage(struct file_storage* fstorage, const char* filename) @@ -82,6 +90,9 @@ static size_t file_storage_size(const void* storage) static void file_storage_save(void* storage) { + if (netplay_is_init() && netplay_get_controller(0) == -1) + return; + struct file_storage* fstorage = (struct file_storage*)storage; switch(write_to_file(fstorage->filename, fstorage->data, fstorage->size)) diff --git a/src/backends/plugins_compat/input_plugin_compat.c b/src/backends/plugins_compat/input_plugin_compat.c index 7da0c7ab..2e869133 100644 --- a/src/backends/plugins_compat/input_plugin_compat.c +++ b/src/backends/plugins_compat/input_plugin_compat.c @@ -27,6 +27,7 @@ #include "plugin/plugin.h" #include "main/main.h" +#include "main/netplay.h" #include #include @@ -60,8 +61,28 @@ static m64p_error input_plugin_get_input(void* opaque, uint32_t* input_) int pak_change_requested = 0; /* first poll controller */ - if (input.getKeys) { - input.getKeys(cin_compat->control_id, &keys); + if (!netplay_is_init()) + { + if (input.getKeys) + input.getKeys(cin_compat->control_id, &keys); + } + else + { + int netplay_controller = netplay_get_controller(cin_compat->control_id); + if (netplay_controller >= 0) + { + //Here we "trick" the input plugin + //by passing it the controller number that is controlling the player during netplay + uint8_t plugin = Controls[netplay_controller].Plugin; + uint8_t present = Controls[netplay_controller].Present; + if (input.getKeys) + input.getKeys(netplay_controller, &keys); + + netplay_set_plugin(cin_compat->control_id, Controls[netplay_controller].Plugin); + Controls[netplay_controller].Plugin = plugin; + Controls[netplay_controller].Present = present; + cin_compat->last_input = keys.Value; //disable pak switching for netplay + } } /* return an error if controller is not plugged */ @@ -130,6 +151,11 @@ static void input_plugin_rumble_exec(void* opaque, enum rumble_action action) return; } + //This is for netplay, -1 means there is no local controller controlling this player + if (control_id == -1) { + return; + } + static const uint8_t rumble_cmd_header[] = { 0x23, 0x01, /* T=0x23, R=0x01 */ @@ -168,6 +194,11 @@ static void input_plugin_read_controller(void* opaque, return; } + //This is for netplay, -1 means there is no local controller controlling this player + if (control_id == -1) { + return; + } + /* UGLY: use negative offsets to get access to non-const tx pointer */ input.readController(control_id, rx - 1); } @@ -182,6 +213,11 @@ void input_plugin_controller_command(void* opaque, return; } + //This is for netplay, -1 means there is no local controller controlling this player + if (control_id == -1) { + return; + } + input.controllerCommand(control_id, tx); } diff --git a/src/backends/plugins_compat/plugins_compat.h b/src/backends/plugins_compat/plugins_compat.h index de5028c6..80ec59c2 100644 --- a/src/backends/plugins_compat/plugins_compat.h +++ b/src/backends/plugins_compat/plugins_compat.h @@ -50,6 +50,9 @@ struct controller_input_compat unsigned int gb_switch_delay; unsigned int gb_cart_switch_enabled; + + uint32_t netplay_count; + struct netplay_event* event_first; }; extern const struct controller_input_backend_interface diff --git a/src/device/pif/pif.c b/src/device/pif/pif.c index 21cda056..63c13de2 100644 --- a/src/device/pif/pif.c +++ b/src/device/pif/pif.c @@ -33,6 +33,7 @@ #include "device/memory/memory.h" #include "device/r4300/r4300_core.h" #include "plugin/plugin.h" +#include "main/netplay.h" #define __STDC_FORMAT_MACROS #include @@ -371,6 +372,8 @@ void update_pif_ram(struct pif* pif) input.readController(-1, NULL); } + netplay_update_input(pif); + #ifdef DEBUG_PIF DebugMessage(M64MSG_INFO, "PIF post read"); print_pif(pif); diff --git a/src/main/main.c b/src/main/main.c index 709ee877..c747d54a 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -64,7 +64,6 @@ #include "device/pif/bootrom_hle.h" #include "eventloop.h" #include "main.h" -#include "cheat.h" #include "osal/files.h" #include "osal/preproc.h" #include "osd/osd.h" @@ -76,6 +75,7 @@ #include "savestates.h" #include "screenshot.h" #include "util.h" +#include "netplay.h" #ifdef DBG #include "debugger/dbg_debugger.h" @@ -327,6 +327,9 @@ int main_set_core_defaults(void) void main_speeddown(int percent) { + if (netplay_is_init()) + return; + if (l_SpeedFactor - percent > 10) /* 10% minimum speed */ { l_SpeedFactor -= percent; @@ -338,6 +341,9 @@ void main_speeddown(int percent) void main_speedup(int percent) { + if (netplay_is_init()) + return; + if (l_SpeedFactor + percent < 300) /* 300% maximum speed */ { l_SpeedFactor += percent; @@ -349,6 +355,9 @@ void main_speedup(int percent) static void main_speedset(int percent) { + if (netplay_is_init()) + return; + if (percent < 1 || percent > 1000) { DebugMessage(M64MSG_WARNING, "Invalid speed setting %i percent", percent); @@ -365,6 +374,9 @@ static void main_speedset(int percent) void main_set_fastforward(int enable) { + if (netplay_is_init()) + return; + static int ff_state = 0; static int SavedSpeedFactor = 100; @@ -395,6 +407,9 @@ void main_set_fastforward(int enable) static void main_set_speedlimiter(int enable) { + if (netplay_is_init() && !netplay_lag()) + return; + l_MainSpeedLimit = enable ? 1 : 0; } @@ -408,6 +423,9 @@ void main_toggle_pause(void) if (!g_EmulatorRunning) return; + if (netplay_is_init()) + return; + if (g_rom_pause) { DebugMessage(M64MSG_STATUS, "Emulation continued."); @@ -491,6 +509,9 @@ void main_state_inc_slot(void) void main_state_load(const char *filename) { + if (netplay_is_init()) + return; + if (filename == NULL) // Save to slot savestates_set_job(savestates_job_load, savestates_type_m64p, NULL); else @@ -499,6 +520,9 @@ void main_state_load(const char *filename) void main_state_save(int format, const char *filename) { + if (netplay_is_init()) + return; + if (filename == NULL) // Save to slot savestates_set_job(savestates_job_save, savestates_type_m64p, NULL); else // Save to file @@ -864,6 +888,9 @@ static void apply_speed_limiter(void) /* TODO: make a GameShark module and move that there */ static void gs_apply_cheats(struct cheat_ctx* ctx) { + if (netplay_is_init()) + return; + struct r4300_core* r4300 = &g_dev.r4300; if (g_gs_vi_counter < 60) @@ -906,6 +933,8 @@ void new_vi(void) main_check_inputs(); pause_loop(); + + netplay_check_sync(&g_dev.r4300.cp0); } static void main_switch_pak(int control_id) @@ -1272,12 +1301,12 @@ m64p_error main_run(void) { size_t i, k; size_t rdram_size; - unsigned int count_per_op; - unsigned int emumode; - unsigned int disable_extra_mem; - int si_dma_duration; - int no_compiled_jump; - int randomize_interrupt; + uint32_t count_per_op; + uint32_t emumode; + uint32_t disable_extra_mem; + int32_t si_dma_duration; + int32_t no_compiled_jump; + int32_t randomize_interrupt; struct file_storage eep; struct file_storage fla; struct file_storage sra; @@ -1304,7 +1333,8 @@ m64p_error main_run(void) savestates_set_autoinc_slot(ConfigGetParamBool(g_CoreConfig, "AutoStateSlotIncrement")); savestates_select_slot(ConfigGetParamInt(g_CoreConfig, "CurrentStateSlot")); no_compiled_jump = ConfigGetParamBool(g_CoreConfig, "NoCompiledJump"); - randomize_interrupt = ConfigGetParamBool(g_CoreConfig, "RandomizeInterrupt"); + //We disable any randomness for netplay + randomize_interrupt = !netplay_is_init() ? ConfigGetParamBool(g_CoreConfig, "RandomizeInterrupt") : 0; count_per_op = ConfigGetParamInt(g_CoreConfig, "CountPerOp"); if (ROM_PARAMS.disableextramem) @@ -1322,6 +1352,9 @@ m64p_error main_run(void) if (si_dma_duration < 0) si_dma_duration = ROM_PARAMS.sidmaduration; + //During netplay, player 1 is the source of truth for these settings + netplay_sync_settings(&count_per_op, &disable_extra_mem, &si_dma_duration, &emumode, &no_compiled_jump); + cheat_add_hacks(&g_cheat_ctx, ROM_PARAMS.cheats); /* do byte-swapping if it hasn't been done yet */ @@ -1400,9 +1433,13 @@ m64p_error main_run(void) memset(&l_gb_carts_data, 0, GAME_CONTROLLERS_COUNT*sizeof(*l_gb_carts_data)); memset(cin_compats, 0, GAME_CONTROLLERS_COUNT*sizeof(*cin_compats)); + netplay_read_registration(cin_compats); + for (i = 0; i < GAME_CONTROLLERS_COUNT; ++i) { - control_ids[i] = (int)i; + //During netplay, we "trick" the input plugin + //by replacing the regular control_id with the ID that is controlling the player during netplay + control_ids[i] = netplay_is_init() ? netplay_get_controller(i) : (int)i; /* if input plugin requests RawData let the input plugin do the channel device processing */ if (Controls[i].RawData) { @@ -1425,6 +1462,9 @@ m64p_error main_run(void) cin_compats[i].cont = &g_dev.controllers[i]; cin_compats[i].tpk = &g_dev.transferpaks[i]; cin_compats[i].last_pak_type = Controls[i].Plugin; + cin_compats[i].last_input = 0; + cin_compats[i].netplay_count = 0; + cin_compats[i].event_first = NULL; l_gb_carts_data[i].control_id = (int)i; diff --git a/src/main/netplay.c b/src/main/netplay.c new file mode 100644 index 00000000..d2adefff --- /dev/null +++ b/src/main/netplay.c @@ -0,0 +1,669 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - netplay.c * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2020 loganmc10 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define M64P_CORE_PROTOTYPES 1 +#include "api/callbacks.h" +#include "main.h" +#include "util.h" +#include "plugin/plugin.h" +#include "backends/plugins_compat/plugins_compat.h" +#include "netplay.h" + +#include + +static int l_canFF; +static int l_netplay_controller; +static int l_netplay_control[4]; +static UDPsocket l_udpSocket; +static TCPsocket l_tcpSocket; +static int l_udpChannel; +static int l_spectator; +static int l_netplay_is_init = 0; +static uint32_t l_vi_counter; +static uint8_t l_status; +static uint32_t l_reg_id; +static struct controller_input_compat *l_cin_compats; +static uint8_t l_plugin[4]; +static uint8_t l_buffer_target; +static uint8_t l_player_lag[4]; + +//UDP packet formats +#define UDP_SEND_KEY_INFO 0 +#define UDP_RECEIVE_KEY_INFO 1 +#define UDP_REQUEST_KEY_INFO 2 +#define UDP_SYNC_DATA 4 + +//TCP packet formats +#define TCP_SEND_SAVE 1 +#define TCP_RECEIVE_SAVE 2 +#define TCP_SEND_SETTINGS 3 +#define TCP_RECEIVE_SETTINGS 4 +#define TCP_REGISTER_PLAYER 5 +#define TCP_GET_REGISTRATION 6 +#define TCP_DISCONNECT_NOTICE 7 + + +m64p_error netplay_start(const char* host, int port) +{ + if (SDLNet_Init() < 0) + { + DebugMessage(M64MSG_ERROR, "Netplay: Could not initialize SDL Net library"); + return M64ERR_SYSTEM_FAIL; + } + + l_udpSocket = SDLNet_UDP_Open(0); + if (l_udpSocket == NULL) + { + DebugMessage(M64MSG_ERROR, "Netplay: UDP socket creation failed"); + return M64ERR_SYSTEM_FAIL; + } + + IPaddress dest; + SDLNet_ResolveHost(&dest, host, port); + + l_udpChannel = SDLNet_UDP_Bind(l_udpSocket, -1, &dest); + if (l_udpChannel < 0) + { + DebugMessage(M64MSG_ERROR, "Netplay: could not bind to UDP socket"); + SDLNet_UDP_Close(l_udpSocket); + l_udpSocket = NULL; + return M64ERR_SYSTEM_FAIL; + } + + l_tcpSocket = SDLNet_TCP_Open(&dest); + if (l_tcpSocket == NULL) + { + DebugMessage(M64MSG_ERROR, "Netplay: could not bind to TCP socket"); + SDLNet_UDP_Close(l_udpSocket); + l_udpSocket = NULL; + return M64ERR_SYSTEM_FAIL; + } + + for (int i = 0; i < 4; ++i) + { + l_netplay_control[i] = -1; + l_plugin[i] = 0; + l_player_lag[i] = 0; + } + + l_canFF = 0; + l_netplay_controller = 0; + l_netplay_is_init = 1; + l_spectator = 1; + l_vi_counter = 0; + l_status = 0; + l_reg_id = 0; + + return M64ERR_SUCCESS; +} + +m64p_error netplay_stop() +{ + if (l_udpSocket == NULL) + return M64ERR_INVALID_STATE; + else + { + char output_data[5]; + output_data[0] = TCP_DISCONNECT_NOTICE; + SDLNet_Write32(l_reg_id, &output_data[1]); + SDLNet_TCP_Send(l_tcpSocket, &output_data[0], 5); + + SDLNet_UDP_Unbind(l_udpSocket, l_udpChannel); + SDLNet_UDP_Close(l_udpSocket); + SDLNet_TCP_Close(l_tcpSocket); + l_tcpSocket = NULL; + l_udpSocket = NULL; + l_udpChannel = -1; + l_netplay_is_init = 0; + SDLNet_Quit(); + return M64ERR_SUCCESS; + } +} + +int netplay_is_init() +{ + return l_netplay_is_init; +} + +static uint8_t buffer_size(uint8_t control_id) +{ + //This function returns the size of the local input buffer + uint8_t counter = 0; + struct netplay_event* current = l_cin_compats[control_id].event_first; + while (current != NULL) + { + current = current->next; + ++counter; + } + return counter; +} + +static void netplay_request_input(uint8_t control_id) +{ + UDPpacket *packet = SDLNet_AllocPacket(12); + packet->data[0] = UDP_REQUEST_KEY_INFO; + packet->data[1] = control_id; //The player we need input for + SDLNet_Write32(l_reg_id, &packet->data[2]); //our registration ID + SDLNet_Write32(l_cin_compats[control_id].netplay_count, &packet->data[6]); //the current event count + packet->data[10] = l_spectator; //whether we are a spectator + packet->data[11] = buffer_size(control_id); //our local buffer size + packet->len = 12; + SDLNet_UDP_Send(l_udpSocket, l_udpChannel, packet); + SDLNet_FreePacket(packet); +} + +static int check_valid(uint8_t control_id, uint32_t count) +{ + //Check if we already have this event recorded locally, returns 1 if we do + struct netplay_event* current = l_cin_compats[control_id].event_first; + while (current != NULL) + { + if (current->count == count) //event already recorded + return 1; + current = current->next; + } + return 0; +} + +static int netplay_require_response(void* opaque) +{ + //This function runs inside a thread. + //It runs if our local buffer size is 0 (we need to execute a key event, but we don't have the data we need). + //We basically beg the server for input data. + //After 10 seconds a timeout occurs, we assume we have lost connection to the server. + uint8_t control_id = *(uint8_t*)opaque; + uint32_t timeout = SDL_GetTicks() + 10000; + while (!check_valid(control_id, l_cin_compats[control_id].netplay_count)) + { + if (SDL_GetTicks() > timeout) + { + l_udpChannel = -1; + return 0; + } + netplay_request_input(control_id); + SDL_Delay(5); + } + return 1; +} + +static void netplay_process() +{ + //In this function we process data we have received from the server + UDPpacket *packet = SDLNet_AllocPacket(512); + uint32_t curr, count, keys; + uint8_t plugin, player, current_status; + while (SDLNet_UDP_Recv(l_udpSocket, packet) == 1) + { + switch (packet->data[0]) + { + case UDP_RECEIVE_KEY_INFO: + player = packet->data[1]; + //current_status is a status update from the server + //it will let us know if another player has disconnected, or the games have desynced + current_status = packet->data[2]; + l_player_lag[player] = packet->data[3]; + if (current_status != l_status) + { + if (((current_status & 0x1) ^ (l_status & 0x1)) != 0) + DebugMessage(M64MSG_ERROR, "Netplay: players have de-synced at VI %u", l_vi_counter); + for (int dis = 1; dis < 5; ++dis) + { + if (((current_status & (0x1 << dis)) ^ (l_status & (0x1 << dis))) != 0) + DebugMessage(M64MSG_ERROR, "Netplay: player %u has disconnected", dis); + } + l_status = current_status; + } + curr = 5; + //this loop processes input data from the server, inserting new events into the linked list for each player + //it skips events that we have already recorded, or if we receive data for an event that has already happened + for (uint8_t i = 0; i < packet->data[4]; ++i) + { + count = SDLNet_Read32(&packet->data[curr]); + curr += 4; + + if (((count - l_cin_compats[player].netplay_count) > (UINT32_MAX / 2)) || (check_valid(player, count))) //event doesn't need to be recorded + { + curr += 5; + continue; + } + + keys = SDLNet_Read32(&packet->data[curr]); + curr += 4; + plugin = packet->data[curr]; + curr += 1; + + //insert new event at beginning of linked list + struct netplay_event* new_event = (struct netplay_event*)malloc(sizeof(struct netplay_event)); + new_event->count = count; + new_event->buttons = keys; + new_event->plugin = plugin; + new_event->next = l_cin_compats[player].event_first; + l_cin_compats[player].event_first = new_event; + } + break; + default: + DebugMessage(M64MSG_ERROR, "Netplay: received unknown message from server"); + break; + } + } + SDLNet_FreePacket(packet); +} + +static int netplay_ensure_valid(uint8_t control_id) +{ + //This function makes sure we have data for a certain event + //If we don't have the data, it will create a new thread that will request the data + if (check_valid(control_id, l_cin_compats[control_id].netplay_count)) + return 1; + + if (l_udpChannel == -1) + return 0; + +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_Thread* thread = SDL_CreateThread(netplay_require_response, "Netplay key request", &control_id); +#else + SDL_Thread* thread = SDL_CreateThread(netplay_require_response, &control_id); +#endif + while (!check_valid(control_id, l_cin_compats[control_id].netplay_count) && l_udpChannel != -1) + netplay_process(); + int success; + SDL_WaitThread(thread, &success); + return success; +} + +static void netplay_delete_event(struct netplay_event* current, uint8_t control_id) +{ + //This function deletes an event from the linked list + struct netplay_event* find = l_cin_compats[control_id].event_first; + while (find != NULL) + { + if (find->next == current) + { + find->next = current->next; + break; + } + find = find->next; + } + if (current == l_cin_compats[control_id].event_first) + l_cin_compats[control_id].event_first = l_cin_compats[control_id].event_first->next; + free(current); +} + +static uint32_t netplay_get_input(uint8_t control_id) +{ + uint32_t keys; + netplay_process(); + netplay_request_input(control_id); + + //l_buffer_target is set by the server upon registration + //l_player_lag is how far behind we are from the lead player + //buffer_size is the local buffer size + if (l_player_lag[control_id] > 0 && buffer_size(control_id) > l_buffer_target) + { + l_canFF = 1; + main_core_state_set(M64CORE_SPEED_LIMITER, 0); + } + else + { + main_core_state_set(M64CORE_SPEED_LIMITER, 1); + l_canFF = 0; + } + + if (netplay_ensure_valid(control_id)) + { + //We grab the event from the linked list, the delete it once it has been used + //Finally we increment the event counter + struct netplay_event* current = l_cin_compats[control_id].event_first; + while (current->count != l_cin_compats[control_id].netplay_count) + current = current->next; + keys = current->buttons; + Controls[control_id].Plugin = current->plugin; + netplay_delete_event(current, control_id); + ++l_cin_compats[control_id].netplay_count; + } + else + { + DebugMessage(M64MSG_ERROR, "Netplay: lost connection to server"); + main_core_state_set(M64CORE_EMU_STATE, M64EMU_STOPPED); + keys = 0; + } + + return keys; +} + +static void netplay_send_input(uint8_t control_id, uint32_t keys) +{ + UDPpacket *packet = SDLNet_AllocPacket(11); + packet->data[0] = UDP_SEND_KEY_INFO; + packet->data[1] = control_id; //player number + SDLNet_Write32(l_cin_compats[control_id].netplay_count, &packet->data[2]); // current event count + SDLNet_Write32(keys, &packet->data[6]); //key data + packet->data[10] = l_plugin[control_id]; //current plugin + packet->len = 11; + SDLNet_UDP_Send(l_udpSocket, l_udpChannel, packet); + SDLNet_FreePacket(packet); +} + +uint8_t netplay_register_player(uint8_t player, uint8_t plugin, uint8_t rawdata, uint32_t reg_id) +{ + l_reg_id = reg_id; + char output_data[8]; + output_data[0] = TCP_REGISTER_PLAYER; + output_data[1] = player; //player number we'd like to register + output_data[2] = plugin; //current plugin + output_data[3] = rawdata; //whether we are using a RawData input plugin + SDLNet_Write32(l_reg_id, &output_data[4]); + + SDLNet_TCP_Send(l_tcpSocket, &output_data[0], 8); + + uint8_t response[2]; + size_t recv = 0; + while (recv < 2) + recv += SDLNet_TCP_Recv(l_tcpSocket, &response[recv], 2 - recv); + l_buffer_target = response[1]; //local buffer size target + return response[0]; +} + +int netplay_lag() +{ + return l_canFF; +} + +int netplay_next_controller() +{ + return l_netplay_controller; +} + +void netplay_set_controller(uint8_t player) +{ + l_netplay_control[player] = l_netplay_controller++; + l_spectator = 0; +} + +int netplay_get_controller(uint8_t player) +{ + return l_netplay_control[player]; +} + +file_status_t netplay_read_storage(const char *filename, void *data, size_t size) +{ + //This function syncs save games. + //If the client is controlling player 1, it sends its save game to the server + //All other players receive save files from the server + const char *short_filename = strrchr(filename, '/'); + if (short_filename == NULL) + short_filename = strrchr(filename, '\\'); + short_filename += 1; + + uint32_t buffer_pos = 0; + char *output_data = malloc(size + strlen(short_filename) + 6); + + file_status_t ret; + uint8_t request; + if (l_netplay_control[0] != -1) + { + request = TCP_SEND_SAVE; + memcpy(&output_data[buffer_pos], &request, 1); + ++buffer_pos; + + //send file name + memcpy(&output_data[buffer_pos], short_filename, strlen(short_filename) + 1); + buffer_pos += strlen(short_filename) + 1; + + ret = read_from_file(filename, data, size); + if (ret == file_open_error) + memset(data, 0, size); //all zeros means there is no save file + SDLNet_Write32((int32_t)size, &output_data[buffer_pos]); //file data size + buffer_pos += 4; + memcpy(&output_data[buffer_pos], data, size); //file data + buffer_pos += size; + + SDLNet_TCP_Send(l_tcpSocket, &output_data[0], buffer_pos); + } + else + { + request = TCP_RECEIVE_SAVE; + memcpy(&output_data[buffer_pos], &request, 1); + ++buffer_pos; + + //name of the file we are requesting + memcpy(&output_data[buffer_pos], short_filename, strlen(short_filename) + 1); + buffer_pos += strlen(short_filename) + 1; + + SDLNet_TCP_Send(l_tcpSocket, &output_data[0], buffer_pos); + size_t recv = 0; + char *data_array = data; + while (recv < size) + recv += SDLNet_TCP_Recv(l_tcpSocket, data_array + recv, size - recv); + + int sum = 0; + for (int i = 0; i < size; ++i) + sum |= data_array[i]; + + if (sum == 0) //all zeros means there is no save file + ret = file_open_error; + else + ret = file_ok; + } + free(output_data); + return ret; +} + +void netplay_sync_settings(uint32_t *count_per_op, uint32_t *disable_extra_mem, int32_t *si_dma_duration, uint32_t *emumode, int32_t *no_compiled_jump) +{ + if (!netplay_is_init()) + return; + + char output_data[21]; + uint8_t request; + if (l_netplay_control[0] != -1) //player 1 is the source of truth for settings + { + request = TCP_SEND_SETTINGS; + memcpy(&output_data[0], &request, 1); + SDLNet_Write32(*count_per_op, &output_data[1]); + SDLNet_Write32(*disable_extra_mem, &output_data[5]); + SDLNet_Write32(*si_dma_duration, &output_data[9]); + SDLNet_Write32(*emumode, &output_data[13]); + SDLNet_Write32(*no_compiled_jump, &output_data[17]); + SDLNet_TCP_Send(l_tcpSocket, &output_data[0], 21); + } + else + { + request = TCP_RECEIVE_SETTINGS; + memcpy(&output_data[0], &request, 1); + SDLNet_TCP_Send(l_tcpSocket, &output_data[0], 1); + int32_t recv = 0; + while (recv < 20) + recv += SDLNet_TCP_Recv(l_tcpSocket, &output_data[recv], 20 - recv); + *count_per_op = SDLNet_Read32(&output_data[0]); + *disable_extra_mem = SDLNet_Read32(&output_data[4]); + *si_dma_duration = SDLNet_Read32(&output_data[8]); + *emumode = SDLNet_Read32(&output_data[12]); + *no_compiled_jump = SDLNet_Read32(&output_data[16]); + } +} + +void netplay_check_sync(struct cp0* cp0) +{ + //This function is used to check if games have desynced + //Every 60 VIs, it sends the value of the CP0 registers to the server + //The server will compare the values, and update the status byte if it detects a desync + if (!netplay_is_init()) + return; + + const uint32_t* cp0_regs = r4300_cp0_regs(cp0); + + if (l_vi_counter % 60 == 0) + { + uint32_t packet_len = (CP0_REGS_COUNT * 4) + 5; + UDPpacket *packet = SDLNet_AllocPacket(packet_len); + packet->data[0] = UDP_SYNC_DATA; + SDLNet_Write32(l_vi_counter, &packet->data[1]); //current VI count + for (int i = 0; i < CP0_REGS_COUNT; ++i) + { + SDLNet_Write32(cp0_regs[i], &packet->data[(i * 4) + 5]); + } + packet->len = packet_len; + SDLNet_UDP_Send(l_udpSocket, l_udpChannel, packet); + SDLNet_FreePacket(packet); + } + ++l_vi_counter; +} + +void netplay_read_registration(struct controller_input_compat* cin_compats) +{ + //This function runs right before the game starts + //The server shares the registration details about each player + if (!netplay_is_init()) + return; + + l_cin_compats = cin_compats; + + uint32_t reg_id; + char output_data = TCP_GET_REGISTRATION; + char input_data[24]; + SDLNet_TCP_Send(l_tcpSocket, &output_data, 1); + size_t recv = 0; + while (recv < 24) + recv += SDLNet_TCP_Recv(l_tcpSocket, &input_data[recv], 24 - recv); + uint32_t curr = 0; + for (int i = 0; i < 4; ++i) + { + reg_id = SDLNet_Read32(&input_data[curr]); + curr += 4; + if (reg_id == 0) //No one registered to control this player + { + Controls[i].Present = 0; + Controls[i].Plugin = 1; + Controls[i].RawData = 0; + curr += 2; + } + else + { + Controls[i].Present = 1; + Controls[i].Plugin = input_data[curr]; + l_plugin[i] = Controls[i].Plugin; + ++curr; + Controls[i].RawData = input_data[curr]; + ++curr; + } + } +} + +static void netplay_send_raw_input(struct pif* pif) +{ + for (int i = 0; i < 4; ++i) + { + if (l_netplay_control[i] != -1) + { + if (pif->channels[i].tx && pif->channels[i].tx_buf[0] == JCMD_CONTROLLER_READ) + netplay_send_input(i, *(uint32_t*)pif->channels[i].rx_buf); + } + } +} + +static void netplay_get_raw_input(struct pif* pif) +{ + for (int i = 0; i < 4; ++i) + { + if (Controls[i].Present == 1) + { + if (pif->channels[i].tx) + { + *pif->channels[i].rx &= ~0xC0; //Always show the controller as connected + + if(pif->channels[i].tx_buf[0] == JCMD_CONTROLLER_READ) + { + *(uint32_t*)pif->channels[i].rx_buf = netplay_get_input(i); + } + else if ((pif->channels[i].tx_buf[0] == JCMD_STATUS || pif->channels[i].tx_buf[0] == JCMD_RESET) && Controls[i].RawData) + { + //a bit of a hack for raw input controllers, force the status + uint16_t type = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT; + pif->channels[i].rx_buf[0] = (uint8_t)(type >> 0); + pif->channels[i].rx_buf[1] = (uint8_t)(type >> 8); + pif->channels[i].rx_buf[2] = 0; + } + else if (pif->channels[i].tx_buf[0] == JCMD_PAK_READ && Controls[i].RawData) + { + //also a hack for raw input, we return "mempak not present" if the game tries to read the mempak + pif->channels[i].rx_buf[32] = 255; + } + else if (pif->channels[i].tx_buf[0] == JCMD_PAK_WRITE && Controls[i].RawData) + { + //also a hack for raw input, we return "mempak not present" if the game tries to write to mempak + pif->channels[i].rx_buf[0] = 255; + } + } + } + } +} + +void netplay_update_input(struct pif* pif) +{ + if (netplay_is_init()) + { + netplay_send_raw_input(pif); + netplay_get_raw_input(pif); + } +} + +void netplay_set_plugin(uint8_t control_id, uint8_t plugin) +{ + if (!(control_id > 0 && plugin == 2)) //Only P1 can use mempak + l_plugin[control_id] = plugin; +} + +m64p_error netplay_send_config(char* data, int size) +{ + if (!netplay_is_init()) + return M64ERR_NOT_INIT; + + if (l_netplay_control[0] != -1 || size == 1) //Only P1 sends settings, we allow all players to send if the size is 1, this may be a request packet + { + int result = SDLNet_TCP_Send(l_tcpSocket, data, size); + if (result < size) + return M64ERR_SYSTEM_FAIL; + return M64ERR_SUCCESS; + } + else + return M64ERR_INVALID_STATE; +} + +m64p_error netplay_receive_config(char* data, int size) +{ + if (!netplay_is_init()) + return M64ERR_NOT_INIT; + + if (l_netplay_control[0] == -1) //Only P2-4 receive settings + { + int recv = 0; + while (recv < size) + { + recv += SDLNet_TCP_Recv(l_tcpSocket, &data[recv], size - recv); + if (recv < 1) + return M64ERR_SYSTEM_FAIL; + } + return M64ERR_SUCCESS; + } + else + return M64ERR_INVALID_STATE; +} diff --git a/src/main/netplay.h b/src/main/netplay.h new file mode 100644 index 00000000..ed83cc1e --- /dev/null +++ b/src/main/netplay.h @@ -0,0 +1,137 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - netplay.h * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2020 loganmc10 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __NETPLAY_H__ +#define __NETPLAY_H__ + +#include "device/r4300/cp0.h" +#include "device/pif/pif.h" +#include "main/util.h" + +#define NETPLAY_CORE_VERSION 1 + +struct netplay_event { + uint32_t buttons; + uint8_t plugin; + uint32_t count; + struct netplay_event* next; +}; + +struct controller_input_compat; + +#ifdef M64P_NETPLAY + +m64p_error netplay_start(const char* host, int port); +m64p_error netplay_stop(); +uint8_t netplay_register_player(uint8_t player, uint8_t plugin, uint8_t rawdata, uint32_t reg_id); +int netplay_lag(); +void netplay_set_controller(uint8_t player); +int netplay_is_init(); +int netplay_get_controller(uint8_t player); +file_status_t netplay_read_storage(const char *filename, void *data, size_t size); +void netplay_sync_settings(uint32_t *count_per_op, uint32_t *disable_extra_mem, int32_t *si_dma_duration, uint32_t *emumode, int32_t *no_compiled_jump); +void netplay_check_sync(struct cp0* cp0); +int netplay_next_controller(); +void netplay_read_registration(struct controller_input_compat* cin_compats); +void netplay_update_input(struct pif* pif); +void netplay_set_plugin(uint8_t control_id, uint8_t plugin); +m64p_error netplay_send_config(char* data, int size); +m64p_error netplay_receive_config(char* data, int size); + +#else + +static osal_inline m64p_error netplay_start(const char* host, int port) +{ + return M64ERR_INCOMPATIBLE; +} + +static osal_inline m64p_error netplay_stop() +{ + return M64ERR_INCOMPATIBLE; +} + +static osal_inline uint8_t netplay_register_player(uint8_t player, uint8_t plugin, uint8_t rawdata, uint32_t reg_id) +{ + return 0; +} + +static osal_inline int netplay_lag() +{ + return 0; +} + +static osal_inline void netplay_set_controller(uint8_t player) +{ +} + +static osal_inline int netplay_is_init() +{ + return 0; +} + +static osal_inline int netplay_get_controller(uint8_t player) +{ + return 0; +} + +static osal_inline file_status_t netplay_read_storage(const char *filename, void *data, size_t size) +{ + return 0; +} + +static osal_inline void netplay_sync_settings(uint32_t *count_per_op, uint32_t *disable_extra_mem, int32_t *si_dma_duration, uint32_t *emumode, int32_t *no_compiled_jump) +{ +} + +static osal_inline void netplay_check_sync(struct cp0* cp0) +{ +} + +static osal_inline int netplay_next_controller() +{ + return 0; +} + +static osal_inline void netplay_read_registration(struct controller_input_compat* cin_compats) +{ +} + +static osal_inline void netplay_update_input(struct pif* pif) +{ +} + +static osal_inline void netplay_set_plugin(uint8_t control_id, uint8_t plugin) +{ +} + +static osal_inline m64p_error netplay_send_config(char* data, int size) +{ + return M64ERR_INCOMPATIBLE; +} + +static osal_inline m64p_error netplay_receive_config(char* data, int size) +{ + return M64ERR_INCOMPATIBLE; +} + +#endif + +#endif diff --git a/src/main/version.h b/src/main/version.h index a3ce184f..4f2f87f9 100644 --- a/src/main/version.h +++ b/src/main/version.h @@ -31,6 +31,7 @@ #define CONFIG_API_VERSION 0x020301 #define DEBUG_API_VERSION 0x020001 #define VIDEXT_API_VERSION 0x030100 +#define NETPLAY_API_VERSION 0x010000 #define VERSION_PRINTF_SPLIT(x) (((x) >> 16) & 0xffff), (((x) >> 8) & 0xff), ((x) & 0xff)