From 620c00c52c2c136be5a9935874605f7ee73d9bbb Mon Sep 17 00:00:00 2001 From: meep-eep Date: Thu, 26 Oct 2006 22:17:03 +0000 Subject: [PATCH] Netplay git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2463 8092fc87-c524-0410-9efc-e669fe64eaf9 --- sc2/ChangeLog | 1 + sc2/INSTALL.msvc | 11 +- sc2/build.vars.in | 3 +- sc2/build/unix/build.config | 26 +- sc2/build/unix/config_functions | 2 +- sc2/build/unix/config_proginfo_host | 4 +- sc2/content/lbm/starcon.txt | 62 + sc2/content/melee/melebkgd.ani | 4 + sc2/content/melee/netplay.0.png | Bin 0 -> 1080 bytes sc2/content/melee/netplay.1.png | Bin 0 -> 1080 bytes sc2/content/melee/netplay.2.png | Bin 0 -> 1080 bytes sc2/content/melee/netplay.3.png | Bin 0 -> 1081 bytes sc2/doc/devel/netplay/notes | 21 + sc2/doc/devel/netplay/protocol | 223 ++++ sc2/doc/devel/netplay/states | 63 + sc2/doc/devel/netplay/todo | 103 ++ sc2/doc/users/netplay-howto.txt | 42 + sc2/src/msvc++/UrQuanMasters.dsp | 297 ++++- sc2/src/port.c | 1 - sc2/src/port.h | 272 ++++ sc2/src/sc2code/Makeinfo | 4 + sc2/src/sc2code/battle.c | 221 +++- sc2/src/sc2code/battle.h | 4 + sc2/src/sc2code/comm.c | 6 +- sc2/src/sc2code/comm.h | 2 + sc2/src/sc2code/controls.h | 12 +- sc2/src/sc2code/cyborg.c | 9 +- sc2/src/sc2code/element.h | 2 + sc2/src/sc2code/galaxy.c | 2 +- sc2/src/sc2code/gameinp.c | 12 +- sc2/src/sc2code/gamestr.h | 4 +- sc2/src/sc2code/intel.c | 17 +- sc2/src/sc2code/intel.h | 13 +- sc2/src/sc2code/libs/Makeinfo | 2 +- sc2/src/sc2code/libs/net.h | 29 + sc2/src/sc2code/libs/network/FILES | 24 + sc2/src/sc2code/libs/network/Makeinfo | 3 + sc2/src/sc2code/libs/network/bytesex.h | 96 ++ sc2/src/sc2code/libs/network/connect/Makeinfo | 2 + .../sc2code/libs/network/connect/connect.c | 475 +++++++ .../sc2code/libs/network/connect/connect.h | 111 ++ sc2/src/sc2code/libs/network/connect/listen.c | 458 +++++++ sc2/src/sc2code/libs/network/connect/listen.h | 104 ++ .../sc2code/libs/network/connect/resolve.c | 211 +++ .../sc2code/libs/network/connect/resolve.h | 107 ++ .../sc2code/libs/network/netmanager/Makeinfo | 2 + .../sc2code/libs/network/netmanager/ndesc.c | 202 +++ .../sc2code/libs/network/netmanager/ndesc.h | 81 ++ .../libs/network/netmanager/ndindex.ci | 103 ++ .../libs/network/netmanager/netmanager.h | 47 + .../libs/network/netmanager/netmanager_bsd.c | 223 ++++ .../libs/network/netmanager/netmanager_bsd.h | 26 + .../network/netmanager/netmanager_common.ci | 58 + .../libs/network/netmanager/netmanager_win.c | 460 +++++++ .../libs/network/netmanager/netmanager_win.h | 35 + sc2/src/sc2code/libs/network/netport.c | 91 ++ sc2/src/sc2code/libs/network/netport.h | 31 + sc2/src/sc2code/libs/network/network.h | 27 + sc2/src/sc2code/libs/network/network_bsd.c | 30 + sc2/src/sc2code/libs/network/network_win.c | 75 ++ sc2/src/sc2code/libs/network/socket/Makeinfo | 2 + sc2/src/sc2code/libs/network/socket/socket.c | 59 + sc2/src/sc2code/libs/network/socket/socket.h | 93 ++ .../sc2code/libs/network/socket/socket_bsd.c | 257 ++++ .../sc2code/libs/network/socket/socket_bsd.h | 33 + .../sc2code/libs/network/socket/socket_win.c | 278 ++++ .../sc2code/libs/network/socket/socket_win.h | 33 + sc2/src/sc2code/melee.c | 1172 ++++++++++++++--- sc2/src/sc2code/melee.h | 23 +- sc2/src/sc2code/netplay/FILES | 48 + sc2/src/sc2code/netplay/Makeinfo | 3 + sc2/src/sc2code/netplay/checkbuf.c | 146 ++ sc2/src/sc2code/netplay/checkbuf.h | 62 + sc2/src/sc2code/netplay/checksum.c | 268 ++++ sc2/src/sc2code/netplay/checksum.h | 86 ++ sc2/src/sc2code/netplay/crc.c | 139 ++ sc2/src/sc2code/netplay/crc.h | 44 + sc2/src/sc2code/netplay/nc_connect.ci | 303 +++++ sc2/src/sc2code/netplay/netconnection.c | 286 ++++ sc2/src/sc2code/netplay/netconnection.h | 209 +++ sc2/src/sc2code/netplay/netinput.c | 160 +++ sc2/src/sc2code/netplay/netinput.h | 50 + sc2/src/sc2code/netplay/netmelee.c | 557 ++++++++ sc2/src/sc2code/netplay/netmelee.h | 72 + sc2/src/sc2code/netplay/netmisc.c | 174 +++ sc2/src/sc2code/netplay/netmisc.h | 70 + sc2/src/sc2code/netplay/netoptions.c | 39 + sc2/src/sc2code/netplay/netoptions.h | 51 + sc2/src/sc2code/netplay/netplay.h | 54 + sc2/src/sc2code/netplay/netrcv.c | 189 +++ sc2/src/sc2code/netplay/netrcv.h | 27 + sc2/src/sc2code/netplay/netsend.c | 87 ++ sc2/src/sc2code/netplay/netsend.h | 28 + sc2/src/sc2code/netplay/netstate.c | 49 + sc2/src/sc2code/netplay/netstate.h | 69 + sc2/src/sc2code/netplay/notify.c | 117 ++ sc2/src/sc2code/netplay/notify.h | 50 + sc2/src/sc2code/netplay/packet.c | 257 ++++ sc2/src/sc2code/netplay/packet.h | 261 ++++ sc2/src/sc2code/netplay/packethandlers.c | 520 ++++++++ sc2/src/sc2code/netplay/packethandlers.h | 47 + sc2/src/sc2code/netplay/packetq.c | 200 +++ sc2/src/sc2code/netplay/packetq.h | 68 + sc2/src/sc2code/netplay/packetsenders.c | 199 +++ sc2/src/sc2code/netplay/packetsenders.h | 56 + sc2/src/sc2code/netplay/proto/Makeinfo | 2 + sc2/src/sc2code/netplay/proto/npconfirm.c | 81 ++ sc2/src/sc2code/netplay/proto/npconfirm.h | 30 + sc2/src/sc2code/netplay/proto/ready.c | 106 ++ sc2/src/sc2code/netplay/proto/ready.h | 31 + sc2/src/sc2code/pickmele.c | 399 ++++-- sc2/src/sc2code/pickmele.h | 3 + sc2/src/sc2code/planets/lander.c | 28 +- sc2/src/sc2code/setup.c | 15 +- sc2/src/sc2code/tactrans.c | 277 +++- sc2/src/starcon2.c | 87 +- sc2/src/uqmversion.h | 4 +- 117 files changed, 12189 insertions(+), 425 deletions(-) create mode 100644 sc2/content/melee/netplay.0.png create mode 100644 sc2/content/melee/netplay.1.png create mode 100644 sc2/content/melee/netplay.2.png create mode 100644 sc2/content/melee/netplay.3.png create mode 100644 sc2/doc/devel/netplay/notes create mode 100644 sc2/doc/devel/netplay/protocol create mode 100644 sc2/doc/devel/netplay/states create mode 100644 sc2/doc/devel/netplay/todo create mode 100644 sc2/doc/users/netplay-howto.txt create mode 100644 sc2/src/sc2code/libs/net.h create mode 100644 sc2/src/sc2code/libs/network/FILES create mode 100644 sc2/src/sc2code/libs/network/Makeinfo create mode 100644 sc2/src/sc2code/libs/network/bytesex.h create mode 100644 sc2/src/sc2code/libs/network/connect/Makeinfo create mode 100644 sc2/src/sc2code/libs/network/connect/connect.c create mode 100644 sc2/src/sc2code/libs/network/connect/connect.h create mode 100644 sc2/src/sc2code/libs/network/connect/listen.c create mode 100644 sc2/src/sc2code/libs/network/connect/listen.h create mode 100644 sc2/src/sc2code/libs/network/connect/resolve.c create mode 100644 sc2/src/sc2code/libs/network/connect/resolve.h create mode 100644 sc2/src/sc2code/libs/network/netmanager/Makeinfo create mode 100644 sc2/src/sc2code/libs/network/netmanager/ndesc.c create mode 100644 sc2/src/sc2code/libs/network/netmanager/ndesc.h create mode 100644 sc2/src/sc2code/libs/network/netmanager/ndindex.ci create mode 100644 sc2/src/sc2code/libs/network/netmanager/netmanager.h create mode 100644 sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.c create mode 100644 sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.h create mode 100644 sc2/src/sc2code/libs/network/netmanager/netmanager_common.ci create mode 100644 sc2/src/sc2code/libs/network/netmanager/netmanager_win.c create mode 100644 sc2/src/sc2code/libs/network/netmanager/netmanager_win.h create mode 100644 sc2/src/sc2code/libs/network/netport.c create mode 100644 sc2/src/sc2code/libs/network/netport.h create mode 100644 sc2/src/sc2code/libs/network/network.h create mode 100644 sc2/src/sc2code/libs/network/network_bsd.c create mode 100644 sc2/src/sc2code/libs/network/network_win.c create mode 100644 sc2/src/sc2code/libs/network/socket/Makeinfo create mode 100644 sc2/src/sc2code/libs/network/socket/socket.c create mode 100644 sc2/src/sc2code/libs/network/socket/socket.h create mode 100644 sc2/src/sc2code/libs/network/socket/socket_bsd.c create mode 100644 sc2/src/sc2code/libs/network/socket/socket_bsd.h create mode 100644 sc2/src/sc2code/libs/network/socket/socket_win.c create mode 100644 sc2/src/sc2code/libs/network/socket/socket_win.h create mode 100644 sc2/src/sc2code/netplay/FILES create mode 100644 sc2/src/sc2code/netplay/Makeinfo create mode 100644 sc2/src/sc2code/netplay/checkbuf.c create mode 100644 sc2/src/sc2code/netplay/checkbuf.h create mode 100644 sc2/src/sc2code/netplay/checksum.c create mode 100644 sc2/src/sc2code/netplay/checksum.h create mode 100644 sc2/src/sc2code/netplay/crc.c create mode 100644 sc2/src/sc2code/netplay/crc.h create mode 100644 sc2/src/sc2code/netplay/nc_connect.ci create mode 100644 sc2/src/sc2code/netplay/netconnection.c create mode 100644 sc2/src/sc2code/netplay/netconnection.h create mode 100644 sc2/src/sc2code/netplay/netinput.c create mode 100644 sc2/src/sc2code/netplay/netinput.h create mode 100644 sc2/src/sc2code/netplay/netmelee.c create mode 100644 sc2/src/sc2code/netplay/netmelee.h create mode 100644 sc2/src/sc2code/netplay/netmisc.c create mode 100644 sc2/src/sc2code/netplay/netmisc.h create mode 100644 sc2/src/sc2code/netplay/netoptions.c create mode 100644 sc2/src/sc2code/netplay/netoptions.h create mode 100644 sc2/src/sc2code/netplay/netplay.h create mode 100644 sc2/src/sc2code/netplay/netrcv.c create mode 100644 sc2/src/sc2code/netplay/netrcv.h create mode 100644 sc2/src/sc2code/netplay/netsend.c create mode 100644 sc2/src/sc2code/netplay/netsend.h create mode 100644 sc2/src/sc2code/netplay/netstate.c create mode 100644 sc2/src/sc2code/netplay/netstate.h create mode 100644 sc2/src/sc2code/netplay/notify.c create mode 100644 sc2/src/sc2code/netplay/notify.h create mode 100644 sc2/src/sc2code/netplay/packet.c create mode 100644 sc2/src/sc2code/netplay/packet.h create mode 100644 sc2/src/sc2code/netplay/packethandlers.c create mode 100644 sc2/src/sc2code/netplay/packethandlers.h create mode 100644 sc2/src/sc2code/netplay/packetq.c create mode 100644 sc2/src/sc2code/netplay/packetq.h create mode 100644 sc2/src/sc2code/netplay/packetsenders.c create mode 100644 sc2/src/sc2code/netplay/packetsenders.h create mode 100644 sc2/src/sc2code/netplay/proto/Makeinfo create mode 100644 sc2/src/sc2code/netplay/proto/npconfirm.c create mode 100644 sc2/src/sc2code/netplay/proto/npconfirm.h create mode 100644 sc2/src/sc2code/netplay/proto/ready.c create mode 100644 sc2/src/sc2code/netplay/proto/ready.h diff --git a/sc2/ChangeLog b/sc2/ChangeLog index f14e88ac8..172bb5b36 100644 --- a/sc2/ChangeLog +++ b/sc2/ChangeLog @@ -1,4 +1,5 @@ Changes towards version 0.6: +- Netplay - SvdB - Added step-by-step instructions for compilation on MSVC++ - SvdB - All output done in binary mode, due to uio/Win32 conflicts (#912) - Michael - Online key configuration menu actually functional now - Michael diff --git a/sc2/INSTALL.msvc b/sc2/INSTALL.msvc index 5e87d54b7..d61f61b95 100644 --- a/sc2/INSTALL.msvc +++ b/sc2/INSTALL.msvc @@ -138,18 +138,13 @@ packages. - "C:\Program Files\OpenAL 1.1 SDK\libs\Win32" - "C:\Build\zlib-1.2.3\lib" -5.4. Remove ignore of msvcrt.lib +5.4. Remove unnecessary dependencies - Still in the "Linker" part of the project properties pages, in the left part of the screen, click on "Input". -- In the right part, select "Ignore Specific Library", and remove - "msvcrt.lib" - -5.5. Remove unnecessary dependencies -- Still in the "Linker", "Input" part of the project properties pages, - in the right part, select "Additional Dependencies", then click on "..." +- In the right part, select "Additional Dependencies", then click on "..." - Remove odbc32.lib and odbccp32.lib -5.6. Add user32.lib +5.5. Add user32.lib - Still in "Additional Dependencies", add "user32.lib" diff --git a/sc2/build.vars.in b/sc2/build.vars.in index 552ae50ed..25b8a178d 100644 --- a/sc2/build.vars.in +++ b/sc2/build.vars.in @@ -38,7 +38,8 @@ uqm_HAVE_REGEX='@HAVE_REGEX_H_FLAG@' uqm_HAVE_OPENGL='@HAVE_OPENGL@' uqm_USE_ZIP_IO='@USE_ZIP_IO@' uqm_USE_PLATFORM_ACCEL='@USE_PLATFORM_ACCEL@' +uqm_NETPLAY='@NETPLAY@' DEBUG='@DEBUG@' export BUILD_SYSTEM HOST_SYSTEM DEBUG export uqm_SOUNDMODULE uqm_USE_INTERNAL_MIKMOD uqm_HAVE_REGEX -export uqm_HAVE_OPENGL uqm_USE_ZIP_IO uqm_USE_PLATFORM_ACCEL +export uqm_HAVE_OPENGL uqm_USE_ZIP_IO uqm_USE_PLATFORM_ACCEL uqm_NETPLAY diff --git a/sc2/build/unix/build.config b/sc2/build/unix/build.config index d75769746..faf8f72fd 100644 --- a/sc2/build/unix/build.config +++ b/sc2/build/unix/build.config @@ -53,7 +53,7 @@ uqm_requirements() uqm_prepare_config() { # Describe the menu: - MENU_main_ITEMS="debug graphics sound mikmod ovcodec joystick \ + MENU_main_ITEMS="debug graphics sound mikmod ovcodec netplay joystick \ ioformat accel" case "$HOST_SYSTEM" in Darwin) @@ -72,6 +72,7 @@ uqm_prepare_config() MENU_main_ITEM_sound_TYPE=CHOICE MENU_main_ITEM_mikmod_TYPE=CHOICE MENU_main_ITEM_ovcodec_TYPE=CHOICE + MENU_main_ITEM_netplay_TYPE=CHOICE MENU_main_ITEM_joystick_TYPE=CHOICE MENU_main_ITEM_ioformat_TYPE=CHOICE MENU_main_ITEM_accel_TYPE=CHOICE @@ -210,6 +211,27 @@ uqm_prepare_config() CHOICE_joystick_OPTION_disabled_TITLE="disabled" CHOICE_joystick_DEFAULT=enabled + CHOICE_netplay_OPTIONS="none full ipv4" + CHOICE_netplay_TITLE="Network Supermelee support" + CHOICE_netplay_OPTION_none_TITLE="disabled" + CHOICE_netplay_OPTION_none_ACTIOn=netplay_none_action + netplay_none_action() { + NETPLAY="" + } + CHOICE_netplay_OPTION_full_TITLE="IPv4 and IPv6 (experimental)" + CHOICE_netplay_OPTION_full_ACTION=netplay_full_action + netplay_full_action() { + CFLAGS="$CFLAGS -DNETPLAY=NETPLAY_FULL" + NETPLAY="FULL" + } + CHOICE_netplay_OPTION_ipv4_TITLE="IPv4; no IPv6 (experimental)" + CHOICE_netplay_OPTION_ipv4_ACTION=netplay_ipv4_action + netplay_ipv4_action() { + CFLAGS="$CFLAGS -DNETPLAY=NETPLAY_IPV4" + NETPLAY="IPV4" + } + CHOICE_netplay_DEFAULT=full + CHOICE_ioformat_OPTIONS="stdio stdio_zip" CHOICE_ioformat_TITLE="Supported file i/o methods" CHOICE_ioformat_OPTION_stdio_TITLE="Only direct file i/o" @@ -311,7 +333,7 @@ uqm_process_config() { # Make build.vars from build.vars.in, substituting variables. SUBSTITUTE_VARS="BUILD_SYSTEM HOST_SYSTEM COMPILE CFLAGS LDFLAGS \ - MAKE ECHON SED DEBUG JOYSTICK \ + MAKE ECHON SED DEBUG JOYSTICK NETPLAY \ SOUNDMODULE USE_INTERNAL_MIKMOD \ HAVE_OPENGL HAVE_REGEX_H_FLAG USE_ZIP_IO \ USE_PLATFORM_ACCEL \ diff --git a/sc2/build/unix/config_functions b/sc2/build/unix/config_functions index 16371571e..a0a0e9dc4 100644 --- a/sc2/build/unix/config_functions +++ b/sc2/build/unix/config_functions @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -BUILDLOG=/dev/null +BUILDLOG=/tmp/build.log TEMPFILE="/tmp/build.$$.tmp" diff --git a/sc2/build/unix/config_proginfo_host b/sc2/build/unix/config_proginfo_host index 5ccd03368..8572d57f8 100644 --- a/sc2/build/unix/config_proginfo_host +++ b/sc2/build/unix/config_proginfo_host @@ -90,8 +90,8 @@ case "$HOST_SYSTEM" in LIB_SDL_image_LDFLAGS="-framework SDL_image" ;; *) - LIB_SDL_image_CFLAGS="$LIB_SDL_CFLAGS" - LIB_SDL_image_LDFLAGS="$LIB_SDL_LDFLAGS -lSDL_image" + LIB_SDL_image_CFLAGS="$LIB_SDL_CFLAGS -I/opt/SDL_image/include/SDL" + LIB_SDL_image_LDFLAGS="$LIB_SDL_LDFLAGS -L/opt/SDL_image/lib -lSDL_image" ;; esac LIB_SDL_image_VERSION="" diff --git a/sc2/content/lbm/starcon.txt b/sc2/content/lbm/starcon.txt index 9578eee57..82d6f77ca 100755 --- a/sc2/content/lbm/starcon.txt +++ b/sc2/content/lbm/starcon.txt @@ -2246,3 +2246,65 @@ dispatch exit menu +#(Unconnected. Press LEFT to connect.) +Unconnected. Press LEFT to connect. + + +#(Awaiting incoming connection...) +Awaiting incoming connection... +Press RIGHT to cancel. + +#(Attempting outgoing connection...) +Attempting outgoing connection... +Press RIGHT to cancel. + +#(Connected. Press RIGHT to disconnect.) +Connected. Press RIGHT to disconnect. + + +#(Confirmation cancelled. Press FIRE to reconfirm.) +Confirmation cancelled. Press FIRE to reconfirm. + + +#(Connection for bottom player not established.) +Connection for bottom player not established. + + +#(Connection for top player not established.) +Connection for top player not established. + + +#(Waiting for remote confirmation.) +Waiting for remote confirmation. + + +#(Bottom player is connected.) +Bottom player is connected. + + +#(Top player is connected.) +Top player is connected. + + +#(Bottom player: connection failed.) +Bottom player: connection failed. + + +#(Top player: connection failed.) +Top player: connection failed. + + +#(Bottom player: connection closed.) +Bottom player: connection closed. + + +#(Top player: connection closed.) +Top player: connection closed. + +#(Connection for bottom player not ready.) +Connection for bottom player not ready. + + +#(Connection for top player not ready.) +Connection for top player not ready. + diff --git a/sc2/content/melee/melebkgd.ani b/sc2/content/melee/melebkgd.ani index 6f4381b2f..de8c42447 100755 --- a/sc2/content/melee/melebkgd.ani +++ b/sc2/content/melee/melebkgd.ani @@ -29,3 +29,7 @@ melebkgd.27.png -1 0 0 50 melebkgd.28.png -1 0 -2 -22 melebkgd.29.png -1 0 -268 -217 melebkgd.30.png -1 0 -268 -217 +netplay.0.png -1 0 -256 -24 +netplay.1.png -1 0 -256 -24 +netplay.2.png -1 0 -256 -167 +netplay.3.png -1 0 -256 -167 diff --git a/sc2/content/melee/netplay.0.png b/sc2/content/melee/netplay.0.png new file mode 100644 index 0000000000000000000000000000000000000000..da390f291bbab750ac06ed648927663e0cbad56c GIT binary patch literal 1080 zcmWO5Z%i9y9KiA4+R~nv(y@A7`P23xLdMxF7&`1KIj#d~fxz(QZk82pBTF-BLMEe4 zcE+PG&;`LRp(9qe$1vDrG#U$u#MGN48fQX+3zM0J)66j^4o!@xi93TPJ}-XHFP|6R z7tf2|n9$W)OqbIDP~6_u@*>$B84qP7Z)^C%Fj>Y#bGt}U)b|fw*dPL-Qw%wsPE-su zjSNnM$AXF&4?;9DnRXdaMFp~0EHt4D5KY?w*`Fjp2oSiy?RFyo#4rfi9f(1DU@z(1 zMvo0KWo~ddP+8foVn=OlEnf}{!<%YNoQbPZE*AanW zQ0nL~)&zTddzE@%S%vDQWS7DSvZAm`Nr{P41_l(D%f%87g2J+GptN-oC<-Vr2+3qp z0Z;@%QQAaBP+FjybV;imP=r_#L{W*wO!Zw;ccNtNj9>v|v%5C}Iw7laAWC*CE{bWMeYHw*00~a>M9{ZP-)YoSN&U3ex?Ds;4%6A^L zUO$TWyng(|jXTf#@BVS6?>CF%tD%mdtNz#f2N$Mp{jf8(nN0=yFVCWl(-rFn9zUc$ zkP{vIefQ_iKE3yo*Iw-zNutl??w8=smG7GW@fy>GXFvWgdujIInZ~znj0DZ?jgR7^ zQ-3WQ*RMt1?h6`4-H X<3~S!I(z#r`7gAGx?1wheIx$^Bzl7w literal 0 HcmV?d00001 diff --git a/sc2/content/melee/netplay.1.png b/sc2/content/melee/netplay.1.png new file mode 100644 index 0000000000000000000000000000000000000000..a98d2eae2de2b889de7bd3b9f83ab9e4dad912e9 GIT binary patch literal 1080 zcmWO5Z%i9y9KiA4+R~n9DXd;s{TVExcBjcfr4E9*ocbcrFS(}{|K zrjfyE@K{h0<3Wf!E%kezJQys2wpGm^BF3EEhs+U&RKi6h z$s<{FJRGB#x6Le^BD2z0g9so@XLniRs!U|EIgr(~DH2(qHEN@=NyQig^Um&?Tx4uit7?Vz-G5hw~MFbs)A zLIF?&K~dU8MNnFymvjlM98iR40z^@XMokT3JRZl*;PYWRPG_odc{GEu4>L>=jmO7v zb#=9mFa_A>ivW%sC%_mmq#&J6V*nhDVjPL#C_VzGNS7|jgmH8<4Y3#=9W82(O-@d# z-N13Gb<(=%RXy^es&Z;snc1pdy{dY>UXCyeD#s0hIy6F{s-Qv^a=DxepvtnU4kcAt zJqhoSE@xj9RXLM`WKzv!%-zY^*;%a*!ePxeYg?_?s#B|)7S>!Yi%nZv((3E$!-Tg% z3x|`SCC?IQ8fb7E3Wb6OprulpmYmX3+8MY^x`Jc%gqE5wz|@pBKX2)q%IEX?AP9n9 znlIf7=r1}?6ZGaMSXpGzw=C?$$md{pkn)B z+l|9`=i5h*-Mss<|NB3$4E%0!d^OS;bT$0;VBf;@H$QDpY-Eyw!OPdt`pL?*y-y!e zAIb5~J-!EXr(f9h*;{Y+jU~_*a_?(!_sS0~|9Fk5{7awym$`I(->IheZjJ@b9Zip8 z$B84qP7Z)^C%Fj>Y#bGt}U)b|fw*dPL-Qw%wsPE-su zjSNnM$AXF&4?;9DnRXdaMFp~0EHt4D5KY?w*`Fjp2oSiy?RFyo#4rfi9f(1DU@z(1 zMvo0KWo~ddP+8foVn=OlEnf}{!<%YNoQbPZE*AanW zQ0nL~)&zTddzE@%S%vDQWS7DSvZAm`Nr{P41_l(D%f%87g2J+GptN-oC<-Vr2+3qp z0Z;@%QQAaBP+FjybV;imP=r_#L{W*wO!Zw;ccNtNj9>v|v%5C}Iw7laAWC*CE{bWMeYHw*00~a>M9{ZP-)YoSN&U3ex?Ds;4%6A^L zUO$TWyng(|jXTf#@BVS6?>CF%tD%mdtNz#f2N$Mp{jf8(nN0=yFVCWl(-rFn9zUc$ zkP{vIefQ_iKE3yo*Iw-zNutl??w8=smG7GW@fy>GXFvWgdujIInZ~znj0DZ?jgR7^ zQ-3WQ*RMt1?h6`4-H X<3~S!I(z#r`7gAGx?1wheIx$^Bzl7w literal 0 HcmV?d00001 diff --git a/sc2/content/melee/netplay.3.png b/sc2/content/melee/netplay.3.png new file mode 100644 index 0000000000000000000000000000000000000000..304ab826f6e8850af7e3a464a81a140438291853 GIT binary patch literal 1081 zcmWO5e@q)?7{KvorL@PTbX~cQA8qf*O2*kJjE*$5JC8tUp@HDd+$)#n zAMYQ}xX{y4PS%hBP#)@Re+F+34^5Qdw3Of0H=E5ArVn_Essq06 zAO_C^4?ghw{X78AvOHhs=2`v#JdQiRfphSz#}95d@A2qr+<`zqst1~u3;{#LChg@S zl0-`;Q;9)3dQ`I6Y&50_5>0zS@;-?nNg#m;;c!?2kT_0~ye$$Z9fmgCh07vtiR%hO zON-RiRZ`#5-`|g#fn^cVPb3|P5|Rk9$YRkGC>}=+hl9l&1jMqPfI7P|5CQ~)Fg!er z0FWRc)F~nXwL>rNhD}Ky5~9N(iYOY@H;aixLT&|*MGeJ2zG6k`iJTg++8k?Ay zPkS=L#@AtWd_JFmxe1yeOoBG~9)_lY2AfbU7Bv7Zo7J?*oR-zj z!94DYRXc-PcC`q(oVL13oXq{Q&#y1%KbyUAApG1Pwtiym>|;Zt@4iuL544Vl^s&C5 z-(UXta4s8vXQO7_erVBGQLta!{xZ;U$PeYAr$Co&$k^v^FO{ug5m%G literal 0 HcmV?d00001 diff --git a/sc2/doc/devel/netplay/notes b/sc2/doc/devel/netplay/notes new file mode 100644 index 000000000..afac62808 --- /dev/null +++ b/sc2/doc/devel/netplay/notes @@ -0,0 +1,21 @@ +As the game currently unfortunately works with polling, this is how +to integrate network handling with the game. + +In the function called periodically by DoInput(), there should be a call +to netInput() somewhere at the beginning, and a call to flushPacketQueues() +somewhere at the end. + +netInput() checks all connections for incoming packets, and calls +the appropriate packet handlers. + +flushPacketQueues() sends all pending packets on their way, for all +connections. + +In between, you can call functions that enqueue packets. +You would also check the network state here to determine whether you need to +act on some packet that has been delivered to the local party. + + + + + diff --git a/sc2/doc/devel/netplay/protocol b/sc2/doc/devel/netplay/protocol new file mode 100644 index 000000000..e7b651502 --- /dev/null +++ b/sc2/doc/devel/netplay/protocol @@ -0,0 +1,223 @@ +There are three types of negotiations used to synchronised the parties +of a network connection. + +- Continue when we know the other is ready ("Ready") + This is used when both parties need to sending information to the + other side, but what each party is doing does not interfere with + what the other party is doing. +- Only speak in your own turn ("Turn") + This is used when the parties have changes to make to common data. +- Mutual agreement on an action ("Confirm") + This is used to end a state where both parties are modifying + common data. Both parties have to agree with the data for either + party to continue. + + +============================================================================ + +"Ready" negotiation. + +Sometimes the parties need to notify eachother of their local state, +and then go on when both are ready. For this purpose both parties signify +that they are ready by sending the READY message. When a party is ready +and has received notice that the other party is ready, it can go on. + +States: +0. notReady - send nor received READY + !localReady && !remoteReady +1. localReady - sent READY, not yet received READY + localReady && !remoteReady +2. remoteReady - received READY, not yet sent READY + !localReady && remoteReady +3. ready - sent and received READY + +Messages: +- READY - "I have nothing further to send at this point" + + +From state 0 (notReady): +local decision -> Send READY, goto 1 +received READY -> goto 2 + +From state 1 (localReady): +received READY -> goto 3 + +From state 2 (remoteReady): +local decision -> Send READY goto 3 + + +============================================================================ + +"Turn" negotiation. + +For some actions (like changing a shared configuration option), it is +important that both sides don't just send changes at once. +To handle this, only one party may send these packets at any moment. +If the party whose turn it isn't wants to speak, or if the party whose +turn it is doesn't have anything further to say, he can send an ENDTURN +packet. The other party should confirm this by sending another ENDTURN +packet back. + +States: +0. myTurn - I may speak + myTurn && !endTurn +1. endMyTurn - I've given up speaking, waiting for confirmation + myTurn && endTurn +2. yourTurn - You may speak + !myTurn && !endTurn +3. endYourTurn - I want to speak, waiting for confirmation + !myTurn && endTurn + +Messages: +- ENDTURN - "this party ready to change turns" + + +From state 0 (myTurn): +local decision -> Send ENDTURN, goto 1 +received ENDTURN -> Send ENDTURN, goto 2 + +From state 1 (endMyTurn): +received ENDTURN -> Send ENDTURN, goto 2 + +From state 2 (yourTurn): +local decision -> Send ENDTURN, goto 3 +received ENDTURN -> Send ENDTURN, goto 0 + +From state 3 (endYourTurn): +received ENDTURN -> Send ENDTURN, goto 0 + + +============================================================================ + +"Confirm" negotiation. + +Some actions (like agreeing on a configuration) require confirmation +from both parties. This section documents the handshaking protocol involved. + +Each player must manually confirm the action. +After a player has confirmed an action, he may cancel it as long as +he hasn't received a confirmation from the other party. + +All messages arrive in the order sent. + + +States: +0. waiting + !handshake.canceling && !handshake.localOk && !handshake.remoteOk +1. localOk (cancelable) - sent CONFIRM1 (since last CANCEL) + !handshake.canceling && handshake.localOk && !handshake.remoteOk +2. remoteOk (cancelable) - received CONFIRM1 + !handshake.canceling && !handshake.localOk && handshake.remoteOk +3. committed - sent CONFIRM1 (since last CANCEL, + received CONFIRM1, + sent CONFIRM2 (since last CANCEL) + !handshake.canceling && handshake.localOk && handshake.remoteOk +4. cancelWaiting - sent CANCEL + handshake.canceling && !handshake.localOk && !handshake.remoteOk +5. cancelLocalOk - sent CANCEL and ready to send CONFIRM1, + but received no CANCELACK + handshake.canceling && handshake.localOk && !handshake.remoteOk +6. cancelRemoteOk - sent CANCEL and received CONFIRM1, + but received no CANCELACK + handshake.canceling && !handshake.localOk && handshake.remoteOk +7. cancelCommitted - sent CANCEL and ready to send CONFIRM2, + received CONFIRM1, + but received no CANCELACK + handshake.canceling && handshake.localOk && handshake.remoteOk +8. done - sent and received CONFIRM1 and CONFIRM2 + (since last CANCEL) + + +Handshake messages: +- CONFIRM1 - "the current local configuration OK for me" +- CONFIRM2 - "acknowledging your CONFIRM1; my own configuration is unchanged + since I sent CONFIRM1 (after the last CANCEL)" +- CANCEL - "forget about my earlier CONFIRM1" +- CANCELACK - "received your CANCEL" +MESSAGE(x) indicates any other message. + + +From state 0: (waiting) +local confirmation -> Send CONFIRM1, goto 1 +local changes -> Send MESSAGE(changes) (goto 0) +received CONFIRM1 -> goto 2 +received MESSAGE(changes) -> Process(changes) (goto 0) + +From state 1: (localOk) +local cancel -> Send CANCEL, goto 4 +received CONFIRM1 -> Send CONFIRM2, goto 3 +received CONFIRM2 -> Send CONFIRM2, goto 8 +received MESSAGE(changes) -> Process(changes), Send CANCEL, goto 4 + +From state 2: (remoteOk) +local confirmation -> Send CONFIRM2, goto 3 +local changes -> Send MESSAGE(changes), (goto 2) +received CANCEL -> Send CANCELACK, goto 0 + +From state 3: (committed) +received CONFIRM2 -> goto 8 +received CANCEL -> Send CANCELACK, goto 1 + +From state 4: (cancelWaiting) +local changes -> Send MESSAGE(changes), (goto 4) +local confirmation -> goto 5 +received CONFIRM1 -> goto 6 +received CONFIRM2 -> goto 6 +received CANCELACK -> goto 0 +received MESSAGE(changes) -> Process(changes), (goto 4) + +From state 5: (cancelLocalOk) +local cancel -> goto 4 +received CONFIRM1 -> goto 7 +received CONFIRM2 -> goto 7 +received CANCELACK -> SEND CONFIRM1 goto 1 +received MESSAGE(changes) -> Process(changes), goto 4 + +From state 6: (cancelRemoteOk) +local confirmation -> goto 7 +local changes -> Send MESSAGE(changes), (goto 6) +received CONFIRM2 -> (goto 6) +received CANCEL -> Send CANCELACK, goto 4 +received CANCELACK -> goto 2 + +From state 7: (cancelCommitted) +received CONFIRM2 -> (goto 7) +received CANCEL -> Send CANCELACK, goto 5 +received CANCELACK -> Send CONFIRM2, goto 3 + +On receiving local confirmation, sending CONFIRM2 is a shortcut for +sending CONFIRM1 followed by CONFIRM2. Receiving CONFIRM2 from localOk +and cancelLocalOk is accepted just for this shortcut. + + +To prove there are no race conditions, I examine all the combinations +of states and messages that are underway. Whenever the order of actions +isn't fixed, the result should be the same (eg. recv(CONFIRM1) followed +by send(CANCEL) should leave the party in the same state as when +the send(CANCEL) preceded the recv(CONFIRM1)). +I also check whether it is possible for packets to arrive that +aren't expected. + + +============================================================================ + +Battle ending negotiation. + +This negotation consists of: +1. a 'Ready' negotiation, before stopping sending frame data +2. communication of each sides current battle frame count +3. the side running behind processes more frames to catch up + + +States: + +0. Playing +1. localReady +2. remoteReady +3. countSent +4. catchingUp +5. awaitingCatchup + +// Unfinished... partially described in readyForBattleEndPlayer() + + diff --git a/sc2/doc/devel/netplay/states b/sc2/doc/devel/netplay/states new file mode 100644 index 000000000..64a21f1ec --- /dev/null +++ b/sc2/doc/devel/netplay/states @@ -0,0 +1,63 @@ +NetState_unconnected is the initial state. When a connection attempt is made, +the state is set to NetState_connecting. +The state field of a NetConnection is NULL. + +NetState_connecting indicates a connection is in progress. +When the connection is established, an INIT packet is sent, the state +is changed to NetState_init and InputFunc is set to DoNetworkInit. +The state field of a NetConnection is a ConnectStateData structure. + +NetState_init is for initialising the connection before actual game +information is sent. When an INIT packet is received, the state is set +to NetState_inSetup and InputFunc is set to DoMelee. +The state field of a NetConnection is a BattleStateData structure. + +NetState_inSetup is the state in which the fleet configuration is negotiated. +Only the side who has myTurn set may send this ship information, by means +of FLEET and TEAMNAME packets. +The Turn negotiation is used to change myTurn. +The Confirm negotiation is used to end this state and go to +NetState_preBattle. At this time InputFunc is set to DoPreMelee. +The state field of a NetConnection is a BattleStateData structure. + +NetState_preBattle is used for non-interactive battle negotiations. +One side sends the random seed; the other receives it. +The Ready negotiation is used to end this state and go to +NetState_interBattle. +The state field of a NetConnection is a BattleStateData structure. + +NetState_interBattle is used to allow either side to do some local +initialisations before moving on. +The Ready negotiation is used to end this state and go to +NetState_selectShip, or if there are no more ships to be selected, +to NetState_inBattle. +The state field of a NetConnection is a BattleStateData structure. + +NetState_selectShip is where a side may select his ship. The other +side is waiting for notice of this selection. +As soon as the selection has been sent or received, the state is changed +back to NetState_interBattle. +The state field of a NetConnection is a BattleStateData structure. + +NetState_inBattle is where the actual melee takes place. +Both sides send their input until the game is over, at which point +the Ready negotiation is used to end this state and go to +the NetState_endingBattle state. Until the Ready negotiation has been +completed, the simulation is continuing. +The state field of a NetConnection is a BattleStateData structure. + +NetState_endingBattle is where the local side waits for the remote +battle frame count, after it has sent its own. When it arrives, +the state changes to NetState_endingBattle2. +The state field of a NetConnection is a BattleStateData structure. + +NetState_endingBattle2 is where the side with the lowest battle frame count +catches up with the other other side, while the other side waits. +The Ready negotiation is used to signal that each side is ready, +and the state changes back to NetState_interBattle. +The state field of a NetConnection is a BattleStateData structure. + + +When a connection is aborted, the state is returned to NetState_unconnected. + + diff --git a/sc2/doc/devel/netplay/todo b/sc2/doc/devel/netplay/todo new file mode 100644 index 000000000..b19fafbac --- /dev/null +++ b/sc2/doc/devel/netplay/todo @@ -0,0 +1,103 @@ +High priority items: +- test compilation on release mode (on both Linux and Windows) + + +Medium-priority: +- Test disconnection at various points. + Whenever there's a call to NetInput(), connections may get lost, and + this needs to be checked. +- Setup menu for network options. +- Don't check PlayerControl in netmelee etc. Make it an invariant that + (netConnections[player] != NULL) if + ((PlayerControl[player] & NETWORK_CONTROL) == NETWORK_CONTROL) +- For the battle ending synchronisation, set the end at at least + getBattleInputDelay() + 1 frames in the future (instead of just 1), + so that there will be no hickup during the end synchronisation. + Also check this value for incoming packets. +- Disconnect notification. +- If a player only moves away from 'Battle!' there's no need for the other + to have to reconfirm. +- decent pause handling +- make compilation of crc.c and checksum.c conditional. +- negotiate checksum interval +- disconnect during battle will trigger the setup disconnect notification. + + +Low-priority: +- Different coloured icons for unconnected/connecting/connected. +- Send error packets; at least when there's a protocol version mismatch. +- Check whether the random seed and frame delay have been agreed before + continuing (in doConfirmSettings). +- Replacement for TOS. It is IPv4 only. +- Integrate network check functions with doInput + It will be easy to get rid of the separate threads then too. +- The state changes from interBattle to interBattle. That shouldn't happen, + but it doesn't seem to cause any problems. Need to investigate. + Addition: negotiateReadyConnections() is called again just to make sure + all sides pass this checkpoint. This is not a problem. It should be + documented in STATES though. +- More files define NETCONNECTION_INTERNAL than they should. +- voice transmission during the game (using an external lib) + read ramjee94adaptive.pdf +- Keep-alive packets. Store time of last packet sent, use alarms to determine + when to send the next one. Count received messages? + + +Future improvements/optimisations: +- For BSD sockets: use dup2() to move fds to lower values, so that less fds + have to be checked on select(). +- Use writev() to send multiple packets in one syscall, instead of + calling send() for each packet. +- Refusing games with both parties network controlled is not always + necessary. In theory it should be possible to have a client work + as "proxy". The client can watch as the actual players play the game. + Checking for "loops" would be tricky (eventually there should be a human + or computer controller for each side). +- Concurrent selection of ships. Note that if this is handled properly, it + will also be easy to take care of the "Allow Shofixti to choose last" bug. + Note that one party will still have to send his choice to the other side + first, which may be "eploited". Encryption would take care of this, + but at the least make sure the same player who gets to chose first + every time. +- meta-server. Use HTTP? Existing libs can be used, no problems with NAT, + human-readable. Speed is not an issue. +- move to UDP. Repeat past battleinput packets for each new packet that + is sent until they are confirmed. + + +Bugs: +- as positions are dependant on the screen resolution, you won't be able + to keep sync on games with a different resolution. +- Both sides need identical battle frame rates. This value is not + negotiated. +- When melee selection is aborted, the game crashes. + + +Final actions: +- Check out TODO, XXX, WORK tags +- memleak testing + - check for and remove mtrace()/muntrace() calls. +- update documentation + - FILES, also note which part of the separation they are in +- check coding style (search for '\>(') +- compile with maximum warnings + +When done: +- cvs diff +- Update uqm version number +- Update changelog + + +To put in the announcement of Netplay: +- Slow connections is acceptable. Packet loss isn't. + + +Bugs and todos unrelated to netplay. +- When you insert a new ship, the cursor moves to the next square, + but the picture at the right doesn't change. +- The "Battle!" icon is not positioned correctly. +- other player being able to choose the next ship after 3 seconds + of inactivity +- DoRunAway() shouldn't be handled in ProcessInput() + + diff --git a/sc2/doc/users/netplay-howto.txt b/sc2/doc/users/netplay-howto.txt new file mode 100644 index 000000000..9c32eb7fd --- /dev/null +++ b/sc2/doc/users/netplay-howto.txt @@ -0,0 +1,42 @@ +To use Netplay you need to pass some parameters to uqm. In future versions +you should be able to set everything you need to set from within the +game itself. + +--netport1 specifies the port the bottom player will connect to or accept + incoming connections on. If this parameter is not specified, 21837 will + be used. + Your firewall needs to be set up to allow TCP connections from/to + the used port. +--nethost1 specifies the name or ip number of the host to connect to for + the bottom player. If this parameter is not specified, UQM will + instead accept incoming connections. +--netport2 + As --netport1, but for the top player +--nethost2 + As --netport2, but for the bottom player +--netdelay + The desired input delay (in frames). A keypress or keyrelease + will only take effect after this many frames. The result is that while + higher values make your ship to seem to respond more sluggishly, + they give the keypress information more time to reach the remote party. + If the game stutters, this is because it is waiting for this + keypress information to arrive, which is an indication that the + input delay is too low. + As SuperMelee runs at 24 frames per second, each frame delayed will + delay the input by about 42ms. + The delay used is the maximum of the desired value for both parties. + The default is 0. Values lower than 4 will be hardly noticable + in terms of responsiveness. + Future versions may automatically decide on the best value to use. + +In SuperMelee, there is now an extra control method, "network control". +If you change the control method to that, and press LEFT, UQM will start +paying attention to incoming connections or try to establish an outgoing +connection, depending on the specified parameters. Pressing RIGHT will +cancel connection attempts or abort an already established connection. +You'll get a message when a connection has been established. In the mean +time you can already set up your own team. +The game won't start until both parties have confirmed the current setup. +If one player moves away from the 'Battle!' button, both parties need to +reconfirm. + diff --git a/sc2/src/msvc++/UrQuanMasters.dsp b/sc2/src/msvc++/UrQuanMasters.dsp index 1138df6dc..e1f19bcb9 100644 --- a/sc2/src/msvc++/UrQuanMasters.dsp +++ b/sc2/src/msvc++/UrQuanMasters.dsp @@ -44,7 +44,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MD /W3 /GX /Zi /O2 /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /D "USE_PLATFORM_ACCEL" /FD /c +# ADD CPP /nologo /MD /W3 /GX /Zi /O2 /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "NETPLAY" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /D "USE_PLATFORM_ACCEL" /FD /c # SUBTRACT CPP /YX # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" @@ -52,8 +52,8 @@ BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib /nologo /subsystem:windows /pdb:none /debug /machine:I386 /nodefaultlib:"msvcrtd.lib" /out:"../../uqm.exe" +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib ws2_32.lib /nologo /subsystem:windows /pdb:none /debug /machine:I386 /nodefaultlib:"msvcrtd.lib" /out:"../../uqm.exe" # Begin Special Build Tool SOURCE="$(InputPath)" PostBuild_Desc=Stripping debug info... @@ -74,7 +74,7 @@ PostBuild_Cmds=rebase -b 0x400000 -x . "../../uqm.exe" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MDd /W3 /GX /Zi /Od /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "DEBUG" /D "_DEBUG" /D "DEBUG_TRACK_SEM" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /D "USE_PLATFORM_ACCEL" /FR /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /GX /Zi /Od /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "DEBUG" /D "_DEBUG" /D "DEBUG_TRACK_SEM" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "NETPLAY" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /D "USE_PLATFORM_ACCEL" /FR /FD /GZ /c # SUBTRACT CPP /YX # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" @@ -82,8 +82,8 @@ BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo /o"UrQuanMasters.bsc" LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"../../uqmdebug.exe" /pdbtype:sept +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib ws2_32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"../../uqmdebug.exe" /pdbtype:sept # SUBTRACT LINK32 /nodefaultlib !ELSEIF "$(CFG)" == "UrQuanMasters - Win32 Debug NoAccel" @@ -102,7 +102,7 @@ LINK32=link.exe # PROP Target_Dir "" # ADD BASE CPP /nologo /MDd /W3 /GX /Zi /Od /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "DEBUG" /D "_DEBUG" /D "DEBUG_TRACK_SEM" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "ZLIB_DLL" /D "USE_PLATFORM_ACCEL" /FR /FD /GZ /c # SUBTRACT BASE CPP /YX -# ADD CPP /nologo /MDd /W3 /GX /Zi /Od /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "DEBUG" /D "_DEBUG" /D "DEBUG_TRACK_SEM" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /FR /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /GX /Zi /Od /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "DEBUG" /D "_DEBUG" /D "DEBUG_TRACK_SEM" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "NETPLAY" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /FR /FD /GZ /c # SUBTRACT CPP /YX # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" @@ -110,8 +110,8 @@ BSC32=bscmake.exe # ADD BASE BSC32 /nologo /o"UrQuanMasters.bsc" # ADD BSC32 /nologo /o"UrQuanMasters.bsc" LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"../../uqmdebug.exe" /pdbtype:sept -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"../../uqmdebug.exe" /pdbtype:sept +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib ws2_32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"../../uqmdebug.exe" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib ws2_32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"../../uqmdebug.exe" /pdbtype:sept # SUBTRACT LINK32 /nodefaultlib !ELSEIF "$(CFG)" == "UrQuanMasters - Win32 Release NoAccel" @@ -130,7 +130,7 @@ LINK32=link.exe # PROP Target_Dir "" # ADD BASE CPP /nologo /MD /W3 /GX /Zi /O2 /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "ZLIB_DLL" /D "USE_PLATFORM_ACCEL" /FD /c # SUBTRACT BASE CPP /YX -# ADD CPP /nologo /MD /W3 /GX /Zi /O2 /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /FR /FD /c +# ADD CPP /nologo /MD /W3 /GX /Zi /O2 /I "." /I ".." /I "..\sc2code" /I "..\sc2code\libs" /I "..\sc2code\ships" /I "..\regex" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D _VW=320 /D _VH=240 /D "HAVE_OPENGL" /D "GFXMODULE_SDL" /D "HAVE_OPENAL" /D "HAVE_ZIP" /D "HAVE_JOYSTICK" /D "NETPLAY" /D "ZLIB_DLL" /D "USE_INTERNAL_MIKMOD" /FR /FD /c # SUBTRACT CPP /YX # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" @@ -138,8 +138,8 @@ BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib /nologo /subsystem:console /pdb:none /debug /machine:I386 /nodefaultlib:"msvcrtd.lib" /out:"../../uqm.exe" -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib /nologo /subsystem:windows /pdb:none /debug /machine:I386 /nodefaultlib:"msvcrtd.lib" /out:"../../uqm.exe" +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib ws2_32.lib /nologo /subsystem:console /pdb:none /debug /machine:I386 /nodefaultlib:"msvcrtd.lib" /out:"../../uqm.exe" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib SDL.lib SDLmain.lib SDL_image.lib zdll.lib ws2_32.lib /nologo /subsystem:windows /pdb:none /debug /machine:I386 /nodefaultlib:"msvcrtd.lib" /out:"../../uqm.exe" # Begin Special Build Tool SOURCE="$(InputPath)" PostBuild_Desc=Stripping debug info... @@ -1333,6 +1333,114 @@ SOURCE=..\sc2code\libs\mikmod\virtch2.c SOURCE=..\sc2code\libs\mikmod\virtch_common.c # End Source File # End Group +# Begin Group "network" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\sc2code\libs\network\bytesex.h +# End Source File +# Begin Group "connect" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\sc2code\libs\network\connect\connect.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\connect\connect.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\connect\listen.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\connect\listen.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\connect\resolve.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\connect\resolve.h +# End Source File +# End Group + +# Begin Group "netmanager" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\ndesc.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\ndesc.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\ndindex.ci +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\netmanager_common.ci +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\netmanager.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\netmanager_win.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netmanager\netmanager_win.h +# End Source File +# End Group + +# Begin Source File + +SOURCE=..\sc2code\libs\network\netport.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\netport.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\network_win.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\network.h +# End Source File +# Begin Group "socket" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\sc2code\libs\network\socket\socket.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\socket\socket.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\socket\socket_win.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\libs\network\socket\socket_win.h +# End Source File +# End Group + +# End Group + # Begin Source File SOURCE=..\sc2code\libs\alarm.h @@ -1387,6 +1495,10 @@ SOURCE=..\sc2code\libs\misc.h # End Source File # Begin Source File +SOURCE=..\sc2code\libs\net.h +# End Source File +# Begin Source File + SOURCE=..\sc2code\libs\platform.h # End Source File # Begin Source File @@ -2333,6 +2445,167 @@ SOURCE=..\sc2code\comm\zoqfot\zoqfotc.c SOURCE=..\sc2code\comm\commall.h # End Source File +# End Group +# Begin Group "netplay" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\sc2code\netplay\checksum.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\checksum.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\checkbuf.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\checkbuf.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\crc.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\crc.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\nc_connect.ci +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netconnection.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netconnection.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netinput.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netinput.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netoptions.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netoptions.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netmelee.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netmelee.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netmisc.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netmisc.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netplay.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netrcv.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netrcv.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netsend.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netsend.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netstate.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\netstate.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\notify.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\notify.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packet.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packet.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packethandlers.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packethandlers.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packetsenders.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packetsenders.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packetq.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\packetq.h +# End Source File +# Begin Group "proto" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\sc2code\netplay\proto\npconfirm.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\proto\npconfirm.h +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\proto\ready.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\proto\ready.h +# End Source File +# End Group + # End Group # Begin Group "planets" diff --git a/sc2/src/port.c b/sc2/src/port.c index 0f4394535..e7aeaa394 100644 --- a/sc2/src/port.c +++ b/sc2/src/port.c @@ -110,4 +110,3 @@ readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) { } #endif - diff --git a/sc2/src/port.h b/sc2/src/port.h index 9aeb6155f..53fc7bf94 100644 --- a/sc2/src/port.h +++ b/sc2/src/port.h @@ -128,6 +128,278 @@ typedef unsigned short wchar_t; typedef unsigned int wint_t; #endif +// errno error numbers. The values used don't matter, as long as they +// don't conflict with existing errno error numbers. +#if defined (PORT_WANT_ERRNO) && defined (_MSC_VER) +# include +# ifndef E2BIG +# define E2BIG 0x01000001 +# endif +# ifndef EACCES +# define EACCES 0x01000002 +# endif +# ifndef EADDRINUSE +# define EADDRINUSE 0x01000003 +# endif +# ifndef EADDRNOTAVAIL +# define EADDRNOTAVAIL 0x01000004 +# endif +# ifndef EAFNOSUPPORT +# define EAFNOSUPPORT 0x01000005 +# endif +# ifndef EAGAIN +# ifdef EWOULDBLOCK +# define EAGAIN EWOULDBLOCK +# else +# define EAGAIN 0x01000006 +# endif +# endif +# ifndef EALREADY +# define EALREADY 0x01000007 +# endif +# ifndef EBADF +# define EBADF 0x01000008 +# endif +# ifndef EBADMSG +# define EBADMSG 0x01000009 +# endif +# ifndef EBUSY +# define EBUSY 0x0100000a +# endif +# ifndef ECANCELED +# define ECANCELED 0x0100000b +# endif +# ifndef ECHILD +# define ECHILD 0x0100000c +# endif +# ifndef ECONNABORTED +# define ECONNABORTED 0x0100000d +# endif +# ifndef ECONNREFUSED +# define ECONNREFUSED 0x0100000e +# endif +# ifndef ECONNRESET +# define ECONNRESET 0x0100000f +# endif +# ifndef EDEADLK +# define EDEADLK 0x01000010 +# endif +# ifndef EDESTADDRREQ +# define EDESTADDRREQ 0x01000011 +# endif +# ifndef EDOM +# define EDOM 0x01000012 +# endif +// Reserved in POSIX +//# ifndef //EDQUOT +//# define //EDQUOT 0x01000013 +//# endif +# ifndef EEXIST +# define EEXIST 0x01000014 +# endif +# ifndef EFAULT +# define EFAULT 0x01000015 +# endif +# ifndef EFBIG +# define EFBIG 0x01000016 +# endif +# ifndef EHOSTUNREACH +# define EHOSTUNREACH 0x01000017 +# endif +# ifndef EIDRM +# define EIDRM 0x01000018 +# endif +# ifndef EILSEQ +# define EILSEQ 0x01000019 +# endif +# ifndef EINPROGRESS +# define EINPROGRESS 0x0100001a +# endif +# ifndef EINTR +# define EINTR 0x0100001b +# endif +# ifndef EINVAL +# define EINVAL 0x0100001c +# endif +# ifndef EIO +# define EIO 0x0100001d +# endif +# ifndef EISCONN +# define EISCONN 0x0100001e +# endif +# ifndef EISDIR +# define EISDIR 0x0100001f +# endif +# ifndef ELOOP +# define ELOOP 0x01000020 +# endif +# ifndef EMFILE +# define EMFILE 0x01000021 +# endif +# ifndef EMLINK +# define EMLINK 0x01000022 +# endif +# ifndef EMSGSIZE +# define EMSGSIZE 0x01000023 +# endif +// Reserved in POSIX +//# ifndef //EMULTIHOP +//# define //EMULTIHOP 0x01000024 +//# endif +# ifndef ENAMETOOLONG +# define ENAMETOOLONG 0x01000025 +# endif +# ifndef ENETDOWN +# define ENETDOWN 0x01000026 +# endif +# ifndef ENETRESET +# define ENETRESET 0x01000027 +# endif +# ifndef ENETUNREACH +# define ENETUNREACH 0x01000028 +# endif +# ifndef ENFILE +# define ENFILE 0x01000029 +# endif +# ifndef ENOBUFS +# define ENOBUFS 0x0100002a +# endif +# ifndef ENODATA +# define ENODATA 0x0100002b +# endif +# ifndef ENODEV +# define ENODEV 0x0100002c +# endif +# ifndef ENOENT +# define ENOENT 0x0100002d +# endif +# ifndef ENOEXEC +# define ENOEXEC 0x0100002e +# endif +# ifndef ENOLCK +# define ENOLCK 0x0100002f +# endif +// Reserved in POSIX +//# ifndef ENOLINK +//# define ENOLINK 0x01000030 +//# endif +# ifndef ENOMEM +# define ENOMEM 0x01000031 +# endif +# ifndef ENOMSG +# define ENOMSG 0x01000032 +# endif +# ifndef ENOPROTOOPT +# define ENOPROTOOPT 0x01000033 +# endif +# ifndef ENOSPC +# define ENOSPC 0x01000034 +# endif +# ifndef ENOSR +# define ENOSR 0x01000035 +# endif +# ifndef ENOSTR +# define ENOSTR 0x01000036 +# endif +# ifndef ENOSYS +# define ENOSYS 0x01000037 +# endif +# ifndef ENOTCONN +# define ENOTCONN 0x01000038 +# endif +# ifndef ENOTDIR +# define ENOTDIR 0x01000039 +# endif +# ifndef ENOTEMPTY +# define ENOTEMPTY 0x0100003a +# endif +# ifndef ENOTSOCK +# define ENOTSOCK 0x0100003b +# endif +# ifndef ENOTSUP +# define ENOTSUP 0x0100003c +# endif +# ifndef ENOTTY +# define ENOTTY 0x0100003d +# endif +# ifndef ENXIO +# define ENXIO 0x0100003e +# endif +# ifndef EOPNOTSUPP +# define EOPNOTSUPP 0x0100003f +# endif +# ifndef EOVERFLOW +# define EOVERFLOW 0x01000040 +# endif +# ifndef EPERM +# define EPERM 0x01000041 +# endif +# ifndef EPIPE +# define EPIPE 0x01000042 +# endif +# ifndef EPROTO +# define EPROTO 0x01000043 +# endif +# ifndef EPROTONOSUPPORT +# define EPROTONOSUPPORT 0x01000044 +# endif +# ifndef EPROTOTYPE +# define EPROTOTYPE 0x01000045 +# endif +# ifndef ERANGE +# define ERANGE 0x01000046 +# endif +# ifndef EROFS +# define EROFS 0x01000047 +# endif +# ifndef ESPIPE +# define ESPIPE 0x01000048 +# endif +# ifndef ESRCH +# define ESRCH 0x01000049 +# endif +// Reserved in POSIX +//# ifndef //ESTALE +//# define //ESTALE 0x0100004a +//# endif +# ifndef ETIME +# define ETIME 0x0100004b +# endif +# ifndef ETIMEDOUT +# define ETIMEDOUT 0x0100004c +# endif +# ifndef ETXTBSY +# define ETXTBSY 0x0100004d +# endif +# ifndef EWOULDBLOCK +# ifdef EAGAIN +# define EWOULDBLOCK EAGAIN +# else +# define EWOULDBLOCK 0x0100004e +# endif +# endif +# ifndef EXDEV +# define EXDEV 0x0100004f +# endif + +// Non-POSIX: +# ifndef EHOSTDOWN +# define EHOSTDOWN 0x01100001 +# endif +# ifndef EPFNOSUPPORT +# define EPFNOSUPPORT 0x01100002 +# endif +# ifndef EPROCLIM +# define EPROCLIM 0x01100003 +# endif +# ifndef ESHUTDOWN +# define ESHUTDOWN 0x01100004 +# endif +# ifndef ESOCKTNOSUPPORT +# define ESOCKTNOSUPPORT 0x01100005 +# endif +#endif /* defined (PORT_WANT_ERRNO) && defined (_MSC_VER) */ + // Use SDL_INCLUDE and SDL_IMAGE_INCLUDE to portably include the SDL files // from the right location. // TODO: Where the SDL and SDL_image headers are located could be detected diff --git a/sc2/src/sc2code/Makeinfo b/sc2/src/sc2code/Makeinfo index d1dd450e5..d7f97aa97 100644 --- a/sc2/src/sc2code/Makeinfo +++ b/sc2/src/sc2code/Makeinfo @@ -9,3 +9,7 @@ uqm_CFILES="battle.c border.c build.c cleanup.c clock.c collide.c comm.c settings.c setup.c setupmenu.c ship.c shipstat.c shipyard.c sis.c sounds.c starbase.c starcon.c starmap.c state.c status.c tactrans.c trans.c uqmdebug.c util.c velocity.c weapon.c" +if [ -n "$uqm_NETPLAY" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS netplay" +fi + diff --git a/sc2/src/sc2code/battle.c b/sc2/src/sc2code/battle.c index 115228aed..d37f4bb97 100644 --- a/sc2/src/sc2code/battle.c +++ b/sc2/src/sc2code/battle.c @@ -21,6 +21,12 @@ #include "controls.h" #include "init.h" #include "intel.h" +#ifdef NETPLAY +# include "netplay/netmelee.h" +# ifdef NETPLAY_CHECKSUM +# include "netplay/checksum.h" +# endif +#endif #include "resinst.h" #include "setup.h" #include "settings.h" @@ -32,6 +38,15 @@ QUEUE disp_q; SIZE battle_counter; BOOLEAN instantVictory; +size_t battleInputOrder[NUM_SIDES]; + // Indices of the sides in the order their input is processed. + // Network sides are last so that the sides will never be waiting + // on eachother, and games with a 0 frame delay are theoretically + // possible. +#ifdef NETPLAY +BattleFrameCounter battleFrameCount; + // Used for synchronisation purposes during netplay. +#endif static BOOLEAN RunAwayAllowed (void) @@ -67,7 +82,7 @@ DoRunAway (STARSHIPPTR StarShipPtr) ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED); SetPrimColor (&DisplayArray[ElementPtr->PrimIndex], - BUILD_COLOR (MAKE_RGB15 (0xB, 0x00, 0x00), 0x2E)); + BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E)); SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMPFILL_PRIM); CyborgDescPtr->ship_input_state = 0; @@ -75,17 +90,54 @@ DoRunAway (STARSHIPPTR StarShipPtr) UnlockElement (StarShipPtr->hShip); } +static void +setupBattleInputOrder(void) +{ + size_t i; + +#ifndef NETPLAY + for (i = 0; i < NUM_SIDES; i++) + battleInputOrder[i] = i; +#else + int j; + + i = 0; + // First put the locally controlled players in the array. + for (j = 0; j < NUM_SIDES; j++) { + if (!(PlayerControl[j] & NETWORK_CONTROL)) { + battleInputOrder[i] = j; + i++; + } + } + + // Next put the network controlled players in the array. + for (j = 0; j < NUM_SIDES; j++) { + if (PlayerControl[j] & NETWORK_CONTROL) { + battleInputOrder[i] = j; + i++; + } + } +#endif +} + static void ProcessInput (void) { BOOLEAN CanRunAway; + size_t sideI; + +#ifdef NETPLAY + netInput (); +#endif CanRunAway = RunAwayAllowed (); - for (cur_player = NUM_SIDES - 1; cur_player >= 0; --cur_player) + for (sideI = 0; sideI < NUM_SIDES; sideI++) { HSTARSHIP hBattleShip, hNextShip; + cur_player = battleInputOrder[sideI]; + for (hBattleShip = GetHeadLink (&race_q[cur_player]); hBattleShip != 0; hBattleShip = hNextShip) { @@ -99,10 +151,24 @@ ProcessInput (void) { CyborgDescPtr = StarShipPtr; - InputState = (*(PlayerInput[cur_player]))(); + InputState = (*(PlayerInput[cur_player]))(cur_player, + StarShipPtr); #if CREATE_JOURNAL JournalInput (InputState); #endif /* CREATE_JOURNAL */ +#ifdef NETPLAY + if (PlayerControl[cur_player] & HUMAN_CONTROL) + { + BattleInputBuffer *bib = getBattleInputBuffer(cur_player); + sendBattleInputConnections (InputState); + flushPacketQueues (); + + BattleInputBuffer_push (bib, InputState); + // Add this input to the end of the buffer. + BattleInputBuffer_pop (bib, &InputState); + // Get the input from the front of the buffer. + } +#endif CyborgDescPtr->ship_input_state = 0; if (CyborgDescPtr->RaceDescPtr->ship_info.crew_level) @@ -128,6 +194,10 @@ ProcessInput (void) UnlockStarShip (&race_q[cur_player], hBattleShip); } } + +#ifdef NETPLAY + flushPacketQueues (); +#endif if (GLOBAL (CurrentActivity) & (CHECK_LOAD | CHECK_ABORT)) GLOBAL (CurrentActivity) &= ~IN_BATTLE; @@ -180,7 +250,39 @@ DoBattle (BATTLE_STATE *bs) bs->MenuRepeatDelay = 0; SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); +#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM) + if (getNumNetConnections() > 0 && + battleFrameCount % NETPLAY_CHECKSUM_INTERVAL == 0) + { + crc_State state; + Checksum checksum; + + crc_init(&state); + crc_processDispQueue (&state); + checksum = (Checksum) crc_finish (&state); + + sendChecksumConnections ((uint32) battleFrameCount, + (uint32) checksum); + flushPacketQueues (); + addLocalChecksum (battleFrameCount, checksum); + } +#endif ProcessInput (); + // Also calls NetInput() +#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM) + if (getNumNetConnections() > 0) + { + size_t delay = getBattleInputDelay(); + + if (battleFrameCount >= delay + && (battleFrameCount - delay) % NETPLAY_CHECKSUM_INTERVAL == 0) + { + if (!verifyChecksums (battleFrameCount - delay)) + GLOBAL(CurrentActivity) |= CHECK_ABORT; + } + } +#endif + LockMutex (GraphicsLock); if (bs->first_time) { @@ -220,7 +322,79 @@ DoBattle (BATTLE_STATE *bs) + BATTLE_FRAME_RATE / (battle_speed + 1)); bs->NextTime = GetTimeCounter (); } - return (BOOLEAN) ((GLOBAL (CurrentActivity) & IN_BATTLE) != 0); + + if ((GLOBAL (CurrentActivity) & IN_BATTLE) == 0) + return FALSE; + +#ifdef NETPLAY + battleFrameCount++; +#endif + return TRUE; +} + +// Let each player pick his ship. +static BOOLEAN +selectAllShips (SIZE num_ships) +{ +#ifndef NETPLAY + while (num_ships--) + { + if (!GetNextStarShip (NULL_PTR, num_ships == 1)) + return FALSE; + } + return TRUE; +#else + // On network play, what is the top player to one party may be + // the bottom player the other. To keep both sides synchronised + // the order is always the same. + SIZE order[2]; + + if (num_ships == 1) { + // HyperSpace in full game. + return GetNextStarShip (NULL_PTR, 0); + } + + if (num_ships != 2) + { + fprintf(stderr, "More than two players is not supported.\n"); + return FALSE; + } + + if ((PlayerControl[0] & NETWORK_CONTROL) && + (PlayerControl[1] & NETWORK_CONTROL)) + { + fprintf(stderr, "Only one side at a time can be network " + "controlled.\n"); + return FALSE; + } + + // Iff 'myTurn' is set on a connection, the local party will be + // processed first. + // If neither is network controlled, the top player (1) is handled first. + if (((PlayerControl[0] & NETWORK_CONTROL) && + !NetConnection_getDiscriminant (netConnections[0])) || + ((PlayerControl[1] & NETWORK_CONTROL) && + NetConnection_getDiscriminant (netConnections[1]))) + { + order[0] = 0; + order[1] = 1; + } + else + { + order[0] = 1; + order[1] = 0; + } + + { + size_t i; + for (i = 0; i < 2; i++) + { + if (!GetNextStarShip (NULL_PTR, order[i])) + return FALSE; + } + } + return TRUE; +#endif } BOOLEAN @@ -233,7 +407,10 @@ Battle (void) SetResourceIndex (hResIndex); #if !(DEMO_MODE || CREATE_JOURNAL) - TFB_SeedRandom (GetTimeCounter ()); + if (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE) { + // In Supermelee, the RNG is already initialised. + TFB_SeedRandom (GetTimeCounter ()); + } #else /* DEMO_MODE */ if (BattleSeed == 0) BattleSeed = TFB_Random (); @@ -262,21 +439,41 @@ Battle (void) CountLinks (&race_q[1]) ); - while (num_ships--) - { - if (!GetNextStarShip (NULL_PTR, num_ships)) - goto AbortBattle; - } + if (!selectAllShips (num_ships)) + goto AbortBattle; BattleSong (TRUE); bs.NextTime = 0; - bs.InputFunc = &DoBattle; - bs.first_time = (BOOLEAN)(LOBYTE (GLOBAL (CurrentActivity)) == IN_HYPERSPACE); + setupBattleInputOrder (); +#ifdef NETPLAY + initBattleInputBuffers (); +#ifdef NETPLAY_CHECKSUM + initChecksumBuffers (); +#endif + battleFrameCount = 0; + initBattleStateDataConnections (); + { + bool allOk = negotiateReadyConnections (true, NetState_inBattle); + if (!allOk) + goto AbortBattle; + } +#endif /* NETPLAY */ + bs.InputFunc = DoBattle; + bs.first_time = (BOOLEAN)(LOBYTE (GLOBAL (CurrentActivity)) == + IN_HYPERSPACE); + UnlockMutex (GraphicsLock); DoInput ((PVOID)&bs, FALSE); LockMutex (GraphicsLock); AbortBattle: +#ifdef NETPLAY + uninitBattleInputBuffers(); +#ifdef NETPLAY_CHECKSUM + uninitChecksumBuffers (); +#endif +#endif /* NETPLAY */ + StopMusic (); StopSound (); } diff --git a/sc2/src/sc2code/battle.h b/sc2/src/sc2code/battle.h index 21b771f18..94404cfee 100644 --- a/sc2/src/sc2code/battle.h +++ b/sc2/src/sc2code/battle.h @@ -22,6 +22,10 @@ extern QUEUE disp_q; extern SIZE battle_counter; extern BOOLEAN instantVictory; +#ifdef NETPLAY +typedef DWORD BattleFrameCounter; +extern BattleFrameCounter battleFrameCount; +#endif extern BOOLEAN Battle (void); diff --git a/sc2/src/sc2code/comm.c b/sc2/src/sc2code/comm.c index a24f2b22a..cdc58e9df 100644 --- a/sc2/src/sc2code/comm.c +++ b/sc2/src/sc2code/comm.c @@ -60,8 +60,6 @@ static int ambient_anim_task (void *data); -static BOOLEAN getLineWithinWidth(TEXT *pText, - const unsigned char **startNext, SIZE maxWidth, COUNT maxChars); #define MAX_RESPONSES 8 #define BACKGROUND_VOL \ @@ -355,8 +353,8 @@ add_text (int status, PTEXT pTextIn) // pText is the text to be fitted. pText->CharCount will be set to the // number of characters that fitted. // startNext will be filled with the start of the first word that -// doesn't fit in one line, or if an entire line fitted, to the character -// past the newline, or if the entire string fitted, to the end of the +// doesn't fit in one line, or if an entire line fits, to the character +// past the newline, or if the entire string fits, to the end of the // string. // maxWidth is the maximum number of pixels that a line may be wide // maxChars is the maximum number of characters (not bytes) that are to diff --git a/sc2/src/sc2code/comm.h b/sc2/src/sc2code/comm.h index 350803c55..38bf153f0 100644 --- a/sc2/src/sc2code/comm.h +++ b/sc2/src/sc2code/comm.h @@ -24,6 +24,8 @@ extern void init_communication (void); extern void uninit_communication (void); extern int do_subtitles (UNICODE *pStr); extern void AlienTalkSegue (COUNT wait_track); +BOOLEAN getLineWithinWidth(TEXT *pText, const unsigned char **startNext, + SIZE maxWidth, COUNT maxChars); extern RECT CommWndRect; /* comm window rect */ diff --git a/sc2/src/sc2code/controls.h b/sc2/src/sc2code/controls.h index 8b2ae3016..fb3bea434 100644 --- a/sc2/src/sc2code/controls.h +++ b/sc2/src/sc2code/controls.h @@ -19,6 +19,9 @@ #ifndef _CONTROLS_H_ #define _CONTROLS_H_ +#include "races.h" + // For STARSHIPPTR + #include "libs/compiler.h" #include "libs/strlib.h" @@ -85,8 +88,9 @@ typedef UBYTE BATTLE_INPUT_STATE; #define BATTLE_ESCAPE ((BATTLE_INPUT_STATE)(1 << 5)) #define BATTLE_DOWN ((BATTLE_INPUT_STATE)(1 << 6)) -typedef BATTLE_INPUT_STATE (*battle_summary_func) (void); -extern battle_summary_func ComputerInput, HumanInput[]; +typedef BATTLE_INPUT_STATE (*battle_summary_func) (COUNT player, + STARSHIPPTR StarShipPtr); +extern battle_summary_func ComputerInput, HumanInput[], NetworkInput; extern battle_summary_func PlayerInput[]; extern CONTROLLER_INPUT_STATE CurrentInputState, PulsedInputState; @@ -107,8 +111,8 @@ extern void WaitForNoInput (SIZE Duration); extern BOOLEAN ConfirmExit (void); extern void DoInput (PVOID pInputState, BOOLEAN resetInput); -BATTLE_INPUT_STATE p1_combat_summary (void); -BATTLE_INPUT_STATE p2_combat_summary (void); +BATTLE_INPUT_STATE p1_combat_summary (COUNT player, STARSHIPPTR StarShipPtr); +BATTLE_INPUT_STATE p2_combat_summary (COUNT player, STARSHIPPTR StarShipPtr); extern volatile BOOLEAN GamePaused, ExitRequested; diff --git a/sc2/src/sc2code/cyborg.c b/sc2/src/sc2code/cyborg.c index 9caebb944..101af0944 100644 --- a/sc2/src/sc2code/cyborg.c +++ b/sc2/src/sc2code/cyborg.c @@ -1010,7 +1010,7 @@ Avoid (ELEMENTPTR ShipPtr, EVALUATE_DESCPTR EvalDescPtr) } BATTLE_INPUT_STATE -tactical_intelligence (void) +tactical_intelligence (COUNT player, STARSHIPPTR StarShipPtr) { ELEMENTPTR ShipPtr; ELEMENT Ship; @@ -1019,10 +1019,9 @@ tactical_intelligence (void) COUNT ConcernCounter; EVALUATE_DESC ObjectsOfConcern[10]; BOOLEAN ShipMoved, UltraManeuverable; - STARSHIPPTR StarShipPtr, EnemyStarShipPtr; + STARSHIPPTR EnemyStarShipPtr; RACE_DESCPTR RDPtr, EnemyRDPtr; - StarShipPtr = CyborgDescPtr; RDPtr = StarShipPtr->RaceDescPtr; if (RDPtr->cyborg_control.ManeuverabilityIndex == 0) @@ -1037,7 +1036,7 @@ tactical_intelligence (void) } ShipMoved = TRUE; - if (PlayerControl[cur_player] & STANDARD_RATING) + if (PlayerControl[player] & STANDARD_RATING) ++StarShipPtr->special_counter; #ifdef DEBUG_CYBORG @@ -1260,7 +1259,7 @@ if (!(ShipPtr->state_flags & FINITE_LIFE) ed.which_turn = 0; } } - else if (!(PlayerControl[cur_player] & AWESOME_RATING)) + else if (!(PlayerControl[player] & AWESOME_RATING)) ed.which_turn = 0; else { diff --git a/sc2/src/sc2code/element.h b/sc2/src/sc2code/element.h index 249735bfe..e1b568e4a 100644 --- a/sc2/src/sc2code/element.h +++ b/sc2/src/sc2code/element.h @@ -43,6 +43,7 @@ typedef QUEUE_HANDLE HELEMENT; +// Bits for ELEMENT_FLAGS: #define GOOD_GUY (1 << 0) #define BAD_GUY (1 << 1) #define PLAYER_SHIP (1 << 2) @@ -65,6 +66,7 @@ typedef QUEUE_HANDLE HELEMENT; #define CREW_OBJECT (1 << 14) #define BACKGROUND_OBJECT (1 << 15) + #define HYPERJUMP_LIFE 15 #define NUM_EXPLOSION_FRAMES 12 diff --git a/sc2/src/sc2code/galaxy.c b/sc2/src/sc2code/galaxy.c index 7c03529d2..830047d90 100644 --- a/sc2/src/sc2code/galaxy.c +++ b/sc2/src/sc2code/galaxy.c @@ -260,7 +260,7 @@ InitGalaxy (void) { SetPrimType (&DisplayArray[p], STAMP_PRIM); SetPrimColor (&DisplayArray[p], - BUILD_COLOR (MAKE_RGB15 (0xB, 0xB, 0x1F), 0x09)); + BUILD_COLOR (MAKE_RGB15 (0x0B, 0x0B, 0x1F), 0x09)); DisplayArray[p].Object.Stamp.frame = stars_in_space; } else diff --git a/sc2/src/sc2code/gameinp.c b/sc2/src/sc2code/gameinp.c index cbdc3c10d..74d66e4f3 100644 --- a/sc2/src/sc2code/gameinp.c +++ b/sc2/src/sc2code/gameinp.c @@ -26,7 +26,7 @@ #include "libs/threadlib.h" -battle_summary_func ComputerInput, HumanInput[NUM_PLAYERS]; +battle_summary_func ComputerInput, HumanInput[NUM_PLAYERS], NetworkInput; battle_summary_func PlayerInput[NUM_PLAYERS]; #define ACCELERATION_INCREMENT (ONE_SECOND / 12) @@ -379,7 +379,7 @@ GetMenuSounds (MENU_SOUND_FLAGS *s0, MENU_SOUND_FLAGS *s1) /* These can really be refactored */ BATTLE_INPUT_STATE -p1_combat_summary (void) +p1_combat_summary (COUNT player, STARSHIPPTR StarShipPtr) { BATTLE_INPUT_STATE InputState = 0; if (CurrentInputState.key[PlayerOne][KEY_UP]) @@ -396,11 +396,14 @@ p1_combat_summary (void) InputState |= BATTLE_ESCAPE; if (CurrentInputState.key[PlayerOne][KEY_DOWN]) InputState |= BATTLE_DOWN; + + (void) player; + (void) StarShipPtr; return InputState; } BATTLE_INPUT_STATE -p2_combat_summary (void) +p2_combat_summary (COUNT player, STARSHIPPTR StarShipPtr) { BATTLE_INPUT_STATE InputState = 0; if (CurrentInputState.key[PlayerTwo][KEY_UP]) @@ -415,6 +418,9 @@ p2_combat_summary (void) InputState |= BATTLE_SPECIAL; if (CurrentInputState.key[PlayerTwo][KEY_DOWN]) InputState |= BATTLE_DOWN; + + (void) player; + (void) StarShipPtr; return InputState; } diff --git a/sc2/src/sc2code/gamestr.h b/sc2/src/sc2code/gamestr.h index 30a91dd52..bfac98e34 100644 --- a/sc2/src/sc2code/gamestr.h +++ b/sc2/src/sc2code/gamestr.h @@ -46,6 +46,7 @@ #define FLAGSHIP_STRING_COUNT 13 #define ORBITSCAN_STRING_COUNT 19 #define MAINMENU_STRING_COUNT 56 +#define NETMELEE_STRING_COUNT 16 enum { STAR_STRING_BASE = 0, @@ -69,8 +70,9 @@ enum { FLAGSHIP_STRING_BASE = STATUS_STRING_BASE + STATUS_STRING_COUNT, ORBITSCAN_STRING_BASE = FLAGSHIP_STRING_BASE + FLAGSHIP_STRING_COUNT, MAINMENU_STRING_BASE = ORBITSCAN_STRING_BASE + ORBITSCAN_STRING_COUNT, + NETMELEE_STRING_BASE = MAINMENU_STRING_BASE + MAINMENU_STRING_COUNT, - GAMESTR_COUNT = MAINMENU_STRING_BASE + MAINMENU_STRING_COUNT + GAMESTR_COUNT = NETMELEE_STRING_BASE + NETMELEE_STRING_COUNT }; diff --git a/sc2/src/sc2code/intel.c b/sc2/src/sc2code/intel.c index f55cf7bb4..3e336ff59 100644 --- a/sc2/src/sc2code/intel.c +++ b/sc2/src/sc2code/intel.c @@ -29,27 +29,28 @@ SIZE cur_player; BATTLE_INPUT_STATE -computer_intelligence (void) +computer_intelligence (COUNT player, STARSHIPPTR StarShipPtr) { BATTLE_INPUT_STATE InputState; if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE) return 0; - if (CyborgDescPtr) + if (StarShipPtr) { - if (PlayerControl[cur_player] & CYBORG_CONTROL) + if (PlayerControl[player] & CYBORG_CONTROL) { - InputState = tactical_intelligence (); + InputState = tactical_intelligence (player, StarShipPtr); // allow a player to warp-escape in cyborg mode - if (cur_player == 0) - InputState |= (*(HumanInput[cur_player])) () & BATTLE_ESCAPE; + if (player == 0) + InputState |= (*(HumanInput[player])) (player, StarShipPtr) + & BATTLE_ESCAPE; } else - InputState = (*(HumanInput[cur_player])) (); + InputState = (*(HumanInput[player])) (player, StarShipPtr); } - else if (!(PlayerControl[cur_player] & PSYTRON_CONTROL)) + else if (!(PlayerControl[player] & PSYTRON_CONTROL)) InputState = 0; else { diff --git a/sc2/src/sc2code/intel.h b/sc2/src/sc2code/intel.h index aae203dad..fb465fbcd 100644 --- a/sc2/src/sc2code/intel.h +++ b/sc2/src/sc2code/intel.h @@ -63,8 +63,10 @@ enum extern STARSHIPPTR CyborgDescPtr; extern SIZE cur_player; -extern BATTLE_INPUT_STATE computer_intelligence (void); -extern BATTLE_INPUT_STATE tactical_intelligence (void); +extern BATTLE_INPUT_STATE computer_intelligence (COUNT player, + STARSHIPPTR StarShipPtr); +extern BATTLE_INPUT_STATE tactical_intelligence (COUNT player, + STARSHIPPTR StarShipPtr); extern void ship_intelligence (ELEMENTPTR ShipPtr, EVALUATE_DESCPTR ObjectsOfConcern, COUNT ConcernCounter); extern BOOLEAN ship_weapons (ELEMENTPTR ShipPtr, ELEMENTPTR OtherPtr, @@ -80,11 +82,12 @@ extern BOOLEAN ThrustShip (ELEMENTPTR ShipPtr, COUNT angle); #define HUMAN_CONTROL (BYTE)(1 << 0) #define CYBORG_CONTROL (BYTE)(1 << 1) #define PSYTRON_CONTROL (BYTE)(1 << 2) +#define NETWORK_CONTROL (BYTE)(1 << 3) #define COMPUTER_CONTROL (CYBORG_CONTROL | PSYTRON_CONTROL) -#define STANDARD_RATING (BYTE)(1 << 3) -#define GOOD_RATING (BYTE)(1 << 4) -#define AWESOME_RATING (BYTE)(1 << 5) +#define STANDARD_RATING (BYTE)(1 << 4) +#define GOOD_RATING (BYTE)(1 << 5) +#define AWESOME_RATING (BYTE)(1 << 6) #endif /* _INTEL_H */ diff --git a/sc2/src/sc2code/libs/Makeinfo b/sc2/src/sc2code/libs/Makeinfo index 34fc2c94c..066b75963 100644 --- a/sc2/src/sc2code/libs/Makeinfo +++ b/sc2/src/sc2code/libs/Makeinfo @@ -1,5 +1,5 @@ uqm_SUBDIRS="callback decomp file graphics heap input list math memory - resource sound strings task threads time uio video log" + network resource sound strings task threads time uio video log" if [ -n "$uqm_USE_INTERNAL_MIKMOD" ]; then uqm_SUBDIRS="$uqm_SUBDIRS mikmod" fi diff --git a/sc2/src/sc2code/libs/net.h b/sc2/src/sc2code/libs/net.h new file mode 100644 index 000000000..50ce7e1bd --- /dev/null +++ b/sc2/src/sc2code/libs/net.h @@ -0,0 +1,29 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NET_H +#define _NET_H + +#include "network/network.h" +#include "network/netmanager/netmanager.h" +#include "network/connect/connect.h" +#include "network/connect/listen.h" +#include "network/connect/resolve.h" + +#endif /* _NET_H */ + diff --git a/sc2/src/sc2code/libs/network/FILES b/sc2/src/sc2code/libs/network/FILES new file mode 100644 index 000000000..8725b9f23 --- /dev/null +++ b/sc2/src/sc2code/libs/network/FILES @@ -0,0 +1,24 @@ +In libs/network/: +netport.{c,h} Portability definitions. +bytesex.h Functions for endianness conversions. +network.h Functions for initialising the network subsystem. +network_bsd.c Network subsystem functions for BSD sockets. +network_win.c Network subsystem functions for Winsock sockets. + +In libs/network/netmanager/: +ndesc.{c,h} Defines network descriptors. +netmanager.h Handles callbacks for network activity. +netmanager_bsd.{c,h} NetManager for systems with BSD sockets. +netmanager_win.{c,h} NetManager for Winsock systems. + +In libs/network/socket/: +socket.{c,h} Platform-independant socket layer. +socket_bsd.{c,h} Socket operations for BSD sockets. +socket_win.{c,h} Socket operations for Winsock sockets. + +In libs/network/connect/: +connect.{c,h} Routines for establishing outgoing connections. +listen.{c,h} Routines for receiving incoming connections. +resolve.{c,h} Routines for hostname lookups. + + diff --git a/sc2/src/sc2code/libs/network/Makeinfo b/sc2/src/sc2code/libs/network/Makeinfo new file mode 100644 index 000000000..916fe848e --- /dev/null +++ b/sc2/src/sc2code/libs/network/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="connect netmanager socket" +uqm_CFILES="netport.c network_bsd.c" + diff --git a/sc2/src/sc2code/libs/network/bytesex.h b/sc2/src/sc2code/libs/network/bytesex.h new file mode 100644 index 000000000..34a678260 --- /dev/null +++ b/sc2/src/sc2code/libs/network/bytesex.h @@ -0,0 +1,96 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Routines for changing the endianness of values. +// I'm not using ntohs() etc. as those would require include files that may +// have conflicting definitions. This is a problem on Windows, where these +// functions are in winsock2.h, which includes windows.h, which includes +// pretty much Microsoft's complete collection of .h files. + +#ifndef _BYTESEX_H +#define _BYTESEX_H + +#include "port.h" + // for inline +#include "config.h" + // for WORDS_BIGENDIAN +#include "types.h" + +static inline uint16 +swapBytes16(uint16 x) { + return (x << 8) | (x >> 8); +} + +static inline uint32 +swapBytes32(uint32 x) { + return (x << 24) + | ((x & 0x0000ff00) << 8) + | ((x & 0x00ff0000) >> 8) + | (x >> 24); +} + +#ifdef WORDS_BIGENDIAN +// Already in network order. + +static inline uint16 +hton16(uint16 x) { + return x; +} + +static inline uint32 +hton32(uint32 x) { + return x; +} + +static inline uint16 +ntoh16(uint16 x) { + return x; +} + +static inline uint32 +ntoh32(uint32 x) { + return x; +} + +#else /* !defined(WORDS_BIGENDIAN) */ +// Need to swap bytes + +static inline uint16 +hton16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +hton32(uint32 x) { + return swapBytes32(x); +} + +static inline uint16 +ntoh16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +ntoh32(uint32 x) { + return swapBytes32(x); +} + +#endif /* defined(WORDS_BIGENDIAN) */ + +#endif /* _BYTESEX_H */ + diff --git a/sc2/src/sc2code/libs/network/connect/Makeinfo b/sc2/src/sc2code/libs/network/connect/Makeinfo new file mode 100644 index 000000000..863983f58 --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="connect.c listen.c resolve.c" + diff --git a/sc2/src/sc2code/libs/network/connect/connect.c b/sc2/src/sc2code/libs/network/connect/connect.c new file mode 100644 index 000000000..6ca17c9a3 --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/connect.c @@ -0,0 +1,475 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#define CONNECT_INTERNAL +#define SOCKET_INTERNAL +#include "connect.h" + +#include "resolve.h" +#include "libs/alarm.h" +#include "../socket/socket.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include +#include +#include +#ifdef _MSC_VER +# include +# include +# include +#else +# include +#endif + +#define DEBUG_CONNECT_REF +#ifdef DEBUG_CONNECT_REF +# include "types.h" +#endif + + +static void connectHostNext(ConnectState *connectState); +static void doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen); +static void doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error); + + +static ConnectState * +ConnectState_alloc(void) { + return (ConnectState *) malloc(sizeof (ConnectState)); +}; + +static void +ConnectState_free(ConnectState *connectState) { + free(connectState); +} + +static void +ConnectState_delete(ConnectState *connectState) { + assert(connectState->nd == NULL); + assert(connectState->alarm == NULL); + assert(connectState->info == NULL); + assert(connectState->infoPtr == NULL); + ConnectState_free(connectState); +} + +void +ConnectState_incRef(ConnectState *connectState) { + assert(connectState->refCount < REFCOUNT_MAX); + connectState->refCount++; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref++ (%d)\n", + (uintptr_t) connectState, connectState->refCount); +#endif +} + +bool +ConnectState_decRef(ConnectState *connectState) { + assert(connectState->refCount > 0); + connectState->refCount--; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref-- (%d)\n", + (uintptr_t) connectState, connectState->refCount); +#endif + if (connectState->refCount == 0) { + ConnectState_delete(connectState); + return true; + } + return false; +} + +// decrements ref count by 1 +void +ConnectState_close(ConnectState *connectState) { + if (connectState->resolveState != NULL) { + Resolve_close(connectState->resolveState); + connectState->resolveState = NULL; + } + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + if (connectState->nd != NULL) { + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + } + if (connectState->info != NULL) { + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + } + connectState->state = Connect_closed; + ConnectState_decRef(connectState); +} + +void +ConnectState_setExtra(ConnectState *connectState, void *extra) { + connectState->extra = extra; +} + +void * +ConnectState_getExtra(ConnectState *connectState) { + return connectState->extra; +} + +// Try connecting to the next address. +static void +connectCallback(NetDescriptor *nd) { + // Called by the NetManager when a connection has been established. + ConnectState *connectState = + (ConnectState *) NetDescriptor_getExtra(nd); + int err; + + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + + if (connectState->state == Connect_closed) { + // The connection attempt has been aborted. + ConnectState_decRef(connectState); + return; + } + + if (Socket_getError(NetDescriptor_getSocket(nd), &err) == -1) { + log_add(log_Fatal, "Socket_getError() failed: %s.\n", + strerror(errno)); + explode(); + } + if (err != 0) { +#ifdef DEBUG + log_add(log_Debug, "connect() failed: %s.\n", strerror(err)); +#endif + NetDescriptor_close(nd); + connectState->nd = NULL; + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); + return; + } + + // Notify the higher layer. + connectState->nd = NULL; + // The callback function takes over ownership of the + // NetDescriptor. + NetDescriptor_setWriteCallback(nd, NULL); + // Note that connectState->info and connectState->infoPtr are cleaned up + // when ConnectState_close() is called by the callback function. + + ConnectState_incRef(connectState); + doConnectCallback(connectState, nd, connectState->infoPtr->ai_addr, + connectState->infoPtr->ai_addrlen); + { + // The callback called should release the last reference to + // the connectState, by calling ConnectState_close(). + bool released = ConnectState_decRef(connectState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +connectTimeoutCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); +} + +static void +setConnectTimeout(ConnectState *connectState) { + assert(connectState->alarm == NULL); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.timeout, + (AlarmCallback) connectTimeoutCallback, connectState); +} + +static Socket * +tryConnectHostNext(ConnectState *connectState) { + struct addrinfo *info; + Socket *sock; + int connectResult; + + assert(connectState->nd == NULL); + + info = connectState->infoPtr; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.\n", strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.\n", + strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + (void) Socket_setInlineOOB(sock); + // Ignore errors; it's not a big deal as the other party is not + // not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + + connectResult = Socket_connect(sock, info->ai_addr, info->ai_addrlen); + if (connectResult == 0) { + // Connection has already succeeded. + // We just wait for the writability callback anyhow, so that + // we can use one code path. + return sock; + } + + switch (errno) { + case EINPROGRESS: + // Connection in progress; wait for the write callback. + return sock; + } + + // Connection failed. + { + int savedErrno = errno; + Socket_close(sock); + errno = savedErrno; + } + return Socket_noSocket; +} + +static void +connectRetryCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + connectState->infoPtr = connectState->info; + connectHostNext(connectState); +} + +static void +setConnectRetryAlarm(ConnectState *connectState) { + assert(connectState->alarm == NULL); + assert(connectState->flags.retryDelayMs != Connect_noRetry); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.retryDelayMs, + (AlarmCallback) connectRetryCallback, connectState); +} + +static void +connectHostReportAllFailed(ConnectState *connectState) { + // Could not connect to any host. + ConnectError error; + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = ETIMEDOUT; + // No errno code is exactly suitable. We have been unable + // to connect to any host, but the reasons may vary + // (unreachable, refused, ...). + // ETIMEDOUT is the least specific portable errno code that + // seems appropriate. + doConnectErrorCallback(connectState, &error); +} + +static void +connectHostNext(ConnectState *connectState) { + Socket *sock; + + while (connectState->infoPtr != NULL) { + sock = tryConnectHostNext(connectState); + + if (sock != Socket_noSocket) { + // Connection succeeded or connection in progress + connectState->nd = + NetDescriptor_new(sock, (void *) connectState); + if (connectState->nd == NULL) { + ConnectError error; + int savedErrno = errno; + + log_add(log_Error, "NetDescriptor_new() failed: %s.\n", + strerror(errno)); + Socket_close(sock); + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = savedErrno; + doConnectErrorCallback(connectState, &error); + return; + } + + NetDescriptor_setWriteCallback(connectState->nd, connectCallback); + setConnectTimeout(connectState); + return; + } + + connectState->infoPtr = connectState->infoPtr->ai_next; + } + + // Connect failed to all addresses. + + if (connectState->flags.retryDelayMs == Connect_noRetry) { + connectHostReportAllFailed(connectState); + return; + } + + setConnectRetryAlarm(connectState); +} + +static void +connectHostResolveCallback(ResolveState *resolveState, + struct addrinfo *info) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + + connectState->state = Connect_connecting; + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + if (connectState->flags.familyPrefer != PF_UNSPEC) { + // Reorganise the 'info' list to put the structures of the + // prefered family in front. + struct addrinfo *preferred; + struct addrinfo **preferredEnd; + struct addrinfo *rest; + struct addrinfo **restEnd; + splitAddrInfoOnFamily(info, connectState->flags.familyPrefer, + &preferred, &preferredEnd, &rest, &restEnd); + info = preferred; + *preferredEnd = rest; + } + + connectState->info = info; + connectState->infoPtr = info; + + connectHostNext(connectState); +} + +static void +connectHostResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + ConnectError connectError; + + assert(resolveError->gaiRes != 0); + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + connectError.state = Connect_resolving; + connectError.resolveError = resolveError; + connectError.err = resolveError->err; + doConnectErrorCallback(connectState, &connectError); +} + +ConnectState * +connectHostByName(const char *host, const char *service, Protocol proto, + const ConnectFlags *flags, ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ConnectState *connectState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = 0; + + connectState = ConnectState_alloc(); + connectState->refCount = 1; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref=1 (%d)\n", + (uintptr_t) connectState, connectState->refCount); +#endif + connectState->state = Connect_resolving; + connectState->flags = *flags; + connectState->connectCallback = connectCallback; + connectState->errorCallback = errorCallback; + connectState->extra = extra; + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->nd = NULL; + connectState->alarm = NULL; + + connectState->resolveState = getaddrinfoAsync( + host, service, &hints, &resolveFlags, + (ResolveCallback) connectHostResolveCallback, + (ResolveErrorCallback) connectHostResolveErrorCallback, + (ResolveCallbackArg) connectState); + + return connectState; +} + +// NB: The callback function becomes the owner of nd +static void +doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen) { + assert(connectState->connectCallback != NULL); + + ConnectState_incRef(connectState); + // No need to increment nd as the callback function takes over ownership. + (*connectState->connectCallback)(connectState, nd, addr, addrLen); + ConnectState_decRef(connectState); +} + +static void +doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error) { + assert(connectState->errorCallback != NULL); + + ConnectState_incRef(connectState); + (*connectState->errorCallback)(connectState, error); + ConnectState_decRef(connectState); +} + + + diff --git a/sc2/src/sc2code/libs/network/connect/connect.h b/sc2/src/sc2code/libs/network/connect/connect.h new file mode 100644 index 000000000..95343cb09 --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/connect.h @@ -0,0 +1,111 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CONNECT_CONNECT_H +#define _CONNECT_CONNECT_H + + +typedef struct ConnectFlags ConnectFlags; +typedef struct ConnectError ConnectError; +typedef struct ConnectState ConnectState; + +typedef enum { + Connect_closed, + Connect_resolving, + Connect_connecting +} ConnectStateState; + + +#include "../netmanager/netmanager.h" +#include "../socket/socket.h" +#include "resolve.h" + + +// For connectHost() +struct ConnectFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int timeout; + /* Number of milliseconds before timing out a connection attempt. + * Note that if a host has multiple addresses, a connect to that + * host will have this timeout *per address*. */ + int retryDelayMs; + /* Retry connecting this many ms after connecting to the last + * address for the specified host fails. Set to Connect_noRetry + * to give up after one try. */ +#define Connect_noRetry -1 +}; + +struct ConnectError { + ConnectStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ConnectConnectCallback)(ConnectState *connectState, + NetDescriptor *nd, const struct sockaddr *addr, socklen_t addrLen); +typedef void (*ConnectErrorCallback)(ConnectState *connectState, + const ConnectError *error); + +#ifdef CONNECT_INTERNAL + +#include "libs/alarm.h" + +struct ConnectState { + RefCount refCount; + + ConnectStateState state; + ConnectFlags flags; + + ConnectConnectCallback connectCallback; + ConnectErrorCallback errorCallback; + void *extra; + + struct addrinfo *info; + struct addrinfo *infoPtr; + + ResolveState *resolveState; + + NetDescriptor *nd; + Alarm *alarm; + // Used for both the timeout for a connection attempt + // and to retry after all addresses have been tried. +}; +#endif /* CONNECT_INTERNAL */ + +ConnectState *connectHostByName(const char *host, const char *service, + Protocol proto, const ConnectFlags *flags, + ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra); +void ConnectState_incRef(ConnectState *connectState); +bool ConnectState_decRef(ConnectState *connectState); +void ConnectState_close(ConnectState *connectState); +void ConnectState_setExtra(ConnectState *connectState, void *extra); +void *ConnectState_getExtra(ConnectState *connectState); + +#endif /* _CONNECT_CONNECT_H */ + + diff --git a/sc2/src/sc2code/libs/network/connect/listen.c b/sc2/src/sc2code/libs/network/connect/listen.c new file mode 100644 index 000000000..c03beaeaf --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/listen.c @@ -0,0 +1,458 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define LISTEN_INTERNAL +#define SOCKET_INTERNAL +#include "listen.h" + +#include "resolve.h" +#include "../socket/socket.h" +#include "../netmanager/netmanager.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include +#ifdef _MSC_VER +# include +# include +#else +# include +#endif +#ifdef DEBUG +# ifdef _MSC_VER +# include +# endif +#endif +#include +#include + +#define DEBUG_LISTEN_REF +#ifdef DEBUG_LISTEN_REF +# include "types.h" +#endif + + +static void acceptCallback(NetDescriptor *nd); +static void doListenErrorCallback(ListenState *listenState, + const ListenError *error); + + +static ListenState * +ListenState_alloc(void) { + return (ListenState *) malloc(sizeof (ListenState)); +}; + +static void +ListenState_free(ListenState *listenState) { + free(listenState); +} + +static void +ListenState_delete(ListenState *listenState) { + assert(listenState->nds == NULL); + ListenState_free(listenState); +} + +void +ListenState_incRef(ListenState *listenState) { + assert(listenState->refCount < REFCOUNT_MAX); + listenState->refCount++; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref++ (%d)\n", + (uintptr_t) listenState, listenState->refCount); +#endif +} + +bool +ListenState_decRef(ListenState *listenState) { + assert(listenState->refCount > 0); + listenState->refCount--; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref-- (%d)\n", + (uintptr_t) listenState, listenState->refCount); +#endif + if (listenState->refCount == 0) { + ListenState_delete(listenState); + return true; + } + return false; +} + +// Decrements ref count byh 1 +void +ListenState_close(ListenState *listenState) { + if (listenState->resolveState != NULL) { + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + } + if (listenState->nds != NULL) { + while (listenState->numNd > 0) { + listenState->numNd--; + NetDescriptor_close(listenState->nds[listenState->numNd]); + } + free(listenState->nds); + listenState->nds = NULL; + } + listenState->state = Listen_closed; + ListenState_decRef(listenState); +} + +void +ListenState_setExtra(ListenState *listenState, void *extra) { + listenState->extra = extra; +} + +void * +ListenState_getExtra(ListenState *listenState) { + return listenState->extra; +} + +static NetDescriptor * +listenPortSingle(struct ListenState *listenState, struct addrinfo *info) { + Socket *sock; + int bindResult; + int listenResult; + NetDescriptor *nd; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.\n", strerror(errno)); + errno = savedErrno; + return NULL; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + // Error message is already printed. + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + bindResult = Socket_bind(sock, info->ai_addr, info->ai_addrlen); + if (bindResult == -1) { + int savedErrno = errno; + if (errno == EADDRINUSE) { +#ifdef DEBUG + log_add(log_Warning, "bind() failed: %s.\n", strerror(errno)); +#endif + } else + log_add(log_Error, "bind() failed: %s.\n", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + listenResult = Socket_listen(sock, listenState->flags.backlog); + if (listenResult == -1) { + int savedErrno = errno; + log_add(log_Error, "listen() failed: %s.\n", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + nd = NetDescriptor_new(sock, (void *) listenState); + if (nd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.\n", + strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + NetDescriptor_setReadCallback(nd, acceptCallback); + + return nd; +} + +static void +listenPortMulti(struct ListenState *listenState, struct addrinfo *info) { + struct addrinfo *addrPtr; + size_t addrCount; + size_t addrOkCount; + NetDescriptor **nds; + + // Count how many addresses we've got. + addrCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) + addrCount++; + + // This is where we intend to store the file descriptors of the + // listening sockets. + nds = malloc(addrCount * sizeof listenState->nds[0]); + + // Bind to each address. + addrOkCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) { + NetDescriptor *nd; + nd = listenPortSingle(listenState, addrPtr); + if (nd == NULL) { + // Failed. On to the next. + // An error message is already printed for serious errors. + // If the address is already in use, we here also print + // a message when we are not already listening on one of + // the other addresses. + // This is because on some IPv6 capabable systems (like Linux), + // IPv6 sockets also handle IPv4 connections, which means + // that a separate IPv4 socket won't be able to bind to the + // port. + // BUG: if the IPv4 socket is in the list before the + // IPv6 socket, it will be the IPv6 which will fail to bind, + // so only IPv4 connections will be handled, as v4 sockets can't + // accept v6 connections. + // In practice, on Linux, I haven't seen it happen, but + // it's a real possibility. + if (errno == EADDRINUSE && addrOkCount == 0) { + log_add(log_Error, "Error while preparing a network socket " + "for incoming connections: %s\n", strerror(errno)); + } + continue; + } + + nds[addrOkCount] = nd; + addrOkCount++; + } + + freeaddrinfo(info); + + listenState->nds = + realloc(nds, addrOkCount * sizeof listenState->nds[0]); + listenState->numNd = addrOkCount; + + if (addrOkCount == 0) { + // Could not listen on any port. + ListenError error; + error.state = Listen_listening; + error.err = EIO; + // Nothing better to offer. + doListenErrorCallback(listenState, &error); + return; + } +} + +static void +listenPortResolveCallback(ResolveState *resolveState, + struct addrinfo *result) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + listenState->state = Listen_listening; + listenPortMulti(listenState, result); +} + +static void +listenPortResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + ListenError listenError; + + assert(resolveError->gaiRes != 0); + + listenError.state = Listen_resolving; + listenError.resolveError = resolveError; + listenError.err = resolveError->err; + doListenErrorCallback(listenState, &listenError); +} + +// 'proto' is one of IPProto_tcp or IPProto_udp. +ListenState * +listenPort(const char *service, Protocol proto, const ListenFlags *flags, + ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ListenState *listenState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + // Acquire a list of addresses to bind to. + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = AI_PASSIVE; + + listenState = ListenState_alloc(); + listenState->refCount = 1; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref=1 (%d)\n", + (uintptr_t) listenState, listenState->refCount); +#endif + listenState->state = Listen_resolving; + listenState->flags = *flags; + listenState->connectCallback = connectCallback; + listenState->errorCallback = errorCallback; + listenState->extra = extra; + listenState->nds = NULL; + listenState->numNd = 0; + + listenState->resolveState = getaddrinfoAsync(NULL, service, &hints, + &resolveFlags, listenPortResolveCallback, + listenPortResolveErrorCallback, + (ResolveCallbackArg) listenState); + + return listenState; +} + +// NB: The callback function becomes the owner of newNd. +static void +doListenConnectCallback(ListenState *listenState, NetDescriptor *listenNd, + NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen) { + assert(listenState->connectCallback != NULL); + + ListenState_incRef(listenState); + // No need to increment listenNd, as there's guaranteed to be one + // reference from listenState. And no need to increment newNd, + // as the callback function takes over ownership. + (*listenState->connectCallback)(listenState, listenNd, newNd, + addr, (socklen_t) addrLen); + ListenState_decRef(listenState); +} + +static void +doListenErrorCallback(ListenState *listenState, const ListenError *error) { + assert(listenState->errorCallback != NULL); + + ListenState_incRef(listenState); + (*listenState->errorCallback)(listenState, error); + ListenState_decRef(listenState); +} + +static void +acceptSingleConnection(ListenState *listenState, NetDescriptor *nd) { + Socket *sock; + Socket *acceptResult; + struct sockaddr_storage addr; + socklen_t addrLen; + NetDescriptor *newNd; + + sock = NetDescriptor_getSocket(nd); + addrLen = sizeof (addr); + acceptResult = Socket_accept(sock, (struct sockaddr *) &addr, &addrLen); + if (acceptResult == Socket_noSocket) { + switch (errno) { + case EWOULDBLOCK: + case ECONNABORTED: + // Nothing serious. Keep listening. + return; + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: + case ENOSR: + // Serious problems, but future connections may still + // be possible. + log_add(log_Warning, "accept() reported '%s'\n", + strerror(errno)); + return; + default: + // Should not happen. + log_add(log_Fatal, "Internal error: accept() reported " + "'%s'\n", strerror(errno)); + explode(); + } + } + + (void) Socket_setReuseAddr(acceptResult); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(acceptResult) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.\n", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + (void) Socket_setInlineOOB(acceptResult); + // Ignore errors; it's not a big deal as the other + // party is not not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + +#ifdef DEBUG + { + char hostname[NI_MAXHOST]; + int gniRes; + + gniRes = getnameinfo((struct sockaddr *) &addr, addrLen, + hostname, sizeof hostname, NULL, 0, 0); + if (gniRes != 0) { + log_add(log_Error, "Error while performing hostname " + "lookup for incoming connection: %s\n", + (gniRes == EAI_SYSTEM) ? strerror(errno) : + gai_strerror(gniRes)); + } else { + log_add(log_Debug, "Accepted incoming connection from '%s'.\n", + hostname); + } + } +#endif + + newNd = NetDescriptor_new(acceptResult, NULL); + if (newNd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.\n", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + + doListenConnectCallback(listenState, nd, newNd, + (struct sockaddr *) &addr, addrLen); + // NB: newNd is now handed over to the callback function, and should + // no longer be referenced from here. +} + +// Called when select() has indicated readability on a listening socket, +// i.e. when a connection is in the queue. +static void +acceptCallback(NetDescriptor *nd) { + ListenState *listenState = (ListenState *) NetDescriptor_getExtra(nd); + + acceptSingleConnection(listenState, nd); +} + + diff --git a/sc2/src/sc2code/libs/network/connect/listen.h b/sc2/src/sc2code/libs/network/connect/listen.h new file mode 100644 index 000000000..59a8dc49c --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/listen.h @@ -0,0 +1,104 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CONNECT_LISTEN_H +#define _CONNECT_LISTEN_H + +typedef struct ListenFlags ListenFlags; +typedef enum { + Listen_closed, + Listen_resolving, + Listen_listening +} ListenStateState; +typedef struct ListenError ListenError; +typedef struct ListenState ListenState; + +#ifdef _MSC_VER +// I do not want to include winsock2.h, because of possible conflicts with +// code that includes this file. +// Note that listen.c itself includes winsock2.h; SOCKLEN_T is used there +// only where necessary to keep the API consistent. +# define SOCKLEN_T size_t +struct sockaddr; +#else +# include +# define SOCKLEN_T socklen_t +#endif + +#include "resolve.h" +#include "../socket/socket.h" + +#include "../netmanager/netmanager.h" + +// For listenPort() +struct ListenFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int backlog; + // As the 2rd parameter to listen(); +}; + +struct ListenError { + ListenStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ListenConnectCallback)(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen); +typedef void (*ListenErrorCallback)(ListenState *listenState, + const ListenError *error); + +#ifdef LISTEN_INTERNAL +struct ListenState { + RefCount refCount; + + ListenStateState state; + ListenFlags flags; + + ListenConnectCallback connectCallback; + ListenErrorCallback errorCallback; + void *extra; + + ResolveState *resolveState; + NetDescriptor **nds; + size_t numNd; + // INV: (numNd == NULL) == (nds == NULL) +}; +#endif /* defined(LISTEN_INTERNAL) */ + +ListenState *listenPort(const char *service, Protocol proto, + const ListenFlags *flags, ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra); +void ListenState_close(ListenState *listenState); +void ListenState_incRef(ListenState *listenState); +bool ListenState_decRef(ListenState *listenState); +void ListenState_setExtra(ListenState *listenState, void *extra); +void *ListenState_getExtra(ListenState *listenState); + +#endif /* _CONNECT_LISTEN_H */ + diff --git a/sc2/src/sc2code/libs/network/connect/resolve.c b/sc2/src/sc2code/libs/network/connect/resolve.c new file mode 100644 index 000000000..c29c8f63f --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/resolve.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define RESOLVE_INTERNAL +#include "resolve.h" + +#include +#include +#include + +#define DEBUG_RESOLVE_REF +#ifdef DEBUG_RESOLVE_REF +# include "types.h" +# include "libs/log.h" +# include +#endif + +static ResolveState * +ResolveState_new(void) { + return (ResolveState *) malloc(sizeof (ResolveState)); +}; + +static void +ResolveState_free(ResolveState *resolveState) { + free(resolveState); +} + +static void +ResolveState_delete(ResolveState *resolveState) { + assert(resolveState->callbackID == CallbackID_invalid); + ResolveState_free(resolveState); +} + +void +ResolveState_incRef(ResolveState *resolveState) { + assert(resolveState->refCount < REFCOUNT_MAX); + resolveState->refCount++; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref++ (%d)\n", + (uintptr_t) resolveState, resolveState->refCount); +#endif +} + +bool +ResolveState_decRef(ResolveState *resolveState) { + assert(resolveState->refCount > 0); + resolveState->refCount--; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref-- (%d)\n", + (uintptr_t) resolveState, resolveState->refCount); +#endif + if (resolveState->refCount == 0) { + ResolveState_delete(resolveState); + return true; + } + return false; +} + +void +ResolveState_setExtra(ResolveState *resolveState, void *extra) { + resolveState->extra = extra; +} + +void * +ResolveState_getExtra(ResolveState *resolveState) { + return resolveState->extra; +} + +static void +doResolveCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + (*resolveState->callback)(resolveState, resolveState->result); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +doResolveErrorCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + resolveState->errorCallback(resolveState, &resolveState->error); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +resolveCallback(ResolveState *resolveState) { + resolveState->callbackID = CallbackID_invalid; + if (resolveState->error.gaiRes == 0) { + // Successful lookup. + doResolveCallback(resolveState); + } else { + // Lookup failed. + doResolveErrorCallback(resolveState); + } +} + +// Function that does getaddrinfo() and calls the callback function when +// the result is available. +// TODO: actually make this function asynchronous. Right now it just calls +// getaddrinfo() (which blocks) and then schedules the callback. +ResolveState * +getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra) { + ResolveState *resolveState; + + resolveState = ResolveState_new(); + resolveState->refCount = 1; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref=1 (%d)\n", + (uintptr_t) resolveState, resolveState->refCount); +#endif + resolveState->state = Resolve_resolving; + resolveState->flags = *flags; + resolveState->callback = callback; + resolveState->errorCallback = errorCallback; + resolveState->extra = extra; + resolveState->result = NULL; + + resolveState->error.gaiRes = + getaddrinfo(node, service, hints, &resolveState->result); + resolveState->error.err = errno; + + resolveState->callbackID = Callback_add( + (CallbackFunction) resolveCallback, (CallbackArg) resolveState); + + return resolveState; +} + +void +Resolve_close(ResolveState *resolveState) { + if (resolveState->callbackID != CallbackID_invalid) { + Callback_removeCallback(resolveState->callbackID); + resolveState->callbackID = CallbackID_invalid; + } + resolveState->state = Resolve_closed; + ResolveState_decRef(resolveState); +} + +// Split an addrinfo list into two separate lists, one with structures with +// the specified value for the ai_family field, the other with the other +// structures. The order of entries in the resulting lists will remain +// the same as in the original list. +// info - the original list +// family - the family for the first list +// selected - pointer to where the list of selected structures should be +// stored +// selectedEnd - pointer to where the end of 'selected' should be stored +// rest - pointer to where the list of not-selected structures should be +// stored +// restEnd - pointer to where the end of 'rest' should be stored +// +// Yes, it is allowed to modify the ai_next field of addrinfo structures. +// Or at least, it's not disallowed by RFC 3493, and the following +// text from that RFC seems to suggest it should be possible: +// ] The freeaddrinfo() function must support the freeing of arbitrary +// ] sublists of an addrinfo list originally returned by getaddrinfo(). +void +splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd) { + struct addrinfo *selectedFirst; + struct addrinfo **selectedNext; + struct addrinfo *restFirst; + struct addrinfo **restNext; + + selectedNext = &selectedFirst; + restNext = &restFirst; + while (info != NULL) { + if (info->ai_family == family) { + *selectedNext = info; + selectedNext = &(*selectedNext)->ai_next; + } else { + *restNext = info; + restNext = &(*restNext)->ai_next; + } + info = info->ai_next; + } + *selectedNext = NULL; + *restNext = NULL; + + // Fill in the result parameters. + *selected = selectedFirst; + *selectedEnd = selectedNext; + *rest = restFirst; + *restEnd = restNext; +} + + diff --git a/sc2/src/sc2code/libs/network/connect/resolve.h b/sc2/src/sc2code/libs/network/connect/resolve.h new file mode 100644 index 000000000..131976d33 --- /dev/null +++ b/sc2/src/sc2code/libs/network/connect/resolve.h @@ -0,0 +1,107 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CONNECT_RESOLVE_H +#define _CONNECT_RESOLVE_H + + +typedef struct ResolveFlags ResolveFlags; +typedef struct ResolveError ResolveError; +typedef enum { + Resolve_closed, + Resolve_resolving +} ResolveStateState; +typedef struct ResolveState ResolveState; + + +// For addrinfo. +#ifdef _MSC_VER +// Not including because of possible conflicts with files +// including this file. +struct addrinfo; +#else +# include +#endif + +#include "libs/callback.h" +#include "../netmanager/netmanager.h" + + +struct ResolveFlags { + // Nothing yet. + + int dummy; // empty struct declarations are not allowed by C'99. +}; + +struct ResolveError { + int gaiRes; + int err; + // errno value. Only relevant if gaiRes == EAI_SYSTEM. +}; + +typedef void *ResolveCallbackArg; +typedef void (*ResolveCallback)(ResolveState *resolveState, + struct addrinfo *result); + // The receiver of the callback is owner of 'result' and + // should call freeaddrinfo(). +typedef void (*ResolveErrorCallback)(ResolveState *resolveState, + const ResolveError *error); + +#ifdef RESOLVE_INTERNAL +#ifdef _MSC_VER +# include +# include +# include +#else /* !defined(_MSC_VER) */ +# include +#endif /* !defined(_MSC_VER) */ + +struct ResolveState { + RefCount refCount; + + ResolveStateState state; + ResolveFlags flags; + + ResolveCallback callback; + ResolveErrorCallback errorCallback; + void *extra; + + CallbackID callbackID; + ResolveError error; + struct addrinfo *result; +}; +#endif /* RESOLVE_INTERNAL */ + + +void ResolveState_incRef(ResolveState *resolveState); +bool ResolveState_decRef(ResolveState *resolveState); +void ResolveState_setExtra(ResolveState *resolveState, void *extra); +void *ResolveState_getExtra(ResolveState *resolveState); +ResolveState *getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra); +void Resolve_close(ResolveState *resolveState); + +void splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd); + + +#endif /* _CONNECT_RESOLVE_H */ + diff --git a/sc2/src/sc2code/libs/network/netmanager/Makeinfo b/sc2/src/sc2code/libs/network/netmanager/Makeinfo new file mode 100644 index 000000000..0accccfbb --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="ndesc.c netmanager_bsd.c" + diff --git a/sc2/src/sc2code/libs/network/netmanager/ndesc.c b/sc2/src/sc2code/libs/network/netmanager/ndesc.c new file mode 100644 index 000000000..a34712205 --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/ndesc.c @@ -0,0 +1,202 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETDESCRIPTOR_INTERNAL +#include "ndesc.h" + +#include "netmanager.h" +#include "libs/callback.h" + +#include +#include +#include + +#undef DEBUG_NETDESCRIPTOR_REF +#ifdef DEBUG_NETDESCRIPTOR_REF +# include "libs/log.h" +# include +#endif + + +static NetDescriptor * +NetDescriptor_alloc(void) { + return malloc(sizeof (NetDescriptor)); +} + +static void +NetDescriptor_free(NetDescriptor *nd) { + free(nd); +} + +// Sets the ref count to 1. +NetDescriptor * +NetDescriptor_new(Socket *socket, void *extra) { + NetDescriptor *nd; + + nd = NetDescriptor_alloc(); + nd->refCount = 1; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref=1 (%d)\n", + (uintptr_t) nd, nd->refCount); +#endif + + nd->flags.closed = false; + nd->readCallback = NULL; + nd->writeCallback = NULL; + nd->exceptionCallback = NULL; + nd->closeCallback = NULL; + nd->socket = socket; + nd->smd = NULL; + nd->extra = extra; + + if (NetManager_addDesc(nd) == -1) { + int savedErrno = errno; + NetDescriptor_free(nd); + errno = savedErrno; + return NULL; + } + + return nd; +} + +static void +NetDescriptor_delete(NetDescriptor *nd) { + assert(nd->socket == Socket_noSocket); + assert(nd->smd == NULL); + + NetDescriptor_free(nd); +} + +// Called from the callback handler. +static void +NetDescriptor_closeCallback(NetDescriptor *nd) { + if (nd->closeCallback != NULL) { + // The check is necessary because the close callback may have + // been removed before it is triggered. + (*nd->closeCallback)(nd); + } + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_close(NetDescriptor *nd) { + assert(!nd->flags.closed); + assert(nd->socket != Socket_noSocket); + + NetManager_removeDesc(nd); + (void) Socket_close(nd->socket); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + if (nd->closeCallback != NULL) { + // Keep one reference around until the close callback has been + // called. + (void) Callback_add( + (CallbackFunction) NetDescriptor_closeCallback, + (CallbackArg) nd); + } else + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_incRef(NetDescriptor *nd) { + assert(nd->refCount < REFCOUNT_MAX); + nd->refCount++; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref++ (%d)\n", + (uintptr_t) nd, nd->refCount); +#endif +} + +// returns true iff the ref counter has reached 0. +bool +NetDescriptor_decRef(NetDescriptor *nd) { + assert(nd->refCount > 0); + nd->refCount--; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref-- (%d)\n", + (uintptr_t) nd, nd->refCount); +#endif + if (nd->refCount == 0) { + NetDescriptor_delete(nd); + return true; + } + return false; +} + +Socket * +NetDescriptor_getSocket(NetDescriptor *nd) { + return nd->socket; +} + +void +NetDescriptor_setExtra(NetDescriptor *nd, void *extra) { + nd->extra = extra; +} + +void * +NetDescriptor_getExtra(const NetDescriptor *nd) { + return nd->extra; +} + +void +NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback) { + nd->readCallback = callback; + if (!nd->flags.closed) { + if (nd->readCallback != NULL) { + NetManager_activateReadCallback(nd); + } else + NetManager_deactivateReadCallback(nd); + } +} + +void +NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback) { + nd->writeCallback = callback; + if (!nd->flags.closed) { + if (nd->writeCallback != NULL) { + NetManager_activateWriteCallback(nd); + } else + NetManager_deactivateWriteCallback(nd); + } +} + +void +NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback) { + nd->exceptionCallback = callback; + if (!nd->flags.closed) { + if (nd->exceptionCallback != NULL) { + NetManager_activateExceptionCallback(nd); + } else + NetManager_deactivateExceptionCallback(nd); + } +} + +// The close callback is called as a result of a socket being closed, either +// because of a local command or a remote disconnect. +// The close callback will only be scheduled when this happens. The +// callback will not be called until the Callback_process() is called. +void +NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback) { + nd->closeCallback = callback; +} + + diff --git a/sc2/src/sc2code/libs/network/netmanager/ndesc.h b/sc2/src/sc2code/libs/network/netmanager/ndesc.h new file mode 100644 index 000000000..e1f80ee81 --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/ndesc.h @@ -0,0 +1,81 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NDESC_H +#define _NDESC_H + +#include "types.h" + + +typedef struct NetDescriptor NetDescriptor; + +typedef void (*NetDescriptor_ReadCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_WriteCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_ExceptionCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_CloseCallback)(NetDescriptor *nd); + +typedef uint32 RefCount; +#define REFCOUNT_MAX UINT32_MAX + +#include "../socket/socket.h" +#include "netmanager.h" + +#ifdef NETDESCRIPTOR_INTERNAL +// All created NetDescriptors are registered to the NetManager. +// They are unregisted when the NetDescriptor is closed. +// On creation the ref count is set to 1. On close it is decremented by 1. +struct NetDescriptor { + struct { + bool closed: 1; + } flags; + + RefCount refCount; + + NetDescriptor_ReadCallback readCallback; + NetDescriptor_WriteCallback writeCallback; + NetDescriptor_ExceptionCallback exceptionCallback; + NetDescriptor_CloseCallback closeCallback; + + Socket *socket; + SocketManagementData *smd; + + // Extra state-dependant information for the user. + void *extra; +}; +#endif + +NetDescriptor *NetDescriptor_new(Socket *socket, void *extra); +void NetDescriptor_close(NetDescriptor *nd); +void NetDescriptor_incRef(NetDescriptor *nd); +bool NetDescriptor_decRef(NetDescriptor *nd); +Socket *NetDescriptor_getSocket(NetDescriptor *nd); +void NetDescriptor_setExtra(NetDescriptor *nd, void *extra); +void *NetDescriptor_getExtra(const NetDescriptor *nd); +void NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback); +void NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback); +void NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback); +void NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback); + + +#endif /* _NDESC_H */ + + diff --git a/sc2/src/sc2code/libs/network/netmanager/ndindex.ci b/sc2/src/sc2code/libs/network/netmanager/ndindex.ci new file mode 100644 index 000000000..bee4ecd1a --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/ndindex.ci @@ -0,0 +1,103 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netmanager_bsd.c, from where it is #included. +// Only used for BSD sockets. + +// This file provides a mapping of Sockets to NetDescriptors. + + +static NetDescriptor *netDescriptors[FD_SETSIZE]; + // INV: flags.closed is not set for entries in netDescriptors. +static size_t maxND; + // One past the largest used ND in netDescriptors, as used in + // the first argument of select(); + + +static inline void +NDIndex_init(void) { + size_t i; + size_t numND = sizeof (netDescriptors) / sizeof (netDescriptors[0]); + + for (i = 0; i < numND; i++) + netDescriptors[i] = NULL; +} + +static inline void +NDIndex_uninit(void) { + // Nothing to do. +} + +static inline int +NDIndex_registerNDWithSocket(Socket *sock, NetDescriptor *nd) { + if (sock->fd >= FD_SETSIZE) { + errno = EMFILE; + return -1; + } + + netDescriptors[sock->fd] = nd; + + if ((size_t) sock->fd >= maxND) + maxND = (size_t) sock->fd + 1; + + return 0; +} + +static inline void +NDIndex_unregisterNDForSocket(Socket *sock) { + NetDescriptor **last; + + netDescriptors[sock->fd] = NULL; + + last = &netDescriptors[sock->fd]; + + if ((size_t) sock->fd + 1 == maxND) { + do { + maxND--; + if (last == &netDescriptors[0]) + break; + last--; + } while (*last == NULL); + } +} + +static inline NetDescriptor * +NDIndex_getNDForSocket(Socket *sock) { + assert((size_t) sock->fd < maxND); + return netDescriptors[sock->fd]; +} + +static inline NetDescriptor * +NDIndex_getNDForSocketFd(int fd) { + assert((size_t) fd < maxND); + return netDescriptors[fd]; +} + +static inline bool +NDIndex_socketRegistered(Socket *sock) { + return ((size_t) sock->fd < maxND) + && (NDIndex_getNDForSocket(sock) != NULL); +} + +// Get the first argument to be used in select(). +static inline size_t +NDIndex_getSelectNumND(void) { + return maxND; +} + + diff --git a/sc2/src/sc2code/libs/network/netmanager/netmanager.h b/sc2/src/sc2code/libs/network/netmanager/netmanager.h new file mode 100644 index 000000000..41d15562d --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/netmanager.h @@ -0,0 +1,47 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETMANAGER_H +#define _NETMANAGER_H + +#include "types.h" + +#ifdef WIN32 +# include "netmanager_win.h" +#else +# include "netmanager_bsd.h" +#endif + +#include "ndesc.h" + +void NetManager_init(void); +void NetManager_uninit(void); +int NetManager_process(uint32 *timeoutMs); + +// Only for internal use by the NetManager: +int NetManager_addDesc(NetDescriptor *nd); +void NetManager_removeDesc(NetDescriptor *nd); +void NetManager_activateReadCallback(NetDescriptor *nd); +void NetManager_deactivateReadCallback(NetDescriptor *nd); +void NetManager_activateWriteCallback(NetDescriptor *nd); +void NetManager_deactivateWriteCallback(NetDescriptor *nd); +void NetManager_activateExceptionCallback(NetDescriptor *nd); +void NetManager_deactivateExceptionCallback(NetDescriptor *nd); + +#endif /* _NETMANAGER_H */ + diff --git a/sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.c b/sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.c new file mode 100644 index 000000000..a5808889c --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.c @@ -0,0 +1,223 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define SOCKET_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#include "netmanager_bsd.h" +#include "ndesc.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/log.h" + +#include +#include +#include +#include + +#include "netmanager_common.ci" +#include "ndindex.ci" + + +// INV: The following sets only contain sockets present in the netDescriptor +// array. +static fd_set readSet; +static fd_set writeSet; +static fd_set exceptionSet; + + +void +NetManager_init(void) { + NDIndex_init(); + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&exceptionSet); +} + +void +NetManager_uninit(void) { + NDIndex_uninit(); +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + int fd; + assert(nd->socket != Socket_noSocket); + assert(!NDIndex_socketRegistered(nd->socket)); + + if (NDIndex_registerNDWithSocket(nd->socket, nd) == -1) { + // errno is set + return -1; + } + + fd = nd->socket->fd; + if (nd->readCallback != NULL) + FD_SET(fd, &readSet); + if (nd->writeCallback != NULL) + FD_SET(fd, &writeSet); + if (nd->exceptionCallback != NULL) + FD_SET(fd, &exceptionSet); + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + int fd; + + assert(nd->socket != Socket_noSocket); + assert(NDIndex_getNDForSocket(nd->socket) == nd); + + fd = nd->socket->fd; + FD_CLR(fd, &readSet); + FD_CLR(fd, &writeSet); + FD_CLR(fd, &exceptionSet); + + NDIndex_unregisterNDForSocket(nd->socket); +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &readSet); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &readSet); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &writeSet); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &writeSet); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &exceptionSet); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &exceptionSet); +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + struct timeval timeout; + size_t i; + int selectResult; + fd_set newReadSet; + fd_set newWriteSet; + fd_set newExceptionSet; + bool bitSet; + + timeout.tv_sec = *timeoutMs / 1000; + timeout.tv_usec = (*timeoutMs % 1000) * 1000; + + // Structure assignment: + newReadSet = readSet; + newWriteSet = writeSet; + newExceptionSet = exceptionSet; + + do { + selectResult = select(NDIndex_getSelectNumND(), + &newReadSet, &newWriteSet, &newExceptionSet, &timeout); + // BUG: If select() is restarted because of EINTR, the timeout + // may start over. (Linux changes 'timeout' to the time left, + // but most other platforms don't.) + } while (selectResult == -1 && errno == EINTR); + if (selectResult == -1) { + int savedErrno = errno; + log_add(log_Error, "select() failed: %s.\n", strerror(errno)); + errno = savedErrno; + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return -1; + } + + for (i = 0; i < maxND; i++) { + NetDescriptor *nd; + + if (selectResult == 0) { + // No more bits set in the fd_sets + break; + } + + nd = NDIndex_getNDForSocketFd(i); + if (nd == NULL) + continue; + + bitSet = false; + // Is one of the bits in the fd_sets set? + + // A callback may cause a NetDescriptor to be closed. The deletion + // of the structure will be scheduled, but will still be + // available at least until this function returns. + + if (FD_ISSET(i, &newExceptionSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doExceptionCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newWriteSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doWriteCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newReadSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doReadCallback(nd); + if (closed) + goto next; + } + +next: + if (bitSet) + selectResult--; + } + + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return 0; +} + + + diff --git a/sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.h b/sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.h new file mode 100644 index 000000000..27a51bfbd --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/netmanager_bsd.h @@ -0,0 +1,26 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETMANAGER_BSD_H +#define _NETMANAGER_BSD_H + +typedef struct SocketManagementDataBsd SocketManagementDataBsd; +typedef SocketManagementDataBsd SocketManagementData; + +#endif /* _NETMANAGER_BSD_H */ + diff --git a/sc2/src/sc2code/libs/network/netmanager/netmanager_common.ci b/sc2/src/sc2code/libs/network/netmanager/netmanager_common.ci new file mode 100644 index 000000000..058e73980 --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/netmanager_common.ci @@ -0,0 +1,58 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netmanager_bsd.ci and netmanager_win.ci, +// from where it is #included. + +static bool +NetManager_doReadCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->readCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->readCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doWriteCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->writeCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->writeCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doExceptionCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->exceptionCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->exceptionCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + + diff --git a/sc2/src/sc2code/libs/network/netmanager/netmanager_win.c b/sc2/src/sc2code/libs/network/netmanager/netmanager_win.c new file mode 100644 index 000000000..caa1e0cb6 --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/netmanager_win.c @@ -0,0 +1,460 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define NETMANAGER_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#define SOCKET_INTERNAL +#include "netmanager_win.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include + +#include "netmanager_common.ci" + +int closeWSAEvent(WSAEVENT event); + + +// The elements of the following arrays with the same index belong to +// eachother. +#define MAX_SOCKETS WSA_MAXIMUM_WAIT_EVENTS + // We cannot have more sockets than we can have events, as + // all may need an event at some point. +static NetDescriptor *netDescriptors[MAX_SOCKETS]; +static WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS]; +static size_t numSockets; +static size_t numActiveEvents; +// For each NetDescriptor registered with the NetManager, an event +// is created. Only the first numActiveEvents will be processed though. +// Inv: numActiveEvents <= numSockets +// Inv: for all i: 0 <= i < numActiveEvents: events[i] is active +// (events[i] being active also means netDescriptor[i]->smd->eventMask +// != 0) +// Inv: for all i: 0 <= i < numSockets: netDescriptor[i]->smd->index == i + +void +NetManager_init(void) { + numActiveEvents = 0; +} + +void +NetManager_uninit(void) { + assert(numActiveEvents == 0); +} + +static inline SocketManagementDataWin * +SocketManagementData_alloc(void) { + return malloc(sizeof (SocketManagementDataWin)); +} + +static inline void +SocketManagementData_free(SocketManagementDataWin *smd) { + free(smd); +} + +// XXX: This function should be moved to some file with generic network +// functions. +int +closeWSAEvent(WSAEVENT event) { + for (;;) { + int error; + + if (WSACloseEvent(event)) + break; + + error = WSAGetLastError(); + if (error != WSAEINPROGRESS) { + log_add(log_Error, + "WSACloseEvent() failed with error code %d.\n", error); + errno = winsockErrorToErrno(error); + return -1; + } + } + return 0; +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + long eventMask = 0; + WSAEVENT event; + + if (numSockets >= WSA_MAXIMUM_WAIT_EVENTS) { + errno = EMFILE; + return -1; + } + + if (nd->readCallback != NULL) + eventMask |= FD_READ | FD_ACCEPT; + + if (nd->writeCallback != NULL) + eventMask |= FD_WRITE | FD_CONNECT; + + if (nd->exceptionCallback != NULL) + eventMask |= FD_OOB; + + eventMask |= FD_CLOSE; + + event = WSACreateEvent(); + if (event == WSA_INVALID_EVENT) { + errno = getWinsockErrno(); + return -1; + } + + nd->smd = SocketManagementData_alloc(); + if (eventMask != 0) { + // XXX: This guard is now always true, because of FD_CLOSE. + // This means that numActiveEvents will always be equal to + // numEvents. + // Once I'm convinced this is the right way to go, + // I can remove some unnecessary code. + if (WSAEventSelect(nd->socket->sock, event, eventMask) == + SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(event); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.\n", + strerror(errno)); + explode(); + } + SocketManagementData_free(nd->smd); + errno = savedErrno; + return -1; + } + + // Move existing socket for which there exists no event, so + // so that all sockets for which there exists an event are at + // the front of the array of netdescriptors. + if (numActiveEvents < numSockets) { + netDescriptors[numSockets] = netDescriptors[numActiveEvents]; + netDescriptors[numSockets]->smd->index = numSockets; + } + + nd->smd->index = numActiveEvents; + numActiveEvents++; + } else { + nd->smd->index = numSockets; + } + nd->smd->eventMask = eventMask; + + netDescriptors[nd->smd->index] = nd; + events[nd->smd->index] = event; + numSockets++; + + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + assert(nd->smd != NULL); + assert(nd->smd->index < numSockets); + assert(nd == netDescriptors[nd->smd->index]); + + { + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) + explode(); + } + + if (nd->smd->index < numActiveEvents) { + size_t index = nd->smd->index; + if (index + 1 != numActiveEvents) { + // Keep the list of active events consecutive by filling + // the new hole with the last active event. + events[index] = events[numActiveEvents - 1]; + netDescriptors[index] = netDescriptors[numActiveEvents - 1]; + netDescriptors[index]->smd->index = index; + } + numActiveEvents--; + } + + SocketManagementData_free(nd->smd); + nd->smd = NULL; + + numSockets--; +} + +static void +swapSockets(int index1, int index2) { + NetDescriptor *tempNd; + WSAEVENT tempEvent; + + tempNd = netDescriptors[index2]; + tempEvent = events[index2]; + + netDescriptors[index2] = netDescriptors[index1]; + events[index2] = events[index1]; + netDescriptors[index2]->smd->index = index2; + + netDescriptors[index1] = tempNd; + events[index1] = tempEvent; + netDescriptors[index1]->smd->index = index1; +} + +static int +NetManager_updateEvent(NetDescriptor *nd) { + assert(nd == netDescriptors[nd->smd->index]); + + if (WSAEventSelect(nd->socket->sock, + events[nd->smd->index], nd->smd->eventMask) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.\n", + strerror(errno)); + explode(); + } + errno = savedErrno; + return -1; + } + + if (nd->smd->eventMask != 0) { + // There are some events that we are interested in. + if (nd->smd->index >= numActiveEvents) { + // Event was not yet active. + if (nd->smd->index != numActiveEvents) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents); + } + numActiveEvents++; + } + } else { + // There are no events that we are interested in. + if (nd->smd->index < numActiveEvents) { + // Event was active. + if (nd->smd->index != numActiveEvents - 1) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents - 1); + } + } + numActiveEvents--; + } + + return 0; +} + +static void +activateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask |= eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.\n", + strerror(errno)); + explode(); + // TODO: better error handling. + } + } +} + +static void +deactivateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask &= ~eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.\n", + strerror(errno)); + explode(); + // TODO: better error handling + } + } +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_WRITE | FD_CONNECT); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_WRITE | FD_CONNECT); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_OOB); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_OOB); +} + +static inline int +NetManager_processEvent(size_t index) { + WSANETWORKEVENTS networkEvents; + int enumRes; + + enumRes = WSAEnumNetworkEvents(netDescriptors[index]->socket->sock, + events[index], &networkEvents); + if (enumRes == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + if (networkEvents.lNetworkEvents & FD_READ) { + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a recv() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_WRITE) { + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a send() and will get the error then. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_OOB) { + bool closed; + if (networkEvents.iErrorCode[FD_OOB_BIT] != 0) { + // No special handling is required; the callback function + // will get the error then when it tries to do a recv(). + } + + closed = NetManager_doExceptionCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_ACCEPT) { + // There is no specific accept callback (because the BSD sockets + // don't work with specific notification for accept); we use + // the read callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do an accept() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_CONNECT) { + // There is no specific connect callback (because the BSD sockets + // don't work with specific notification for connect); we use + // the write callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // should do getsockopt() with SO_ERROR to get the error. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_CLOSE) { + // The close event is handled last, in case there was still + // data in the buffers which could be processed. + NetDescriptor_close(netDescriptors[index]); + goto closed; + } + +closed: /* No special actions required for now. */ + + return 0; +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + DWORD timeoutTemp; + DWORD waitResult; + DWORD startEvent; + + timeoutTemp = (DWORD) *timeoutMs; + + // WSAWaitForMultipleEvents only reports events for one socket at a + // time. In order to have each socket checked once, we call it + // again after it has reported an event, but passing only the + // events not yet processed. The second time, the timeout will be set + // to 0, so it won't wait. + startEvent = 0; + while (startEvent < numActiveEvents) { + waitResult = WSAWaitForMultipleEvents(numActiveEvents - startEvent, + &events[startEvent], FALSE, timeoutTemp, FALSE); + + if (waitResult == WSA_WAIT_IO_COMPLETION) + continue; + + if (waitResult == WSA_WAIT_TIMEOUT) { + // No events waiting. + *timeoutMs = 0; + return 0; + } + + if (waitResult == WSA_WAIT_FAILED) { + errno = getWinsockErrno(); + *timeoutMs = timeoutTemp; + return -1; + } + + { + DWORD eventIndex = waitResult - WSA_WAIT_EVENT_0; + if (NetManager_processEvent((size_t) eventIndex) == -1) { + // errno is set + *timeoutMs = timeoutTemp; + return -1; + } + + // Check the rest of the sockets, but don't wait anymore. + startEvent += eventIndex + 1; + timeoutTemp = 0; + } + } + + *timeoutMs = timeoutTemp; + return 0; +} + + diff --git a/sc2/src/sc2code/libs/network/netmanager/netmanager_win.h b/sc2/src/sc2code/libs/network/netmanager/netmanager_win.h new file mode 100644 index 000000000..64edbc7da --- /dev/null +++ b/sc2/src/sc2code/libs/network/netmanager/netmanager_win.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETMANAGER_WIN_H +#define _NETMANAGER_WIN_H + +typedef struct SocketManagementDataWin SocketManagementDataWin; +typedef SocketManagementDataWin SocketManagementData; + +#ifdef NETMANAGER_INTERNAL +struct SocketManagementDataWin { + size_t index; + long eventMask; +}; +#endif /* NETMANAGER_INTERNAL */ + + +#endif /* _NETMANAGER_WIN_H */ + + diff --git a/sc2/src/sc2code/libs/network/netport.c b/sc2/src/sc2code/libs/network/netport.c new file mode 100644 index 000000000..f205bb35b --- /dev/null +++ b/sc2/src/sc2code/libs/network/netport.c @@ -0,0 +1,91 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "netport.h" + +#ifdef _MSC_VER +# include + +int +winsockErrorToErrno(int winsockError) { + switch (winsockError) { + case WSAEINTR: return EINTR; + case WSAEACCES: return EACCES; + case WSAEFAULT: return EFAULT; + case WSAEINVAL: return EINVAL; + case WSAEMFILE: return EMFILE; + case WSAEWOULDBLOCK: return EWOULDBLOCK; + case WSAEINPROGRESS: return EINPROGRESS; + case WSAEALREADY: return EALREADY; + case WSAENOTSOCK: return ENOTSOCK; + case WSAEDESTADDRREQ: return EDESTADDRREQ; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAEPROTOTYPE: return EPROTOTYPE; + case WSAENOPROTOOPT: return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; + case WSAESOCKTNOSUPPORT: return ESOCKTNOSUPPORT; + case WSAEOPNOTSUPP: return EOPNOTSUPP; + case WSAEPFNOSUPPORT: return EPFNOSUPPORT; + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAENETRESET: return ENETRESET; + case WSAECONNABORTED: return ECONNABORTED; + case WSAECONNRESET: return ECONNRESET; + case WSAENOBUFS: return ENOBUFS; + case WSAEISCONN: return EISCONN; + case WSAENOTCONN: return ENOTCONN; + case WSAESHUTDOWN: return ESHUTDOWN; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAEHOSTDOWN: return EHOSTDOWN; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAEPROCLIM: return EPROCLIM; + case WSASYSNOTREADY: return ENOSYS; + case WSAVERNOTSUPPORTED: return ENOSYS; + case WSANOTINITIALISED: return ENOSYS; + case WSAEDISCON: return ECONNRESET; + case WSATYPE_NOT_FOUND: return ENODATA; + case WSAHOST_NOT_FOUND: return ENODATA; + case WSATRY_AGAIN: return EAGAIN; + case WSANO_RECOVERY: return EIO; + case WSANO_DATA: return ENODATA; + case WSA_INVALID_HANDLE: return EBADF; + case WSA_INVALID_PARAMETER: return EINVAL; + case WSA_IO_INCOMPLETE: return EAGAIN; + case WSA_IO_PENDING: return EINPROGRESS; + case WSA_NOT_ENOUGH_MEMORY: return ENOMEM; + case WSA_OPERATION_ABORTED: return EINTR; + case WSAEINVALIDPROCTABLE: return ENOSYS; + case WSAEINVALIDPROVIDER: return ENOSYS; + case WSAEPROVIDERFAILEDINIT: return ENOSYS; + case WSASYSCALLFAILURE: return EIO; + default: return EIO; + } +} + +int +getWinsockErrno(void) { + return winsockErrorToErrno(WSAGetLastError()); +} +#endif /* defined (_MSC_VER) */ + diff --git a/sc2/src/sc2code/libs/network/netport.h b/sc2/src/sc2code/libs/network/netport.h new file mode 100644 index 000000000..86ed6a1db --- /dev/null +++ b/sc2/src/sc2code/libs/network/netport.h @@ -0,0 +1,31 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETPORT_H +#define _NETPORT_H + +#if defined (_MSC_VER) +int winsockErrorToErrno(int winsockError); +int getWinsockErrno(void); +# define EAI_SYSTEM 0x02000001 + // Any value will do that doesn't conflict with an existing value. +#endif + +#endif /* _NETPORT_H */ + + diff --git a/sc2/src/sc2code/libs/network/network.h b/sc2/src/sc2code/libs/network/network.h new file mode 100644 index 000000000..e183199ce --- /dev/null +++ b/sc2/src/sc2code/libs/network/network.h @@ -0,0 +1,27 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETWORK_H +#define _NETWORK_H + +void Network_init(void); +void Network_uninit(void); + +#endif /* _NETWORK_H */ + + diff --git a/sc2/src/sc2code/libs/network/network_bsd.c b/sc2/src/sc2code/libs/network/network_bsd.c new file mode 100644 index 000000000..a213c4e28 --- /dev/null +++ b/sc2/src/sc2code/libs/network/network_bsd.c @@ -0,0 +1,30 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "network.h" + +void +Network_init(void) { + // Nothing to do. +} + +void +Network_uninit(void) { + // Nothing to do. +} + diff --git a/sc2/src/sc2code/libs/network/network_win.c b/sc2/src/sc2code/libs/network/network_win.c new file mode 100644 index 000000000..e54394678 --- /dev/null +++ b/sc2/src/sc2code/libs/network/network_win.c @@ -0,0 +1,75 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netport.h" + +#include "network.h" + +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include + +void +Network_init(void) { + WSADATA data; + int startupResult; + WORD requestVersion = MAKEWORD(2, 2); + + startupResult = WSAStartup(requestVersion, &data); + if (startupResult != 0) { + int savedErrno = winsockErrorToErrno(startupResult); + log_add(log_Fatal, "WSAStartup failed.\n"); + errno = savedErrno; + explode(); + } + +#ifdef DEBUG + log_add(log_Debug, "Winsock version %d.%d found: \"%s\".\n", + LOBYTE(data.wHighVersion), HIBYTE(data.wHighVersion), + data.szDescription); + log_add(log_Debug, "Requesting to use Winsock version %d.%d, got " + "version %d.%d.\n", + LOBYTE(requestVersion), HIBYTE(requestVersion), + LOBYTE(data.wVersion), HIBYTE(data.wVersion)); +#endif + if (data.wVersion != requestVersion) { + log_add(log_Fatal, "Winsock version %d.%d presented, requested " + "%d.%d.\n", LOBYTE(data.wVersion), HIBYTE(data.wVersion), + LOBYTE(requestVersion), HIBYTE(requestVersion)); + (void) WSACleanup(); + // Ignoring errors; we're going to abort anyhow. + explode(); + } +} + +void +Network_uninit(void) { + int cleanupResult; + + cleanupResult = WSACleanup(); + if (cleanupResult == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Fatal, "WSACleanup failed.\n"); + errno = savedErrno; + explode(); + } +} + + diff --git a/sc2/src/sc2code/libs/network/socket/Makeinfo b/sc2/src/sc2code/libs/network/socket/Makeinfo new file mode 100644 index 000000000..bc23703c3 --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="socket_bsd.c socket.c" + diff --git a/sc2/src/sc2code/libs/network/socket/socket.c b/sc2/src/sc2code/libs/network/socket/socket.c new file mode 100644 index 000000000..deee918ad --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/socket.c @@ -0,0 +1,59 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define SOCKET_INTERNAL +#include "socket.h" + +#ifdef _MSC_VER +# include +#else +# include +# include +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +const int protocolFamilyTranslation[] = { + /* .[PF_unspec] = */ PF_UNSPEC, + /* .[PF_inet] = */ PF_INET, + /* .[PF_inet6] = */ PF_INET6, +}; + +const int protocolTranslation[] = { + /* .[IPProto_tcp] = */ IPPROTO_TCP, + /* .[IPProto_udp] = */ IPPROTO_UDP, +}; + +const int socketTypeTranslation[] = { + /* .[Sock_stream] = */ SOCK_STREAM, + /* .[Sock_dgram] = */ SOCK_DGRAM, +}; + + +Socket * +Socket_open(ProtocolFamily domain, SocketType type, Protocol protocol) { + return Socket_openNative(protocolFamilyTranslation[domain], + socketTypeTranslation[type], protocolTranslation[protocol]); +} + + +//////////////////////////////////////////////////////////////////////////// + + diff --git a/sc2/src/sc2code/libs/network/socket/socket.h b/sc2/src/sc2code/libs/network/socket/socket.h new file mode 100644 index 000000000..8b796615e --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/socket.h @@ -0,0 +1,93 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _UQM_SOCKET_H +#define _UQM_SOCKET_H + +typedef struct Socket Socket; +#define Socket_noSocket ((Socket *) NULL) + +#include "port.h" + +#ifdef WIN32 +# include "socket_win.h" +#else +# include "socket_bsd.h" +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +// Defining our own types for protocol families and protocols instead of +// using the system defines, so that the layer using this API does not have +// to have anything to do with the system layer. + +typedef enum { + PF_unspec, + PF_inet, + PF_inet6, +} ProtocolFamily; +typedef ProtocolFamily AddressFamily; + +typedef enum { + IPProto_tcp, + IPProto_udp, +} Protocol; + +typedef enum { + Sock_stream, + Sock_dgram, +} SocketType; + +#ifdef SOCKET_INTERNAL +extern const int protocolFamilyTranslation[]; +#define addressFamilyTranslation protocolFamilyTranslation; +extern const int protocolTranslation[]; +extern const int socketTypeTranslation[]; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +Socket *Socket_open(ProtocolFamily domain, SocketType type, + Protocol protocol); +#ifdef SOCKET_INTERNAL +Socket *Socket_openNative(int domain, int type, int protocol); +#endif +int Socket_close(Socket *sock); + +int Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_bind(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_listen(Socket *sock, int backlog); +Socket *Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen); +ssize_t Socket_send(Socket *sock, const void *buf, size_t len, int flags); +ssize_t Socket_recv(Socket *sock, void *buf, size_t len, int flags); + +int Socket_setNonBlocking(Socket *sock); +int Socket_setReuseAddr(Socket *sock); +int Socket_setInteractive(Socket *sock); +int Socket_setInlineOOB(Socket *sock); +int Socket_setKeepAlive(Socket *sock); +int Socket_getError(Socket *sock, int *err); + +#endif /* _UQM_SOCKET_H */ + diff --git a/sc2/src/sc2code/libs/network/socket/socket_bsd.c b/sc2/src/sc2code/libs/network/socket/socket_bsd.c new file mode 100644 index 000000000..13aa040db --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/socket_bsd.c @@ -0,0 +1,257 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for BSD sockets. + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + int fd; + + fd = socket(domain, type, protocol); + if (fd == -1) { + // errno is set + return Socket_noSocket; + } + + result = Socket_alloc(); + result->fd = fd; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = close(sock->fd); + if (closeResult == 0) { + Socket_free(sock); + return 0; + } + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->fd, addr, addrLen); + } while (connectResult == -1 && errno == EINTR); + + return connectResult; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + return bind(sock->fd, addr, addrLen); +} + +int +Socket_listen(Socket *sock, int backlog) { + return listen(sock->fd, backlog); +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + int acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->fd, addr, &tempAddrLen); + if (acceptResult != -1) { + Socket *result = Socket_alloc(); + result->fd = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + return send(sock->fd, buf, len, flags); +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + return recv(sock->fd, buf, len, flags); +} + +int +Socket_setNonBlocking(Socket *sock) { + int flag = 1; + + if (ioctl(sock->fd, FIONBIO, &flag) == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting non-block mode on socket failed: %s.\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting socket reuse failed: %s.\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof flag) + == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.\n", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// 'tos' should be IPTOS_LOWDELAY, IPTOS_THROUGHPUT, IPTOS_THROUGHPUT, +// IPTOS_RELIABILITY, or IPTOS_MINCOST. +int +Socket_setTOS(Socket *sock, int tos) { + if (setsockopt(sock->fd, SOL_IP, IP_TOS, &tos, sizeof tos) == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Setting socket type-of-service failed: %s.\n", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_OOBINLINE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting inline OOB on socket failed: %s\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_TCP, SO_KEEPALIVE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting keep-alive on socket failed: %s\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + socklen_t errLen = sizeof(*err); + + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, err, &errLen) == -1) { + // errno is set + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/sc2/src/sc2code/libs/network/socket/socket_bsd.h b/sc2/src/sc2code/libs/network/socket/socket_bsd.h new file mode 100644 index 000000000..149cdd6c0 --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/socket_bsd.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SOCKET_BSD_H +#define _SOCKET_BSD_H + +#include "types.h" +#include + +#ifdef SOCKET_INTERNAL +struct Socket { + int fd; +}; +#endif /* SOCKET_INTERNAL */ + + +#endif /* _SOCKET_BSD_H */ + diff --git a/sc2/src/sc2code/libs/network/socket/socket_win.c b/sc2/src/sc2code/libs/network/socket/socket_win.c new file mode 100644 index 000000000..92b4f9981 --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/socket_win.c @@ -0,0 +1,278 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for Winsock sockets. + +#define PORT_WANT_ERRNO +#include "port.h" + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include +#include +#include + + +Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + SOCKET sock; + + sock = socket(domain, type, protocol); + if (sock == INVALID_SOCKET) { + errno = getWinsockErrno(); + return Socket_noSocket; + } + + result = Socket_alloc(); + result->sock = sock; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = closesocket(sock->sock); + if (closeResult != SOCKET_ERROR) { + Socket_free(sock); + return 0; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->sock, addr, addrLen); + if (connectResult == 0) + return 0; + + errno = getWinsockErrno(); + } while (errno == EINTR); + + return -1; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + int bindResult; + + bindResult = bind(sock->sock, addr, addrLen); + if (bindResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +int +Socket_listen(Socket *sock, int backlog) { + int listenResult; + + listenResult = listen(sock->sock, backlog); + if (listenResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + SOCKET acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->sock, addr, &tempAddrLen); + if (acceptResult != INVALID_SOCKET) { + Socket *result = Socket_alloc(); + result->sock = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + int sendResult; + + sendResult = send(sock->sock, buf, len, flags); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + int recvResult; + + recvResult = recv(sock->sock, buf, len, flags); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +int +Socket_setNonBlocking(Socket *sock) { + unsigned long flag = 1; + + if (ioctlsocket(sock->sock, FIONBIO, &flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting non-block mode on socket failed: %s.\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting socket reuse failed: %s.\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, TCP_NODELAY, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { +#ifdef DEBUG + int savedErrno = getWinsockErrno(); + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.\n", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + +#if 0 + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } +#endif + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_OOBINLINE, (const char *) &flag, + sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting inline OOB on socket failed: %s\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, SO_KEEPALIVE, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting keep-alive on socket failed: %s\n", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + int errLen = sizeof(*err); + + if (getsockopt(sock->sock, SOL_SOCKET, SO_ERROR, (char *) err, &errLen) + == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/sc2/src/sc2code/libs/network/socket/socket_win.h b/sc2/src/sc2code/libs/network/socket/socket_win.h new file mode 100644 index 000000000..305cc1333 --- /dev/null +++ b/sc2/src/sc2code/libs/network/socket/socket_win.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SOCKET_WIN_H +#define _SOCKET_WIN_H + +#ifdef SOCKET_INTERNAL +#include +struct Socket { + SOCKET sock; +}; +#endif /* SOCKET_INTERNAL */ + +typedef int socklen_t; + +#endif /* _SOCKET_WIN_H */ + + diff --git a/sc2/src/sc2code/melee.c b/sc2/src/sc2code/melee.c index d45ddbffe..546fbfdc8 100644 --- a/sc2/src/sc2code/melee.c +++ b/sc2/src/sc2code/melee.c @@ -20,6 +20,8 @@ #include "build.h" #include "colors.h" +#include "comm.h" + // for getLineWithinWidth #include "controls.h" #include "file.h" #include "fmv.h" @@ -27,6 +29,11 @@ #include "globdata.h" #include "intel.h" #include "nameref.h" +#ifdef NETPLAY +# include "netplay/netconnection.h" +# include "netplay/netmelee.h" +# include "netplay/notify.h" +#endif #include "options.h" #include "races.h" #include "resinst.h" @@ -46,9 +53,12 @@ #include #include #include +#include static void DrawMeleeShipStrings (PMELEE_STATE pMS, BYTE NewStarShip); +static void StartMelee (PMELEE_STATE pMS); +static ssize_t numPlayersReady (void); static int flash_selection_func (void *data); @@ -86,6 +96,12 @@ enum #define RACE_INFO_ORIGIN_Y (SHIP_INFO_HEIGHT + 6) #define RACE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - RACE_INFO_ORIGIN_Y) +#define MELEE_STATUS_X_OFFS 1 +#define MELEE_STATUS_Y_OFFS 201 +#define MELEE_STATUS_WIDTH (NUM_MELEE_COLUMNS * \ + (MELEE_BOX_WIDTH + MELEE_BOX_SPACE)) +#define MELEE_STATUS_HEIGHT 38 + #define MELEE_BACKGROUND_COLOR \ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04) #define MELEE_TITLE_COLOR \ @@ -108,28 +124,52 @@ enum #define HI_STATE_BACKGROUND_COLOR \ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09) +// XXX: The following entries are unused: #define LIST_INFO_BACKGROUND_COLOR \ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05) #define LIST_INFO_TITLE_COLOR \ - BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F) + WHITE_COLOR #define LIST_INFO_TEXT_COLOR \ - BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x07) + LT_GRAY_COLOR #define LIST_INFO_CURENTRY_TEXT_COLOR \ - BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F) + WHITE_COLOR #define HI_LIST_INFO_TEXT_COLOR \ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04) #define HI_LIST_INFO_BACKGROUND_COLOR \ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x1F), 0x0D) #define TEAM_NAME_TEXT_COLOR \ - BUILD_COLOR (MAKE_RGB15 (15, 16, 27), 0x00) + BUILD_COLOR (MAKE_RGB15 (0x0F, 0x10, 0x1B), 0x00) #define TEAM_NAME_EDIT_TEXT_COLOR \ - BUILD_COLOR (MAKE_RGB15 (23, 24, 29), 0x00) + BUILD_COLOR (MAKE_RGB15 (0x17, 0x18, 0x1D), 0x00) #define TEAM_NAME_EDIT_RECT_COLOR \ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05) #define TEAM_NAME_EDIT_CURS_COLOR \ WHITE_COLOR +#define PICKSHIP_TEAM_NAME_TEXT_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09) +#define PICKSHIP_TEAM_START_VALUE_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x04, 0x05, 0x1F), 0x4B) + +#define SHIPBOX_TOPLEFT_COLOR_NORMAL \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x09), 0x56) +#define SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0E), 0x54) +#define SHIPBOX_INTERIOR_COLOR_NORMAL \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0C), 0x55) + +#define SHIPBOX_TOPLEFT_COLOR_HILITE \ + BUILD_COLOR (MAKE_RGB15 (0x07, 0x00, 0x0C), 0x3E) +#define SHIPBOX_BOTTOMRIGHT_COLOR_HILITE \ + BUILD_COLOR (MAKE_RGB15 (0x0C, 0x00, 0x14), 0x3C) +#define SHIPBOX_INTERIOR_COLOR_HILITE \ + BUILD_COLOR (MAKE_RGB15 (0x0A, 0x00, 0x11), 0x3D) + +#define MELEE_STATUS_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02) + + FRAME PickMeleeFrame; static FRAME MeleeFrame; static FRAME BuildPickFrame; @@ -140,6 +180,7 @@ PMELEE_STATE volatile pMeleeState; static BOOLEAN DoMelee (PMELEE_STATE pMS); static BOOLEAN DoEdit (PMELEE_STATE pMS); static BOOLEAN DoPickShip (PMELEE_STATE pMS); +static BOOLEAN DoConfirmSettings (PMELEE_STATE pMS); #define DTSHS_NORMAL 0 #define DTSHS_EDIT 1 @@ -147,7 +188,8 @@ static BOOLEAN DoPickShip (PMELEE_STATE pMS); #define DTSHS_REPAIR 4 #define DTSHS_BLOCKCUR 8 -static BOOLEAN DrawTeamString (PMELEE_STATE pMS, COUNT HiLiteState); +static BOOLEAN DrawTeamString (PMELEE_STATE pMS, COUNT side, + COUNT HiLiteState); static BOOLEAN DoLoadTeam (PMELEE_STATE pMS); static void DrawFileStrings (PMELEE_STATE pMS, int HiLiteState); @@ -170,7 +212,7 @@ GetShipBox (PRECT pRect, COUNT side, COUNT row, COUNT col) + (col * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE)); pRect->corner.y = MELEE_Y_OFFS + (side * (MELEE_Y_OFFS + MELEE_BOX_SPACE - + (NUM_MELEE_ROWS * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE)))) + + (NUM_MELEE_ROWS * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE)))) + (row * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE)); pRect->extent.width = MELEE_BOX_WIDTH; pRect->extent.height = MELEE_BOX_HEIGHT; @@ -195,37 +237,33 @@ GetShipColumn (int index) } static void -DrawShipBox (PMELEE_STATE pMS, BOOLEAN HiLite) +DrawShipBox (COUNT side, COUNT row, COUNT col, BYTE ship, BOOLEAN HiLite) { RECT r; - BYTE StarShip; - FleetShipIndex index; - GetShipBox (&r, pMS->side, pMS->row, pMS->col); + GetShipBox (&r, side, row, col); BatchGraphics (); - index = GetShipIndex (pMS->row, pMS->col); - StarShip = pMS->TeamImage[pMS->side].ShipList[index]; if (HiLite) DrawStarConBox (&r, 1, - BUILD_COLOR (MAKE_RGB15 (0x7, 0x00, 0xC), 0x3E), - BUILD_COLOR (MAKE_RGB15 (0xC, 0x00, 0x14), 0x3C), - (BOOLEAN)(StarShip != MELEE_NONE), - BUILD_COLOR (MAKE_RGB15 (0xA, 0x00, 0x11), 0x3D)); + SHIPBOX_TOPLEFT_COLOR_HILITE, + SHIPBOX_BOTTOMRIGHT_COLOR_HILITE, + (BOOLEAN)(ship != MELEE_NONE), + SHIPBOX_INTERIOR_COLOR_HILITE); else DrawStarConBox (&r, 1, - BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x9), 0x56), - BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0xE), 0x54), - (BOOLEAN)(StarShip != MELEE_NONE), - BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0xC), 0x55)); + SHIPBOX_TOPLEFT_COLOR_NORMAL, + SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL, + (BOOLEAN)(ship != MELEE_NONE), + SHIPBOX_INTERIOR_COLOR_NORMAL); - if (StarShip != MELEE_NONE) + if (ship != MELEE_NONE) { STAMP s; HSTARSHIP hStarShip; STARSHIPPTR StarShipPtr; - hStarShip = GetStarShipFromIndex (&master_q, StarShip); + hStarShip = GetStarShipFromIndex (&master_q, ship); StarShipPtr = LockStarShip (&master_q, hStarShip); s.origin.x = r.corner.x + (r.extent.width >> 1); s.origin.y = r.corner.y + (r.extent.height >> 1); @@ -237,11 +275,26 @@ DrawShipBox (PMELEE_STATE pMS, BOOLEAN HiLite) UnbatchGraphics (); } +static void +DrawShipBoxCurrent (PMELEE_STATE pMS, BOOLEAN HiLite) +{ + FleetShipIndex index = GetShipIndex (pMS->row, pMS->col); + BYTE ship = pMS->TeamImage[pMS->side].ShipList[index]; + DrawShipBox (pMS->side, pMS->row, pMS->col, ship, HiLite); +} + +// Draw an image for one of the control method selection buttons. static void DrawControls (COUNT which_side, BOOLEAN HiLite) { COUNT which_icon; + if (PlayerControl[which_side] & NETWORK_CONTROL) + { + DrawMeleeIcon (31 + (HiLite ? 1 : 0) + 2 * (1 - which_side)); + return; + } + if (PlayerControl[which_side] & HUMAN_CONTROL) which_icon = 0; else @@ -288,7 +341,8 @@ DrawPickFrame (PMELEE_STATE pMS) r.corner.y = -(ship_r.corner.y + (ship_r.extent.height - r.extent.height)); SetFrameHot (s.frame, MAKE_HOT_SPOT (r.corner.x, r.corner.y)); - s.origin.x = s.origin.y = 0; + s.origin.x = 0; + s.origin.y = 0; DrawStamp (&s); UnlockMutex (GraphicsLock); @@ -315,37 +369,32 @@ RepairMeleeFrame (PRECT pRect) OldContext = SetContext (SpaceContext); GetContextClipRect (&OldRect); SetContextClipRect (&r); - SetFrameHot (Screen, MAKE_HOT_SPOT (r.corner.x - SAFE_X, r.corner.y - SAFE_Y)); + SetFrameHot (Screen, + MAKE_HOT_SPOT (r.corner.x - SAFE_X, r.corner.y - SAFE_Y)); BatchGraphics (); - DrawMeleeIcon (0); - DrawMeleeIcon (26); + DrawMeleeIcon (0); /* Entire melee screen */ + DrawMeleeIcon (26); /* "Battle!" (highlighted) */ { - COUNT old_side, old_row, old_col; + COUNT side; + COUNT row; + COUNT col; - old_side = pMeleeState->side; - old_row = pMeleeState->row; - old_col = pMeleeState->col; - for (pMeleeState->side = 0; - pMeleeState->side < NUM_SIDES; - ++pMeleeState->side) + for (side = 0; side < NUM_SIDES; side++) { - DrawControls (pMeleeState->side, FALSE); - for (pMeleeState->row = 0; - pMeleeState->row < NUM_MELEE_ROWS; - ++pMeleeState->row) + DrawControls (side, FALSE); + for (row = 0; row < NUM_MELEE_ROWS; row++) { - for (pMeleeState->col = 0; - pMeleeState->col < NUM_MELEE_COLUMNS; - ++pMeleeState->col) - DrawShipBox (pMeleeState, FALSE); + for (col = 0; col < NUM_MELEE_COLUMNS; col++) + { + FleetShipIndex index = GetShipIndex (row, col); + BYTE ship = pMeleeState->TeamImage[side].ShipList[index]; + DrawShipBox (side, row, col, ship, FALSE); + } } - DrawTeamString (pMeleeState, DTSHS_NORMAL); + DrawTeamString (pMeleeState, side, DTSHS_NORMAL); } - pMeleeState->side = old_side; - pMeleeState->row = old_row; - pMeleeState->col = old_col; } if (pMeleeState->InputFunc == DoPickShip) @@ -358,13 +407,13 @@ RepairMeleeFrame (PRECT pRect) } static BOOLEAN -DrawTeamString (PMELEE_STATE pMS, COUNT HiLiteState) +DrawTeamString (PMELEE_STATE pMS, COUNT side, COUNT HiLiteState) { RECT r; TEXT lfText; r.corner.x = MELEE_X_OFFS - 1; - r.corner.y = (pMS->side + 1) * (MELEE_Y_OFFS + r.corner.y = (side + 1) * (MELEE_Y_OFFS + ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2)); r.extent.width = NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE); r.extent.height = 13; @@ -376,7 +425,7 @@ DrawTeamString (PMELEE_STATE pMS, COUNT HiLiteState) SetContextFont (MicroFont); - lfText.pStr = pMS->TeamImage[pMS->side].TeamName; + lfText.pStr = pMS->TeamImage[side].TeamName; lfText.baseline.y = r.corner.y + r.extent.height - 3; lfText.baseline.x = r.corner.x + 1; @@ -389,7 +438,7 @@ DrawTeamString (PMELEE_STATE pMS, COUNT HiLiteState) TEXT rtText; UNICODE buf[30]; - sprintf (buf, "%d", pMS->star_bucks[pMS->side]); + sprintf (buf, "%d", pMS->star_bucks[side]); rtText.pStr = buf; rtText.align = ALIGN_RIGHT; rtText.CharCount = (COUNT)~0; @@ -464,6 +513,7 @@ DrawTeamString (PMELEE_STATE pMS, COUNT HiLiteState) return (TRUE); } +// Draw a ship icon in the ship selection popup. static void DrawPickIcon (COUNT iship, BYTE DrawErase) { @@ -494,13 +544,142 @@ DrawPickIcon (COUNT iship, BYTE DrawErase) UnlockStarShip (&master_q, hStarShip); } +#ifdef NETPLAY +// This function is generic. It should probably be moved to elsewhere. +static void +multiLineDrawText(TEXT *textIn, RECT *clipRect) { + RECT oldRect; + + SIZE leading; + TEXT text; + SIZE lineWidth; + + GetContextClipRect (&oldRect); + + SetContextClipRect (clipRect); + GetContextFontLeading (&leading); + + text = *textIn; + text.baseline.x = 1; + text.baseline.y = 0; + + if (clipRect->extent.width <= text.baseline.x) + goto out; + + lineWidth = clipRect->extent.width - text.baseline.x; + + while (*text.pStr != '\0') { + const unsigned char *nextLine; + + text.baseline.y += leading; + text.CharCount = (COUNT) ~0; + getLineWithinWidth(&text, &nextLine, lineWidth, text.CharCount); + // This will also fill in text->CharCount. + + font_DrawText (&text); + + text.pStr = nextLine; + } + +out: + SetContextClipRect (&oldRect); +} + +// Use an empty string to clear the status area. +static void +DrawMeleeStatusMessage (const char *message) +{ + CONTEXT oldContext; + RECT r; + + LockMutex (GraphicsLock); + oldContext = SetContext (SpaceContext); + + r.corner.x = MELEE_STATUS_X_OFFS; + r.corner.y = MELEE_STATUS_Y_OFFS; + r.extent.width = MELEE_STATUS_WIDTH; + r.extent.height = MELEE_STATUS_HEIGHT; + + RepairMeleeFrame (&r); + + if (message[0] != '\0') + { + TEXT lfText; + lfText.pStr = message; + lfText.align = ALIGN_LEFT; + lfText.valign = VALIGN_TOP; + lfText.CharCount = (COUNT)~0; + + SetContextFont (MicroFont); + SetContextForeGroundColor (MELEE_STATUS_COLOR); + + BatchGraphics (); + multiLineDrawText (&lfText, &r); + UnbatchGraphics (); + } + + SetContext (oldContext); + UnlockMutex (GraphicsLock); +} + +static void +UpdateMeleeStatusMessage (ssize_t player) +{ + NetConnection *conn; + + assert (player == -1 || (player >= 0 && player < NUM_PLAYERS)); + + if (player == -1) + { + DrawMeleeStatusMessage (""); + return; + } + + conn = netConnections[player]; + if (conn == NULL) + { + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0)); + // "Unconnected. Press LEFT to connect." + return; + } + + switch (NetConnection_getState (conn)) { + case NetState_unconnected: + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0)); + // "Unconnected. Press LEFT to connect." + break; + case NetState_connecting: + if (NetConnection_getPeerOptions(conn)->isServer) + DrawMeleeStatusMessage ( + GAME_STRING (NETMELEE_STRING_BASE + 1)); + // "Awaiting incoming connection...\n" + // "Press RIGHT to cancel." + else + DrawMeleeStatusMessage ( + GAME_STRING (NETMELEE_STRING_BASE + 2)); + // "Attempting outgoing connection...\n" + // "Press RIGHT to cancel." + break; + case NetState_init: + case NetState_inSetup: + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 3)); + // "Connected. Press RIGHT to disconnect." + break; + default: + DrawMeleeStatusMessage (""); + break; + } +} +#endif /* NETPLAY */ + +// XXX: this function is called when the current selection is blinking off. static void Deselect (BYTE opt) { switch (opt) { case START_MELEE: - DrawMeleeIcon (25); + DrawMeleeIcon (25); /* "Battle!" (not highlighted) */ break; case LOAD_TOP: case LOAD_BOT: @@ -510,10 +689,10 @@ Deselect (BYTE opt) DrawFileStrings (pMeleeState, 0); break; case SAVE_TOP: - DrawMeleeIcon (18); + DrawMeleeIcon (18); /* "Save" (top; not highlighted) */ break; case SAVE_BOT: - DrawMeleeIcon (21); + DrawMeleeIcon (21); /* "Save" (bottom; not highlighted) */ break; case QUIT_BOT: DrawMeleeIcon (29); @@ -531,9 +710,10 @@ Deselect (BYTE opt) if (pMeleeState->InputFunc == DoEdit) { if (pMeleeState->row < NUM_MELEE_ROWS) - DrawShipBox (pMeleeState, FALSE); + DrawShipBoxCurrent (pMeleeState, FALSE); else if (pMeleeState->CurIndex == (BYTE)~0) - DrawTeamString (pMeleeState, DTSHS_NORMAL); + DrawTeamString (pMeleeState, pMeleeState->side, + DTSHS_NORMAL); } else if (pMeleeState->InputFunc == DoPickShip) { @@ -543,13 +723,14 @@ Deselect (BYTE opt) } } +// XXX: this function is called when the current selection is blinking off. static void Select (BYTE opt) { switch (opt) { case START_MELEE: - DrawMeleeIcon (26); + DrawMeleeIcon (26); /* "Battle!" (highlighted) */ break; case LOAD_TOP: case LOAD_BOT: @@ -559,10 +740,10 @@ Select (BYTE opt) DrawFileStrings (pMeleeState, 1); break; case SAVE_TOP: - DrawMeleeIcon (20); + DrawMeleeIcon (20); /* "Save" (top; highlighted) */ break; case SAVE_BOT: - DrawMeleeIcon (23); + DrawMeleeIcon (23); /* "Save" (bottom; highlighted) */ break; case QUIT_BOT: DrawMeleeIcon (30); @@ -580,9 +761,10 @@ Select (BYTE opt) if (pMeleeState->InputFunc == DoEdit) { if (pMeleeState->row < NUM_MELEE_ROWS) - DrawShipBox (pMeleeState, TRUE); + DrawShipBoxCurrent (pMeleeState, TRUE); else if (pMeleeState->CurIndex == (BYTE)~0) - DrawTeamString (pMeleeState, DTSHS_SELECTED); + DrawTeamString (pMeleeState, pMeleeState->side, + DTSHS_SELECTED); } else if (pMeleeState->InputFunc == DoPickShip) { @@ -719,6 +901,30 @@ DrawMeleeShipStrings (PMELEE_STATE pMS, BYTE NewStarShip) UnlockMutex (GraphicsLock); } +// returns (COUNT) ~0 for an invalid ship. +static COUNT +GetShipValue (BYTE StarShip) +{ + HSTARSHIP hStarShip; + + if (StarShip == MELEE_NONE) + return 0; + + hStarShip = GetStarShipFromIndex (&master_q, StarShip); + if (hStarShip == 0) + return (COUNT)~0; + + { + STARSHIPPTR StarShipPtr; + COUNT val; + + StarShipPtr = LockStarShip (&master_q, hStarShip); + val = StarShipPtr->RaceDescPtr->ship_info.ship_cost; + UnlockStarShip (&master_q, hStarShip); + return val; + } +} + static COUNT GetTeamValue (TEAM_IMAGE *pTI) { @@ -728,28 +934,14 @@ GetTeamValue (TEAM_IMAGE *pTI) val = 0; for (index = 0; index < MELEE_FLEET_SIZE; index++) { - BYTE StarShip; - - StarShip = pTI->ShipList[index]; - if (StarShip != MELEE_NONE) - { - HSTARSHIP hStarShip; - - hStarShip = GetStarShipFromIndex (&master_q, StarShip); - if (hStarShip == 0) - pTI->ShipList[index] = MELEE_NONE; - else - { - STARSHIPPTR StarShipPtr; - - StarShipPtr = LockStarShip (&master_q, hStarShip); - val += StarShipPtr->RaceDescPtr->ship_info.ship_cost; - UnlockStarShip (&master_q, hStarShip); - } - } + BYTE StarShip = pTI->ShipList[index]; + COUNT shipVal = GetShipValue (StarShip); + if (shipVal == (COUNT)~0) + pTI->ShipList[index] = MELEE_NONE; + val += shipVal; } - return (val); + return val; } static int @@ -759,7 +951,7 @@ ReadTeamImage (TEAM_IMAGE *pTI, uio_Stream *load_fp) } static int -LoadTeamImage(DIRENTRY DirEntry, TEAM_IMAGE* pTI, +LoadTeamImage (DIRENTRY DirEntry, TEAM_IMAGE* pTI, UNICODE* pFilePath) { UNICODE file[NAME_MAX]; // local buf if needed @@ -770,7 +962,8 @@ LoadTeamImage(DIRENTRY DirEntry, TEAM_IMAGE* pTI, pfile = pFilePath != NULL_PTR ? pFilePath : file; GetDirEntryContents (DirEntry, (STRINGPTR)pfile, FALSE); - if ((load_fp = res_OpenResFile (meleeDir, pfile, "rb")) == 0) + load_fp = res_OpenResFile (meleeDir, pfile, "rb"); + if (load_fp == 0) status = -1; else { @@ -847,7 +1040,7 @@ DrawFileStrings (PMELEE_STATE pMS, int HiLiteState) { COUNT teams_left; - DrawMeleeIcon (28); + DrawMeleeIcon (28); /* Load team frame */ Text.baseline.y = y; teams_left = (COUNT)( @@ -865,8 +1058,7 @@ DrawFileStrings (PMELEE_STATE pMS, int HiLiteState) else { pMS->TeamDE = SetAbsDirEntryTableIndex ( - pMS->TeamDE, bot - NUM_PREBUILT - ); + pMS->TeamDE, bot - NUM_PREBUILT); if (-1 == LoadTeamImage (pMS->TeamDE, &pMS->FileList[bot - top], NULL_PTR)) { @@ -877,11 +1069,13 @@ DrawFileStrings (PMELEE_STATE pMS, int HiLiteState) { Text.pStr = pMS->FileList[bot - top].TeamName; Text.CharCount = (COUNT)~0; - SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (15, 16, 27), 0x00)); + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (15, 16, 27), 0x00)); font_DrawText (&Text); rtText.baseline.y = Text.baseline.y; - sprintf (buf, "%d", GetTeamValue (&pMS->FileList[bot - top])); + sprintf (buf, "%d", + GetTeamValue (&pMS->FileList[bot - top])); rtText.CharCount = (COUNT)~0; font_DrawText (&rtText); } @@ -931,8 +1125,12 @@ DoLoadTeam (PMELEE_STATE pMS) { LockMutex (GraphicsLock); Select (pMS->MeleeOption); - if ((pMS->TopTeamIndex = pMS->CurIndex) == (COUNT)~0) - pMS->TopTeamIndex = pMS->CurIndex = 0; + pMS->TopTeamIndex = pMS->CurIndex; + if (pMS->TopTeamIndex == (COUNT)~0) + { + pMS->TopTeamIndex = 0; + pMS->CurIndex = 0; + } else if (pMS->TopTeamIndex <= MAX_VIS_TEAMS / 2) pMS->TopTeamIndex = 0; else @@ -948,10 +1146,12 @@ DoLoadTeam (PMELEE_STATE pMS) { if (!PulsedInputState.menu[KEY_MENU_CANCEL]) { - pMS->TeamImage[pMS->side] = pMS->FileList[ - pMS->CurIndex - pMS->TopTeamIndex]; + pMS->TeamImage[pMS->side] = + pMS->FileList[pMS->CurIndex - pMS->TopTeamIndex]; pMS->star_bucks[pMS->side] = GetTeamValue (&pMS->TeamImage[pMS->side]); + entireFleetChanged (pMS, pMS->side); + teamStringChanged (pMS, pMS->side); } pMS->InputFunc = DoMelee; @@ -1063,7 +1263,7 @@ GetNewList: int status; TEAM_IMAGE TI; - status = LoadTeamImage(pMS->TeamDE, &TI, file); + status = LoadTeamImage (pMS->TeamDE, &TI, file); if (status == -1) { @@ -1109,7 +1309,8 @@ DoSaveTeam (PMELEE_STATE pMS) { BOOLEAN err; - err = (BOOLEAN)(WriteTeamImage (&pMS->TeamImage[pMS->side], save_fp) == 0); + err = (BOOLEAN)(WriteTeamImage ( + &pMS->TeamImage[pMS->side], save_fp) == 0); if (res_CloseResFile (save_fp) == 0) err = TRUE; if (err) @@ -1166,7 +1367,7 @@ DeleteCurrentShip (PMELEE_STATE pMS) GetShipBox (&r, pMS->side, pMS->row, pMS->col); RepairMeleeFrame (&r); - DrawTeamString (pMS, DTSHS_REPAIR); + DrawTeamString (pMS, pMS->side, DTSHS_REPAIR); UnlockMutex (GraphicsLock); } @@ -1198,7 +1399,7 @@ OnTeamNameChange (PTEXTENTRY_STATE pTES) hl |= DTSHS_BLOCKCUR; LockMutex (GraphicsLock); - ret = DrawTeamString (pMS, hl); + ret = DrawTeamString (pMS, pMS->side, hl); UnlockMutex (GraphicsLock); return ret; @@ -1218,12 +1419,19 @@ DoEdit (PMELEE_STATE pMS) pMS->Initialized = TRUE; pMS->InputFunc = DoEdit; + return TRUE; } - else if ((pMS->row < NUM_MELEE_ROWS || pMS->CurIndex == (BYTE)~0) + +#ifdef NETPLAY + netInput (); +#endif + if ((pMS->row < NUM_MELEE_ROWS || pMS->CurIndex == (BYTE)~0) && (PulsedInputState.menu[KEY_MENU_CANCEL] || (PulsedInputState.menu[KEY_MENU_RIGHT] - && (pMS->col == NUM_MELEE_COLUMNS - 1 || pMS->row == NUM_MELEE_ROWS)))) + && (pMS->col == NUM_MELEE_COLUMNS - 1 + || pMS->row == NUM_MELEE_ROWS)))) { + // Done editing the teams. LockMutex (GraphicsLock); Deselect (EDIT_MELEE); pMS->CurIndex = (COUNT)~0; @@ -1233,8 +1441,10 @@ DoEdit (PMELEE_STATE pMS) InTime = GetTimeCounter (); } else if (pMS->row < NUM_MELEE_ROWS - && (PulsedInputState.menu[KEY_MENU_SELECT] || PulsedInputState.menu[KEY_MENU_SPECIAL])) + && (PulsedInputState.menu[KEY_MENU_SELECT] || + PulsedInputState.menu[KEY_MENU_SPECIAL])) { + // Show a popup to add a new ship to the current team. if (PulsedInputState.menu[KEY_MENU_SELECT]) pMS->Initialized = 0; else @@ -1242,9 +1452,14 @@ DoEdit (PMELEE_STATE pMS) TFB_ResetControls (); DoPickShip (pMS); } - else if (pMS->row < NUM_MELEE_ROWS && PulsedInputState.menu[KEY_MENU_DELETE]) + else if (pMS->row < NUM_MELEE_ROWS && + PulsedInputState.menu[KEY_MENU_DELETE]) { + // Remove the currently selected ship from the current team. + FleetShipIndex fleetShipIndex; DeleteCurrentShip (pMS); + fleetShipIndex = GetShipIndex (pMS->row, pMS->col); + fleetShipChanged (pMS, pMS->side, fleetShipIndex); AdvanceCursor (pMS); } else @@ -1257,6 +1472,7 @@ DoEdit (PMELEE_STATE pMS) if (row == NUM_MELEE_ROWS) { + // Edit the name of the current team. if (PulsedInputState.menu[KEY_MENU_SELECT]) { TEXTENTRY_STATE tes; @@ -1266,7 +1482,7 @@ DoEdit (PMELEE_STATE pMS) // CurIndex to contain the current cursor position pMS->CurIndex = 0; LockMutex (GraphicsLock); - DrawTeamString (pMS, DTSHS_EDIT); + DrawTeamString (pMS, pMS->side, DTSHS_EDIT); UnlockMutex (GraphicsLock); tes.Initialized = FALSE; @@ -1282,8 +1498,10 @@ DoEdit (PMELEE_STATE pMS) // done entering pMS->CurIndex = (BYTE)~0; LockMutex (GraphicsLock); - DrawTeamString (pMS, DTSHS_REPAIR); + DrawTeamString (pMS, pMS->side, DTSHS_REPAIR); UnlockMutex (GraphicsLock); + + teamStringChanged (pMS, pMS->side); return (TRUE); } @@ -1341,7 +1559,8 @@ DoEdit (PMELEE_STATE pMS) else { FleetShipIndex fleetShipIndex = GetShipIndex (row, col); - pMS->CurIndex = pMS->TeamImage[side].ShipList[fleetShipIndex]; + pMS->CurIndex = + pMS->TeamImage[side].ShipList[fleetShipIndex]; } UnlockMutex (GraphicsLock); @@ -1349,9 +1568,14 @@ DoEdit (PMELEE_STATE pMS) } } +#ifdef NETPLAY + flushPacketQueues (); +#endif + return (TRUE); } +// Handle the popup from which a ship to add to the fleet can be chosen. static BOOLEAN DoPickShip (PMELEE_STATE pMS) { @@ -1411,9 +1635,10 @@ DoPickShip (PMELEE_STATE pMS) UnlockStarShip (&master_q, hStarShip); pMS->TeamImage[pMS->side].ShipList[index] = pMS->CurIndex; + fleetShipChanged (pMS, pMS->side, index); LockMutex (GraphicsLock); - DrawTeamString (pMS, DTSHS_REPAIR); - DrawShipBox (pMS, FALSE); + DrawTeamString (pMS, pMS->side, DTSHS_REPAIR); + DrawShipBoxCurrent (pMS, FALSE); UnlockMutex (GraphicsLock); AdvanceCursor (pMS); } @@ -1485,6 +1710,158 @@ DoPickShip (PMELEE_STATE pMS) return (TRUE); } +// Returns -1 if a connection has been aborted. +static ssize_t +numPlayersReady (void) +{ + size_t player; + size_t numDone; + + numDone = 0; + for (player = 0; player < NUM_PLAYERS; player++) + { + if (!(PlayerControl[player] & NETWORK_CONTROL)) + { + numDone++; + continue; + } + +#ifdef NETPLAY + { + NetConnection *conn; + + conn = netConnections[player]; + + if (conn == NULL || !NetConnection_isConnected (conn)) + return -1; + + if (NetConnection_getState(conn) > NetState_inSetup) + numDone++; + } +#endif + } + + return numDone; +} + +// The player has pressed "Start Game", and all Network players are +// connected. We're now just waiting for the confirmation of the other +// party. +// When the other party changes something in the settings, the confirmation +// is cancelled. +static BOOLEAN +DoConfirmSettings (PMELEE_STATE pMS) +{ +#ifdef NETPLAY + ssize_t numDone; +#endif + + if (PulsedInputState.menu[KEY_MENU_CANCEL]) + { + pMS->InputFunc = DoMelee; +#ifdef NETPLAY + cancelConfirmations (); + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 4)); + // "Confirmation cancelled. Press FIRE to reconfirm." +#endif + return TRUE; + } + + if (PulsedInputState.menu[KEY_MENU_LEFT] || + PulsedInputState.menu[KEY_MENU_UP] || + PulsedInputState.menu[KEY_MENU_DOWN]) + { + pMS->InputFunc = DoMelee; +#ifdef NETPLAY + cancelConfirmations (); + DrawMeleeStatusMessage (""); +#endif + return DoMelee (pMS); + // Let the pressed keys take effect immediately. + } + +#ifndef NETPLAY + pMS->InputFunc = DoMelee; + SeedRandomNumbers (); + StartMelee (pMS); + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + return TRUE; +#else + netInput (); + SleepThread (ONE_SECOND / 120); + + numDone = numPlayersReady (); + if (numDone == -1) + { + // Connection aborted + cancelConfirmations (); + flushPacketQueues (); + pMS->InputFunc = DoMelee; + return TRUE; + } + else if (numDone != NUM_SIDES) + { + // Still waiting for some confirmation. + return TRUE; + } + + // All sides have confirmed. + + // Send our own prefered frame delay. + sendInputDelayConnections (netplayOptions.inputDelay); + + // Synchronise the RNGs: + { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn; + + if (!(PlayerControl[player] & NETWORK_CONTROL)) + continue; + + conn = netConnections[player]; + assert (conn != NULL); + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getDiscriminant(conn)) + Netplay_seedRandom (conn, SeedRandomNumbers ()); + } + flushPacketQueues (); + } + + { + // One side will send the seed followed by 'Done' and wait + // for the other side to report 'Done'. + // The other side will report 'Done' and will wait for the other + // side to report 'Done', but before the reception of 'Done' + // it will have received the seed. + bool allOk = negotiateReadyConnections (true, NetState_interBattle); + if (!allOk) + return FALSE; + } + + // The maximum value for all connections is used. + { + bool ok = setupInputDelay(netplayOptions.inputDelay); + if (!ok) + return FALSE; + } + + pMS->InputFunc = DoMelee; + + StartMelee (pMS); + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + return TRUE; +#endif /* defined (NETPLAY) */ +} + static void LoadMeleeInfo (PMELEE_STATE pMS) { @@ -1500,7 +1877,8 @@ LoadMeleeInfo (PMELEE_STATE pMS) DestroyDrawable (ReleaseDrawable (PickMeleeFrame)); PickMeleeFrame = CaptureDrawable (CreateDrawable ( WANT_PIXMAP, MELEE_WIDTH, MELEE_HEIGHT, 2)); - s.origin.x = s.origin.y = 0; + s.origin.x = 0; + s.origin.y = 0; s.frame = CaptureDrawable (LoadGraphic (MELEE_PICK_MASK_PMAP_ANIM)); SetContextFGFrame (PickMeleeFrame); DrawStamp (&s); @@ -1553,6 +1931,10 @@ FreeMeleeInfo (PMELEE_STATE pMS) MeleeFrame = 0; DestroyDrawable (ReleaseDrawable (BuildPickFrame)); BuildPickFrame = 0; + +#ifdef NETPLAY + closeAllConnections (); +#endif } static void @@ -1591,22 +1973,23 @@ BuildAndDrawShipList (PMELEE_STATE pMS) r.corner.y += r.extent.height; r.extent.height = NAME_AREA_HEIGHT; DrawFilledRectangle (&r); + + // Team name at the bottom of the frame: t.align = ALIGN_CENTER; t.pStr = pMS->TeamImage[i].TeamName; t.CharCount = (COUNT)~0; SetContextFont (TinyFont); - SetContextForeGroundColor (BUILD_COLOR ( - MAKE_RGB15 (0xA, 0xA, 0x1F), 0x9)); + SetContextForeGroundColor (PICKSHIP_TEAM_NAME_TEXT_COLOR); font_DrawText (&t); + // Total team value of the starting team: sprintf (buf, "%d", pMS->star_bucks[i]); t.baseline.x = 4; t.baseline.y = 7; t.align = ALIGN_LEFT; t.pStr = buf; t.CharCount = (COUNT)~0; - SetContextForeGroundColor (BUILD_COLOR ( - MAKE_RGB15 (0x4, 0x5, 0x1F), 0x4B)); + SetContextForeGroundColor (PICKSHIP_TEAM_START_VALUE_COLOR); font_DrawText (&t); for (index = 0; index < MELEE_FLEET_SIZE; index++) @@ -1614,7 +1997,9 @@ BuildAndDrawShipList (PMELEE_STATE pMS) BYTE StarShip; StarShip = pMS->TeamImage[i].ShipList[index]; - if (StarShip != MELEE_NONE) + if (StarShip == MELEE_NONE) + continue; + { BYTE row, col; BYTE ship_cost; @@ -1625,12 +2010,14 @@ BuildAndDrawShipList (PMELEE_STATE pMS) StarShipPtr = LockStarShip (&master_q, hStarShip); hBuiltShip = Build (&race_q[side], StarShipPtr->RaceResIndex, 1 << side, NameCaptain (&race_q[side], StarShipPtr)); + row = GetShipRow (index); col = GetShipColumn (index); s.origin.x = 4 + ((ICON_WIDTH + 2) * col); s.origin.y = 10 + ((ICON_HEIGHT + 2) * row); s.frame = StarShipPtr->RaceDescPtr->ship_info.icons; DrawStamp (&s); + ship_cost = StarShipPtr->RaceDescPtr->ship_info.ship_cost; UnlockStarShip (&master_q, hStarShip); @@ -1648,6 +2035,133 @@ BuildAndDrawShipList (PMELEE_STATE pMS) SetContext (OldContext); } +static void +StartMelee (PMELEE_STATE pMS) +{ + if (pMS->flash_task) + { + ConcludeTask (pMS->flash_task); + pMS->flash_task = 0; + } + + { + BYTE black_buf[] = {FadeAllToBlack}; + + FadeMusic (0, ONE_SECOND / 2); + SleepThreadUntil (XFormColorMap ( + (COLORMAPPTR)black_buf, ONE_SECOND / 2) + ONE_SECOND / 60); + FlushColorXForms (); + StopMusic (); + } + FadeMusic (NORMAL_VOLUME, 0); + if (pMS->hMusic) + { + DestroyMusic (pMS->hMusic); + pMS->hMusic = 0; + } + + do + { + LockMutex (GraphicsLock); + BuildAndDrawShipList (pMS); + UnlockMutex (GraphicsLock); + + WaitForSoundEnd (TFBSOUND_WAIT_ALL); + + load_gravity_well ((BYTE)((COUNT)TFB_Random () % + NUMBER_OF_PLANET_TYPES)); + Battle (); + free_gravity_well (); + + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return; + + { + BYTE black_buf[] = { FadeAllToBlack }; + + SleepThreadUntil (XFormColorMap ( + (COLORMAPPTR)black_buf, ONE_SECOND / 2) + + ONE_SECOND / 60); + FlushColorXForms (); + } + } while (0 /* !(GLOBAL (CurrentActivity) & CHECK_ABORT) */); + GLOBAL (CurrentActivity) = SUPER_MELEE; + + pMS->Initialized = FALSE; +} + +static void +StartMeleeButtonPressed (PMELEE_STATE pMS) +{ + if (pMS->star_bucks[0] == 0 || pMS->star_bucks[1] == 0) + { + PlayMenuSound (MENU_SOUND_FAILURE); + return; + } + +#ifdef NETPLAY + // Check whether all network parties are ready; + { + COUNT player; + bool netReady = true; + + // We collect all error conditions, instead of only reporting + // the first one. + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn; + + if (!(PlayerControl[player] & NETWORK_CONTROL)) + continue; + + conn = netConnections[player]; + if (conn == NULL || !NetConnection_isConnected (conn)) + { + // Connection for player not established. + netReady = false; + if (player == 0) + DrawMeleeStatusMessage ( + GAME_STRING (NETMELEE_STRING_BASE + 5)); + // "Connection for bottom player not " + // "established." + else + DrawMeleeStatusMessage ( + GAME_STRING (NETMELEE_STRING_BASE + 6)); + // "Connection for top player not " + // "established." + } + else if (NetConnection_getState(conn) != NetState_inSetup) + { + // This side may be in the setup, but the network connection + // is not in a state that setup information can be sent. + netReady = false; + if (player == 0) + DrawMeleeStatusMessage ( + GAME_STRING (NETMELEE_STRING_BASE + 14)); + // "Connection for bottom player not ready." + else + DrawMeleeStatusMessage ( + GAME_STRING (NETMELEE_STRING_BASE + 15)); + // "Connection for top player not ready." + + } + } + if (!netReady) + { + PlayMenuSound (MENU_SOUND_FAILURE); + return; + } + + if (numPlayersReady () != NUM_PLAYERS) + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 7)); + // "Waiting for remote confirmation." + confirmConnections (); + } +#endif + + pMS->InputFunc = DoConfirmSettings; +} + static BOOLEAN DoMelee (PMELEE_STATE pMS) { @@ -1678,29 +2192,82 @@ DoMelee (PMELEE_STATE pMS) XFormColorMap ((COLORMAPPTR)clut_buf, ONE_SECOND / 2); } InTime = GetTimeCounter (); + return TRUE; } - else if (PulsedInputState.menu[KEY_MENU_CANCEL] || PulsedInputState.menu[KEY_MENU_LEFT]) + +#ifdef NETPLAY + netInput (); +#endif + + if (PulsedInputState.menu[KEY_MENU_CANCEL] || + PulsedInputState.menu[KEY_MENU_LEFT]) { - LockMutex (GraphicsLock); - InTime = GetTimeCounter (); - Deselect (pMS->MeleeOption); - UnlockMutex (GraphicsLock); - pMS->MeleeOption = EDIT_MELEE; - pMS->Initialized = FALSE; - if (PulsedInputState.menu[KEY_MENU_CANCEL]) +#ifdef NETPLAY + bool leftHandled = false; + // Pressing the 'left' key has already been handled. + + if (PulsedInputState.menu[KEY_MENU_LEFT] && + ((pMS->MeleeOption == CONTROLS_TOP && + (PlayerControl[1] & NETWORK_CONTROL)) || + (pMS->MeleeOption == CONTROLS_BOT && + (PlayerControl[0] & NETWORK_CONTROL)))) { - pMS->side = 0; - pMS->row = 0; - pMS->col = 0; + // Build a network connection. + COUNT which_side = (pMS->MeleeOption == CONTROLS_TOP) ? 1 : 0; + NetConnection *conn; + + if (netConnections[which_side] != NULL) + closePlayerNetworkConnection(which_side); + + conn = openPlayerNetworkConnection (which_side, (void *) pMS); + + pMS->InputFunc = DoMelee; + leftHandled = true; + UpdateMeleeStatusMessage (which_side); } - else + + if (!leftHandled) +#endif { - pMS->side = 0; - pMS->row = NUM_MELEE_ROWS - 1; - pMS->col = NUM_MELEE_COLUMNS - 1; + // Start editing the teams. + LockMutex (GraphicsLock); + InTime = GetTimeCounter (); + Deselect (pMS->MeleeOption); + UnlockMutex (GraphicsLock); + pMS->MeleeOption = EDIT_MELEE; + pMS->Initialized = FALSE; + if (PulsedInputState.menu[KEY_MENU_CANCEL]) + { + pMS->side = 0; + pMS->row = 0; + pMS->col = 0; + } + else + { + pMS->side = 0; + pMS->row = NUM_MELEE_ROWS - 1; + pMS->col = NUM_MELEE_COLUMNS - 1; + } + DoEdit (pMS); } - DoEdit (pMS); } +#ifdef NETPLAY + else if (PulsedInputState.menu[KEY_MENU_RIGHT]) + { + if ((pMS->MeleeOption == CONTROLS_TOP && + (PlayerControl[1] & NETWORK_CONTROL)) || + (pMS->MeleeOption == CONTROLS_BOT && + (PlayerControl[0] & NETWORK_CONTROL))) + { + // Terminate a network connection. + COUNT which_side = (pMS->MeleeOption == CONTROLS_TOP) ? 1 : 0; + if (netConnections[which_side] != NULL) { + closePlayerNetworkConnection (which_side); + UpdateMeleeStatusMessage (which_side); + } + } + } +#endif else { MELEE_OPTIONS NewMeleeOption; @@ -1728,11 +2295,27 @@ DoMelee (PMELEE_STATE pMS) if (NewMeleeOption != pMS->MeleeOption) { +#ifdef NETPLAY + if (pMS->MeleeOption == CONTROLS_TOP || + pMS->MeleeOption == CONTROLS_BOT) + UpdateMeleeStatusMessage (-1); +#endif LockMutex (GraphicsLock); Deselect (pMS->MeleeOption); pMS->MeleeOption = NewMeleeOption; Select (pMS->MeleeOption); UnlockMutex (GraphicsLock); +#ifdef NETPLAY + if (NewMeleeOption == CONTROLS_TOP || + NewMeleeOption == CONTROLS_BOT) + { + COUNT side = (NewMeleeOption == CONTROLS_TOP) ? 1 : 0; + if (PlayerControl[side] & NETWORK_CONTROL) + UpdateMeleeStatusMessage (side); + else + UpdateMeleeStatusMessage (-1); + } +#endif } if (PulsedInputState.menu[KEY_MENU_SELECT] || force_select) @@ -1740,64 +2323,9 @@ DoMelee (PMELEE_STATE pMS) switch (pMS->MeleeOption) { case START_MELEE: - if (pMS->star_bucks[0] == 0 || pMS->star_bucks[1] == 0) - { - PlayMenuSound (MENU_SOUND_FAILURE); - break; - } - - if (pMS->flash_task) - { - ConcludeTask (pMS->flash_task); - pMS->flash_task = 0; - } - - { - BYTE black_buf[] = {FadeAllToBlack}; - - FadeMusic (0, ONE_SECOND / 2); - SleepThreadUntil (XFormColorMap ( - (COLORMAPPTR)black_buf, ONE_SECOND / 2 - ) + ONE_SECOND / 60); - FlushColorXForms (); - StopMusic (); - } - FadeMusic (NORMAL_VOLUME, 0); - if (pMS->hMusic) - { - DestroyMusic (pMS->hMusic); - pMS->hMusic = 0; - } - - do - { - LockMutex (GraphicsLock); - BuildAndDrawShipList (pMS); - UnlockMutex (GraphicsLock); - - WaitForSoundEnd (TFBSOUND_WAIT_ALL); - - SeedRandomNumbers (); - load_gravity_well ((BYTE)((COUNT)TFB_Random () % - NUMBER_OF_PLANET_TYPES)); - Battle (); - free_gravity_well (); - - if (GLOBAL (CurrentActivity) & CHECK_ABORT) - return (FALSE); - - { - BYTE black_buf[] = {FadeAllToBlack}; - - SleepThreadUntil (XFormColorMap ( - (COLORMAPPTR)black_buf, ONE_SECOND / 2) - + ONE_SECOND / 60); - FlushColorXForms (); - } - } while (0 /* !(GLOBAL (CurrentActivity) & CHECK_ABORT) */); - GLOBAL (CurrentActivity) = SUPER_MELEE; - - pMS->Initialized = FALSE; + StartMeleeButtonPressed (pMS); + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return (FALSE); break; case LOAD_TOP: case LOAD_BOT: @@ -1826,13 +2354,26 @@ DoMelee (PMELEE_STATE pMS) PlayerControl[which_side] = COMPUTER_CONTROL | STANDARD_RATING; else if (PlayerControl[which_side] & AWESOME_RATING) + { +#ifdef NETPLAY + PlayerControl[which_side] = + NETWORK_CONTROL | STANDARD_RATING; + UpdateMeleeStatusMessage (which_side); + } + else if (PlayerControl[which_side] & NETWORK_CONTROL) + { + if (netConnections[which_side] != NULL) + closePlayerNetworkConnection (which_side); + UpdateMeleeStatusMessage (-1); +#endif /* NETPLAY */ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING; + } else PlayerControl[which_side] = (( PlayerControl[which_side] - & (STANDARD_RATING | GOOD_RATING | AWESOME_RATING) - ) << 1) | COMPUTER_CONTROL; + & (STANDARD_RATING | GOOD_RATING | + AWESOME_RATING)) << 1) | COMPUTER_CONTROL; SetPlayerInput (); DrawControls (which_side, TRUE); break; @@ -1841,6 +2382,10 @@ DoMelee (PMELEE_STATE pMS) } } +#ifdef NETPLAY + flushPacketQueues (); +#endif + return (TRUE); } @@ -1857,6 +2402,7 @@ InitPreBuilt (PMELEE_STATE pMS) } { + /* "Balanced Team 1" */ FleetShipIndex i = 0; utf8StringCopy (pMS->PreBuiltList[0].TeamName, sizeof (pMS->PreBuiltList[0].TeamName), @@ -1873,6 +2419,7 @@ InitPreBuilt (PMELEE_STATE pMS) } { + /* "Balanced Team 2" */ FleetShipIndex i = 0; utf8StringCopy (pMS->PreBuiltList[1].TeamName, sizeof (pMS->PreBuiltList[1].TeamName), @@ -1891,6 +2438,7 @@ InitPreBuilt (PMELEE_STATE pMS) } { + /* "200 points" */ FleetShipIndex i = 0; utf8StringCopy (pMS->PreBuiltList[2].TeamName, sizeof (pMS->PreBuiltList[2].TeamName), @@ -1909,6 +2457,7 @@ InitPreBuilt (PMELEE_STATE pMS) } { + /* "Behemoth Zenith" */ FleetShipIndex i = 0; utf8StringCopy (pMS->PreBuiltList[3].TeamName, sizeof (pMS->PreBuiltList[3].TeamName), @@ -1926,6 +2475,7 @@ InitPreBuilt (PMELEE_STATE pMS) } { + /* "The Peeled Eyes" */ FleetShipIndex i = 0; utf8StringCopy (pMS->PreBuiltList[4].TeamName, sizeof (pMS->PreBuiltList[4].TeamName), @@ -2125,6 +2675,14 @@ Melee (void) MenuState.InputFunc = DoMelee; MenuState.Initialized = FALSE; +#ifdef NETMELE + { + COUNT player; + for (player = 0; player < NUM_PLAYERS; player++) + netConnections[player] = NULL; + } +#endif + MenuState.CurIndex = (COUNT)~0; InitPreBuilt (&MenuState); @@ -2148,10 +2706,12 @@ Melee (void) status = ReadTeamImage (&MenuState.TeamImage[0], load_fp); if (status != -1) { - if ((status = GetResFileChar (load_fp)) != -1) + status = GetResFileChar (load_fp); + if (status != -1) { PlayerControl[1] = (BYTE)status; - status = ReadTeamImage (&MenuState.TeamImage[1], load_fp); + status = ReadTeamImage ( + &MenuState.TeamImage[1], load_fp); } } } @@ -2167,8 +2727,20 @@ Melee (void) PlayerControl[1] = COMPUTER_CONTROL | STANDARD_RATING; MenuState.TeamImage[1] = MenuState.PreBuiltList[1]; } + +#ifndef NETPLAY + /* Do not allow netplay mode if it isn't compiled in. */ + if (PlayerControl[0] & NETWORK_CONTROL) + PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING; + if (PlayerControl[1] & NETWORK_CONTROL) + PlayerControl[1] = HUMAN_CONTROL | STANDARD_RATING; +#endif } SetPlayerInput (); + teamStringChanged (&MenuState, 0); + teamStringChanged (&MenuState, 1); + entireFleetChanged (&MenuState, 0); + entireFleetChanged (&MenuState, 1); MenuState.side = 0; MenuState.star_bucks[0] = GetTeamValue (&MenuState.TeamImage[0]); @@ -2189,11 +2761,13 @@ Melee (void) { if (PutResFileChar (PlayerControl[0], save_fp) == -1) err = TRUE; - if (!err && WriteTeamImage (&MenuState.TeamImage[0], save_fp) == 0) + if (!err && WriteTeamImage (&MenuState.TeamImage[0], + save_fp) == 0) err = TRUE; if (!err && PutResFileChar (PlayerControl[1], save_fp) == -1) err = TRUE; - if (!err && WriteTeamImage (&MenuState.TeamImage[1], save_fp) == 0) + if (!err && WriteTeamImage (&MenuState.TeamImage[1], + save_fp) == 0) err = TRUE; if (res_CloseResFile (save_fp) == 0) err = TRUE; @@ -2217,3 +2791,221 @@ Melee (void) } } +// Notify the network connections of a team name change. +void +teamStringChanged (PMELEE_STATE pMS, int player) +{ +#ifdef NETPLAY + const char *name; + size_t len; + size_t playerI; + + name = pMS->TeamImage[player].TeamName; + len = strlen (name); + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) + { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getState (conn) != NetState_inSetup) + continue; + + Netplay_teamStringChanged (conn, player, name, len); + } +#else + (void) pMS; + (void) player; +#endif +} + +// Notify the network connections of the configuration of a fleet. +void +entireFleetChanged (PMELEE_STATE pMS, int player) +{ +#ifdef NETPLAY + size_t playerI; + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getState (conn) != NetState_inSetup) + continue; + + Netplay_entireFleetChanged (conn, player, + pMS->TeamImage[player].ShipList, MELEE_FLEET_SIZE); + } +#else + (void) player; +#endif + (void) pMS; +} + +// Notify the network of a change in the configuration of a fleet. +void +fleetShipChanged (PMELEE_STATE pMS, int player, size_t index) +{ +#ifdef NETPLAY + size_t playerI; + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) + { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getState (conn) != NetState_inSetup) + continue; + + Netplay_fleetShipChanged (conn, player, index, + pMS->TeamImage[player].ShipList[index]); + } +#else + (void) player; + (void) index; +#endif + (void) pMS; +} + +#ifdef NETPLAY +// NB: 'len' does not include the terminating 0. +// 'len' counts in bytes, not in characters. +void +updateTeamName (PMELEE_STATE pMS, COUNT side, const char *name, + size_t len) +{ + // NB: MAX_TEAM_CHARS is the maximum number of characters, + // without the terminating '\0'. + if (len > MAX_TEAM_CHARS) + len = MAX_TEAM_CHARS; + + // TeamName has space for at least MAX_TEAM_CHARS + 1 bytes. + strncpy (pMS->TeamImage[side].TeamName, name, len); + pMS->TeamImage[side].TeamName[len] = '\0'; + +#if 0 /* DTSHS_REPAIR does not combine with other options */ + if (pMS->MeleeOption == EDIT_MELEE && pMS->side == side + && pMS->row == NUM_MELEE_ROWS) + DrawTeamString (pMS, side, DTSHS_REPAIR | DTSHS_SELECTED); + else +#endif + DrawTeamString (pMS, side, DTSHS_REPAIR); +} + +// Update a ship in a fleet as specified by a remote party. +bool +updateFleetShip (PMELEE_STATE pMS, COUNT side, COUNT index, BYTE ship) +{ + BYTE row = GetShipRow (index); + BYTE col = GetShipColumn (index); + COUNT val; + + FleetShipIndex selectedShipIndex; + BOOLEAN isSelected; + + if (ship >= NUM_MELEE_SHIPS && ship != MELEE_NONE) { + fprintf (stderr, "Invalid ship type number %d (max = %d).\n", + ship, NUM_MELEE_SHIPS - 1); + return false; + } + + if (index >= MELEE_FLEET_SIZE) + { + fprintf (stderr, "Invalid ship position number %d (max = %d).\n", + index, MELEE_FLEET_SIZE - 1); + return false; + } + + val = GetShipValue (ship); + if (val == (COUNT) ~0) + return false; + + pMS->star_bucks[side] -= + GetShipValue (pMS->TeamImage[side].ShipList[index]); + pMS->star_bucks[side] += val; + + pMS->TeamImage[side].ShipList[index] = ship; + + selectedShipIndex = GetShipIndex (pMS->row, pMS->col); + isSelected = (pMS->MeleeOption == EDIT_MELEE) && + (pMS->side == side) && (index == selectedShipIndex); + // Ship to be updated is the currently selected one. + + if (ship == MELEE_NONE) + { + RECT r; + GetShipBox (&r, side, row, col); + RepairMeleeFrame (&r); + } else + DrawShipBox (side, row, col, ship, isSelected); + + if (isSelected) + { + pMS->CurIndex = ship; + DrawMeleeShipStrings (pMS, ship); + } + + // Reprint the team value: + //DrawTeamString (pMeleeState, side, DTSHS_NORMAL); + DrawTeamString (pMS, side, DTSHS_REPAIR); + + return true; +} + +void +updateRandomSeed (PMELEE_STATE pMS, COUNT side, DWORD seed) { + TFB_SeedRandom (seed); + (void) pMS; + (void) side; +} + +void +connectedFeedback (PMELEE_STATE pMS, COUNT side) { + if (side == 0) + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 8)); + // "Bottom player is connected." + else + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 9)); + // "Top player is connected." + + PlayMenuSound (MENU_SOUND_INVOKED); + (void) pMS; +} + +void +errorFeedback (PMELEE_STATE pMS, COUNT side) { + if (side == 0) + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 10)); + // "Bottom player: connection failed." + else + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 11)); + // "Top player: connection failed." + (void) pMS; +} + +void +closeFeedback (PMELEE_STATE pMS, COUNT side) { + if (side == 0) + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 12)); + // "Bottom player: connection closed." + else + DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 13)); + // "Top player: connection closed." + (void) pMS; +} +#endif /* NETPLAY */ + + + diff --git a/sc2/src/sc2code/melee.h b/sc2/src/sc2code/melee.h index 9362b4827..a2e098ab4 100644 --- a/sc2/src/sc2code/melee.h +++ b/sc2/src/sc2code/melee.h @@ -25,6 +25,7 @@ #include "libs/sndlib.h" #include "libs/reslib.h" +typedef struct melee_state MELEE_STATE; enum { @@ -68,7 +69,7 @@ enum extern FRAME PickMeleeFrame; -#define PICK_BG_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x0F), 0x1) +#define PICK_BG_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x0F), 0x01) #define MAX_TEAM_CHARS 30 #define MAX_VIS_TEAMS 5 @@ -82,11 +83,12 @@ typedef COUNT FleetShipIndex; typedef struct { BYTE ShipList[MELEE_FLEET_SIZE]; - UNICODE TeamName[MAX_TEAM_CHARS + 25]; /* in case default names in starcon.txt - are unknowingly mangled */ + UNICODE TeamName[MAX_TEAM_CHARS + 1 + 24]; + /* The +1 is for the terminating \0; the +24 is in case some + * default name in starcon.txt is unknowingly mangled. */ } TEAM_IMAGE; -typedef struct melee_state +struct melee_state { BOOLEAN (*InputFunc) (struct melee_state *pInputState); COUNT MenuRepeatDelay; @@ -104,13 +106,24 @@ typedef struct melee_state TEAM_IMAGE PreBuiltList[NUM_PREBUILT]; MUSIC_REF hMusic; -} MELEE_STATE; +}; typedef MELEE_STATE *PMELEE_STATE; extern PMELEE_STATE volatile pMeleeState; extern void Melee (void); +void teamStringChanged (PMELEE_STATE pMS, int player); +void entireFleetChanged (PMELEE_STATE pMS, int player); +void fleetShipChanged (PMELEE_STATE pMS, int player, size_t index); + +void updateTeamName (PMELEE_STATE pMS, COUNT side, const char *name, + size_t len); +bool updateFleetShip (PMELEE_STATE pMS, COUNT side, COUNT index, BYTE ship); +void updateRandomSeed (PMELEE_STATE pMS, COUNT side, DWORD seed); +void connectedFeedback (PMELEE_STATE pMS, COUNT side); +void errorFeedback (PMELEE_STATE pMS, COUNT side); +void closeFeedback (PMELEE_STATE pMS, COUNT side); #endif /* _MELEE_H */ diff --git a/sc2/src/sc2code/netplay/FILES b/sc2/src/sc2code/netplay/FILES new file mode 100644 index 000000000..a3687ca5f --- /dev/null +++ b/sc2/src/sc2code/netplay/FILES @@ -0,0 +1,48 @@ +In netplay/: +crc.{c,h} Generic CRC routines. +checkbuf.{c,h} Buffer of checksums. +checksum.{c,h} Routines for checksumming the game state. +netconnection.{c,h} Definition of NetConnection, being the state of a + network connection, and operations on it. +nc_connect.ci Part of netconnection.c that handles establishing + connections. +netinput.{c,h} Definitions and operations for melee input commands + over a network connection. +netmelee.{c,h} Keeps track of network connections used in the game, + with methods to control them all at once. + Functions that directly access the game data are kept + in the relevant files (melee.c, pickmele.c, battle.c). +netmisc.{c,h} Miscelaneous functions that didn't fit in elsewhere. +netoptions.{c,h} Description of a network connection to be established. +netplay.h Some global netplay definitions. +netrcv.{c,h} Processes incoming packets. Does know about the protocol + and will do consistency checking. + Does not directly manipulate the game state. +netsend.{c,h} Enqueues all sorts of packets for sending. + Does not know about the protocol and hence will do + no checks for protocol sanity. +netstate.{c,h} Definitions of the states of a network connection. +notify.{c,h} Routines for notifying a remote side of local changes. + Knows about the protocal and has assert()s to + check for local consistency. +packet.{c,h} Definition and creation of packets. Create functions + should only be called from netsend.c. +packethandlers.{c,h} Routines for processing each type of incoming packet. +packetq.{c,h} Manages the packet queue. +packetsenders.{c,h} Creates and sends/queues packets. + +In netplay/proto/: +confirm.{c,h} Functions for handing the 'confirmation' protocol. +ready.{c,h} Functions for handling the 'ready' protocol. + + + + + +TODO: +Division: +- files that interface with sockets and knows nothing of the game +- files that know of sockets and the game but don't directly interface + with either +- files that interface with the game and know nothing of sockets + diff --git a/sc2/src/sc2code/netplay/Makeinfo b/sc2/src/sc2code/netplay/Makeinfo new file mode 100644 index 000000000..bab1b01e9 --- /dev/null +++ b/sc2/src/sc2code/netplay/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="proto" +uqm_CFILES="checkbuf.c checksum.c crc.c netconnection.c netinput.c netmelee.c netmisc.c netoptions.c netrcv.c netsend.c netstate.c notify.c packet.c packethandlers.c packetsenders.c packetq.c" + diff --git a/sc2/src/sc2code/netplay/checkbuf.c b/sc2/src/sc2code/netplay/checkbuf.c new file mode 100644 index 000000000..324138078 --- /dev/null +++ b/sc2/src/sc2code/netplay/checkbuf.c @@ -0,0 +1,146 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#include "netplay.h" +#include "checkbuf.h" +#include "libs/log.h" +#include "libs/misc.h" + +#include "../battle.h" + // for battleFrameCount + + +#include +#include + + + +static inline BattleFrameCounter +ChecksumBuffer_getCurrentFrameNr(void) { + return battleFrameCount; +} + +void +ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval) { + // The input buffer lags BattleInput_inputDelay frames behind, + // but only every interval frames will there be a checksum to be + // checked. + + // Checksums will be checked when 'frameNr % interval == 0'. + // (and frameNr is zero-based). + // The checksum of frame n will be processed in frame 'n + delay'. + + // In the worst case, side 1 processes frames 'n' through 'n + delay - 1', + // then blocks in frame 'n + delay' (after sending a checksum, if that's + // pertinent for that frame). + // Then side 2 receives all this input and these checksums, and + // progresses to 'delay' frames after the last frame the received input + // originated from, and blocks in the frame after it (after sending a + // checksum, if that's pertinent for the frame). + // So it sent input and checksums for frames 'n' through + // 'n + delay + delay + 1'. + // The input and checksums for these '2*delay + 2' frames are still + // unhandled by side 1, so it needs buffer space for this. + // With checksums only sent every interval frames, the buffer space + // needed will be 'roundUp(2*delay + 2)' spaces. + + size_t bufSize = ((2 * delay + 2) + (interval - 1)) / interval; + + { +#ifdef NETPLAY_DEBUG + size_t i; +#endif + + cb->checksums = malloc(bufSize * sizeof (ChecksumEntry)); + cb->maxSize = bufSize; + cb->interval = interval; + +#ifdef NETPLAY_DEBUG + for (i = 0; i < bufSize; i++) { + cb->checksums[i].checksum = 0; + cb->checksums[i].frameNr = (BattleFrameCounter) -1; + } +#endif + } +} + +void +ChecksumBuffer_uninit(ChecksumBuffer *cb) { + if (cb->checksums != NULL) { + free(cb->checksums); + cb->checksums = NULL; + } +} + +// Returns the entry that would be used for the checksum for the specified +// frame. Whether the entry is actually valid is not checked. +static ChecksumEntry * +ChecksumBuffer_getChecksumEntry(ChecksumBuffer *cb, + BattleFrameCounter frameNr) { + size_t index; + ChecksumEntry *entry; + + assert(frameNr % cb->interval == 0); + // We only record checksums exactly every 'interval' frames. + + index = (frameNr / cb->interval) % cb->maxSize; + entry = &cb->checksums[index]; + + return entry; +} + +bool +ChecksumBuffer_addChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr, + Checksum checksum) { + ChecksumEntry *entry; + + assert(frameNr % cb->interval == 0); + + entry = ChecksumBuffer_getChecksumEntry(cb, frameNr); + +#ifdef NETPLAY_DEBUG + entry->frameNr = frameNr; +#endif + entry->checksum = checksum; + return true; +} + +// Pre: frameNr is within the range of the checksums stored in cb. +bool +ChecksumBuffer_getChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr, + Checksum *result) { + ChecksumEntry *entry; + + entry = ChecksumBuffer_getChecksumEntry(cb, frameNr); + +#ifdef NETPLAY_DEBUG + if (frameNr != entry->frameNr) { + log_add(log_Error, "Checksum buffer entry for requested frame %u " + "(still?) contains a checksum for frame %u.\n", + frameNr, entry->frameNr); + return false; + } +#endif + + *result = entry->checksum; + return true; +} + diff --git a/sc2/src/sc2code/netplay/checkbuf.h b/sc2/src/sc2code/netplay/checkbuf.h new file mode 100644 index 000000000..cc3313c90 --- /dev/null +++ b/sc2/src/sc2code/netplay/checkbuf.h @@ -0,0 +1,62 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CHECKBUF_H +#define _CHECKBUF_H + +typedef struct ChecksumEntry ChecksumEntry; +typedef struct ChecksumBuffer ChecksumBuffer; + +#include "../battle.h" + // for BattleFrameCounter +#include "checksum.h" + + +struct ChecksumEntry { +#ifdef NETPLAY_DEBUG + BattleFrameCounter frameNr; + // The number of the frame this checksum originated from. + // If the checksumming code is working correctly, the checksum + // can only come from one frame, so this value is not needed + // for normal operation. + // Its only use is to detect some cases where checksumming code + // is *not* working correctly. +#endif + Checksum checksum; +}; + +struct ChecksumBuffer { + ChecksumEntry *checksums; + // Cyclic buffer. if 'size' > 0, then 'first' is an index to + // the first used entry, 'size' is the number of used + // entries in the buffer, and (first + size) % maxSize is the + // index to just past the end of the buffer. + size_t maxSize; + + size_t interval; +}; + +void ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval); +void ChecksumBuffer_uninit(ChecksumBuffer *cb); +bool ChecksumBuffer_addChecksum(ChecksumBuffer *cb, + BattleFrameCounter frameNr, Checksum checksum); +bool ChecksumBuffer_getChecksum(ChecksumBuffer *cb, + BattleFrameCounter frameNr, Checksum *result); + +#endif /* _CHECKBUF_H */ + diff --git a/sc2/src/sc2code/netplay/checksum.c b/sc2/src/sc2code/netplay/checksum.c new file mode 100644 index 000000000..51d42bb48 --- /dev/null +++ b/sc2/src/sc2code/netplay/checksum.c @@ -0,0 +1,268 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef NETPLAY + +#include "checksum.h" +#include "netoptions.h" + +#ifdef NETPLAY_CHECKSUM + +#include "checkbuf.h" +#include "crc.h" + // for DUMP_CRC_OPS +#include "netconnection.h" +#include "netmelee.h" +#include "libs/log.h" +#include "libs/misc.h" + + +ChecksumBuffer localChecksumBuffer; + +void +crc_processEXTENT(crc_State *state, const EXTENT *val) { +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "START crc_processEXTENT()."); +#endif + crc_processCOORD(state, val->width); + crc_processCOORD(state, val->height); +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processEXTENT()."); +#endif +} + +void +crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val) { +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "START crc_processVELOCITY_DESC()."); +#endif + crc_processCOUNT(state, val->TravelAngle); + crc_processEXTENT(state, &val->vector); + crc_processEXTENT(state, &val->fract); + crc_processEXTENT(state, &val->error); + crc_processEXTENT(state, &val->incr); +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processVELOCITY_DESC()."); +#endif +} + +#if 0 +void +crc_processPOINT(crc_State *state, const POINT *val) { +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "START crc_processPOINT()."); +#endif + crc_processCOORD(state, val->x); + crc_processCOORD(state, val->y); +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processPOINT()."); +#endif +} + +void +crc_processSTAMP(crc_State *state, const STAMP *val) { +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "START crc_processSTAMP()."); +#endif + crc_processPOINT(state, val->origin); + crc_processFRAME(state, val->frame); +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processSTAMP()."); +#endif +} + +void +crc_processINTERSECT_CONTROL(crc_State *state, const INTERSECT_CONTROL *val) { +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "START crc_processINTERSECT_CONTROL()."); +#endif + crc_processTIME_VALUE(state, val->last_time_val); + crc_processPOINT(state, &val->EndPoint); +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processINTERSECT_CONTROL()."); +#endif +} +#endif + +void +crc_processELEMENT(crc_State *state, const ELEMENT *val) { +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "START crc_processELEMENT()."); +#endif + crc_processELEMENT_FLAGS(state, val->state_flags); + crc_processCOUNT(state, val->life_span); + crc_processCOUNT(state, val->crew_level); + crc_processBYTE(state, val->mass_points); + + // HACK: when a ship is being destroyed, turn_wait is abused to store + // the side this ship is on. This must be excluded from the checksum + // as this does not have to be the same for both sides. + { + extern void new_ship(PELEMENT ElementPtr); + BYTE turn_wait = val->turn_wait; + + if (val->preprocess_func == new_ship) + turn_wait = 0; + + crc_processBYTE(state, turn_wait); + } + + crc_processBYTE(state, val->thrust_wait); + crc_processVELOCITY_DESC(state, &val->velocity); +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processELEMENT()."); +#endif +} + +void +crc_processDispQueue(crc_State *state) { + HELEMENT element; + HELEMENT nextElement; + +#ifdef DUMP_CRC_OPS + size_t i = 0; + log_add(log_Debug, "START crc_processDispQueue() (frame %u).", + battleFrameCount); +#endif + for (element = GetHeadElement(); element != 0; element = nextElement) { + ELEMENTPTR elementPtr; + +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "===== disp_q[%d]:", i); +#endif + LockElement(element, &elementPtr); + + crc_processELEMENT(state, element); + + nextElement = GetSuccElement(elementPtr); + UnlockElement(element); +#ifdef DUMP_CRC_OPS + i++; +#endif + } +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "END crc_processDispQueue() (frame %u).", + battleFrameCount); +#endif +} + +void +initChecksumBuffers(void) { + size_t player; + + for (player = 0; player < NETPLAY_NUM_PLAYERS; player++) + { + NetConnection *conn; + ChecksumBuffer *cb; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + cb = NetConnection_getChecksumBuffer(conn); + ChecksumBuffer_init(cb, getBattleInputDelay(), + NETPLAY_CHECKSUM_INTERVAL); + } + + ChecksumBuffer_init(&localChecksumBuffer, getBattleInputDelay(), + NETPLAY_CHECKSUM_INTERVAL); +} + +void +uninitChecksumBuffers(void) +{ + size_t player; + + for (player = 0; player < NETPLAY_NUM_PLAYERS; player++) + { + NetConnection *conn; + ChecksumBuffer *cb; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + cb = NetConnection_getChecksumBuffer(conn); + + ChecksumBuffer_uninit(cb); + } + + ChecksumBuffer_uninit(&localChecksumBuffer); +} + +void +addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum) { + assert(frameNr == battleFrameCount); + + ChecksumBuffer_addChecksum(&localChecksumBuffer, frameNr, checksum); +} + +void +addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum) { + ChecksumBuffer *cb; + + assert(frameNr <= battleFrameCount + getBattleInputDelay() + 1); + assert(frameNr + getBattleInputDelay() >= battleFrameCount); + + cb = NetConnection_getChecksumBuffer(conn); + ChecksumBuffer_addChecksum(cb, frameNr, checksum); +} + +bool +verifyChecksums(BattleFrameCounter frameNr) { + Checksum localChecksum; + size_t player; + + if (!ChecksumBuffer_getChecksum(&localChecksumBuffer, frameNr, + &localChecksum)) { + // Right now, we require that a checksum is present. + // If/when we move to UDP, and packets may get lost, we may prefer + // not to do any checks in this case. + return false; + } + + for (player = 0; player < NETPLAY_NUM_PLAYERS; player++) + { + NetConnection *conn; + ChecksumBuffer *cb; + Checksum remoteChecksum; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + cb = NetConnection_getChecksumBuffer(conn); + + if (!ChecksumBuffer_getChecksum(cb, frameNr, &remoteChecksum)) + return false; + + if (localChecksum != remoteChecksum) { + log_add(log_Error, "Network connections have gone out of " + "sync.\n"); + return false; + } + } + return true; +} + + +#endif /* NETPLAY_CHECKSUM */ + +#endif /* NETPLAY */ + diff --git a/sc2/src/sc2code/netplay/checksum.h b/sc2/src/sc2code/netplay/checksum.h new file mode 100644 index 000000000..128ac76bb --- /dev/null +++ b/sc2/src/sc2code/netplay/checksum.h @@ -0,0 +1,86 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CHECKSUM_H +#define _CHECKSUM_H + + +#include "types.h" + +typedef uint32 Checksum; + + +#include "crc.h" + +#include "element.h" +#include "libs/gfxlib.h" + +#include "netconnection.h" + + +static inline void +crc_processELEMENT_FLAGS(crc_State *state, ELEMENT_FLAGS val) { + crc_processUint16(state, (uint16) (val & ~(GOOD_GUY | BAD_GUY))); + // Excluding the element side, as this does not have to + // be the same for both players over a network. +} + +static inline void +crc_processCOUNT(crc_State *state, COUNT val) { + crc_processUint16(state, (uint16) val); +} + +static inline void +crc_processBYTE(crc_State *state, BYTE val) { + crc_processUint8(state, (uint8) val); +} + +static inline void +crc_processCOORD(crc_State *state, COORD val) { + crc_processUint16(state, (uint16) val); +} + +#if 0 +static inline void +crc_processTIME_VALUE(crc_State *state, const TIME_VALUE val) { + crc_processUint16(state, (uint16) val); +} +#endif + +void crc_processEXTENT(crc_State *state, const EXTENT *val); +void crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val); +#if 0 +void crc_processPOINT(crc_State *state, const POINT *val); +void crc_processSTAMP(crc_State *state, const STAMP *val); +void crc_processINTERSECT_CONTROL(crc_State *state, + const INTERSECT_CONTROL *val); +#endif +void crc_processELEMENT(crc_State *state, const ELEMENT *val); +void crc_processDispQueue(crc_State *state); + + +void initChecksumBuffers(void); +void uninitChecksumBuffers(void); +void addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum); +void addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum); +bool verifyChecksums(BattleFrameCounter frameNr); + +#endif /* _CHECKSUM_H */ + + diff --git a/sc2/src/sc2code/netplay/crc.c b/sc2/src/sc2code/netplay/crc.c new file mode 100644 index 000000000..f13628fc9 --- /dev/null +++ b/sc2/src/sc2code/netplay/crc.c @@ -0,0 +1,139 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "crc.h" + +#ifdef DUMP_CRC_OPS +# include "libs/log.h" +#endif + + +// CRC table for Polynomial 0x04c11db7 (0xedb88320 reversed) +uint32 crcTable[256] = { + 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 +}; + +void +crc_init(crc_State *state) { + state->crc = 0xffffffff; +} + +void +crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen) { + uint8 *end = buf + bufLen; + uint32 newCrc = state->crc; + + while (buf < end) + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ *buf) & 0xff]; + +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "crc_processBytes(%08x, [%z bytes]) --> %08x.", + state->crc, bufLen, newCrc); +#endif + state->crc = newCrc; +} + +void +crc_processUint8(crc_State *state, uint8 val) { + uint32 newCrc = state->crc; + + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ val) & 0xff]; +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "crc_processUint8(%08x, %02x) --> %08x.", + state->crc, (int) val, newCrc); +#endif + state->crc = newCrc; +} + +void +crc_processUint16(crc_State *state, uint16 val) { + uint32 newCrc = state->crc; + + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val >> 8) ) & 0xff]; +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "crc_processUint16(%08x, %04x) --> %08x.", + state->crc, (int) val, newCrc); +#endif + state->crc = newCrc; +} + +void +crc_processUint32(crc_State *state, uint32 val) { + uint32 newCrc = state->crc; + + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 8) & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 16) & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 24) )) & 0xff]; + +#ifdef DUMP_CRC_OPS + log_add(log_Debug, "crc_processUint32(%08x, %08x) --> %08x.", + state->crc, (int) val, newCrc); +#endif + state->crc = newCrc; +} + +uint32 +crc_finish(const crc_State *state) { + return ~state->crc; +} + + diff --git a/sc2/src/sc2code/netplay/crc.h b/sc2/src/sc2code/netplay/crc.h new file mode 100644 index 000000000..772200139 --- /dev/null +++ b/sc2/src/sc2code/netplay/crc.h @@ -0,0 +1,44 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CRC_H +#define _CRC_H + +#include "types.h" + +#include + +// If set, every CRC operation is logged. Very spammy. +#undef DUMP_CRC_OPS + + +typedef struct crc_State crc_State; +struct crc_State { + uint32 crc; +}; + +void crc_init(crc_State *state); +void crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen); +void crc_processUint8(crc_State *state, uint8 val); +void crc_processUint16(crc_State *state, uint16 val); +void crc_processUint32(crc_State *state, uint32 val); +uint32 crc_finish(const crc_State *state); + + +#endif /* _CRC_H */ + diff --git a/sc2/src/sc2code/netplay/nc_connect.ci b/sc2/src/sc2code/netplay/nc_connect.ci new file mode 100644 index 000000000..e4a2d6787 --- /dev/null +++ b/sc2/src/sc2code/netplay/nc_connect.ci @@ -0,0 +1,303 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netconnection.c, from where it is #included. + +static inline ConnectStateData *ConnectStateData_alloc(void); +static inline ConnectStateData *ConnectStateData_new(void); +static inline void ConnectStateData_free(ConnectStateData *connectStateData); +static void ConnectStateData_delete(ConnectStateData *connectStateData); + +static int NetConnection_go(NetConnection *conn); +static ListenState *NetConnection_serverGo(NetConnection *conn); +static ConnectState *NetConnection_clientGo(NetConnection *conn); +static void NetConnection_connected(NetConnection *conn); +static void NetConnection_connectedServerCallback(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, socklen_t addrLen); +static void NetConnection_connectedClientCallback(ConnectState *connectState, + NetDescriptor *newNd, const struct sockaddr *addr, socklen_t addrLen); +static void NetConnection_connected(NetConnection *conn); +static void NetConnection_connectedServerErrorCallback( + ListenState *listenState, const ListenError *listenError); +static void NetConnection_connectedClientErrorCallback( + ConnectState *connectState, const ConnectError *connectError); + + +//////////////////////////////////////////////////////////////////////////// + + +static inline ConnectStateData * +ConnectStateData_alloc(void) { + return (ConnectStateData *) malloc(sizeof (ConnectStateData)); +} + +static inline ConnectStateData * +ConnectStateData_new(void) { + ConnectStateData *connectStateData = ConnectStateData_alloc(); + connectStateData->releaseFunction = + (NetConnectionStateData_ReleaseFunction) ConnectStateData_delete; + return connectStateData; +} + +static inline void +ConnectStateData_free(ConnectStateData *connectStateData) { + free(connectStateData); +} + +static void +ConnectStateData_delete(ConnectStateData *connectStateData) { + if (connectStateData->isServer) { + ListenState *listenState = connectStateData->state.listenState; + ListenState_close(listenState); + } else { + ConnectState *connectState = connectStateData->state.connectState; + ConnectState_close(connectState); + } + ConnectStateData_free(connectStateData); +} + + +//////////////////////////////////////////////////////////////////////////// + + +static int +NetConnection_go(NetConnection *conn) { + ConnectStateData *connectStateData; + + if (NetConnection_isConnected(conn)) + return 0; + + if (conn->options->isServer) { + ListenState *listenState; + listenState = NetConnection_serverGo(conn); + if (listenState == NULL) { + // errno is set + return -1; + } + connectStateData = ConnectStateData_new(); + connectStateData->state.listenState = listenState; + } else { + ConnectState *connectState; + connectState = NetConnection_clientGo(conn); + if (connectState == NULL) { + // errno is set + return -1; + } + connectStateData = ConnectStateData_new(); + connectStateData->state.connectState = connectState; + } + connectStateData->isServer = conn->options->isServer; + + NetConnection_setStateData(conn, (void *) connectStateData); + return 0; +} + +static ListenState * +NetConnection_serverGo(NetConnection *conn) { + ListenFlags listenFlags; + ListenState *result; + + assert(conn->state == NetState_unconnected); + assert(conn->options->isServer); + + NetConnection_setState(conn, NetState_connecting); + + listenFlags.familyDemand = +#if NETPLAY == NETPLAY_IPV4 + PF_inet; +#else + PF_unspec; +#endif + listenFlags.familyPrefer = PF_unspec; + + result = listenPort(conn->options->port, IPProto_tcp, &listenFlags, + NetConnection_connectedServerCallback, + NetConnection_connectedServerErrorCallback, (void *) conn); + + return result; +} + +static ConnectState * +NetConnection_clientGo(NetConnection *conn) { + ConnectFlags connectFlags; + ConnectState *result; + + assert(conn->state == NetState_unconnected); + assert(!conn->options->isServer); + + NetConnection_setState(conn, NetState_connecting); + + connectFlags.familyDemand = +#if NETPLAY == NETPLAY_IPV4 + PF_inet; +#else + PF_unspec; +#endif + connectFlags.familyPrefer = PF_unspec; + connectFlags.timeout = NETPLAY_CONNECTTIMEOUT; + connectFlags.retryDelayMs = NETPLAY_RETRYDELAY; + + result = connectHostByName(conn->options->host, conn->options->port, + IPProto_tcp, &connectFlags, NetConnection_connectedClientCallback, + NetConnection_connectedClientErrorCallback, (void *) conn); + + return result; +} + +// Called when an incoming connection has been established. +// The caller gives up ownership of newNd +static void +NetConnection_connectedServerCallback(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, socklen_t addrLen) { + NetConnection *conn = + (NetConnection *) ListenState_getExtra(listenState); + + assert(conn->nd == NULL); + + conn->nd = newNd; + // No incRef(); the caller gives up ownership. + NetDescriptor_setExtra(conn->nd, (void *) conn); + + (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd)); + // Ignore errors; it's not a big deal. In debug mode, a message + // will already have been printed from the function itself. + + conn->stateFlags.myTurn = true; + // The "server" may speak first. + conn->stateFlags.discriminant = true; + + NetConnection_connected(conn); + (void) listenNd; + (void) addr; + (void) addrLen; +} + +// Called when an outgoing connection has been established. +// The caller gives up ownership of newNd +static void +NetConnection_connectedClientCallback(ConnectState *connectState, + NetDescriptor *newNd, + const struct sockaddr *addr, socklen_t addrLen) { + NetConnection *conn = + (NetConnection *) ConnectState_getExtra(connectState); + + assert(conn->nd == NULL); + + conn->nd = newNd; + // No incRef(); the caller gives up ownership. + NetDescriptor_setExtra(conn->nd, (void *) conn); + + (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd)); + // Ignore errors; it's not a big deal. In debug mode, a message + // will already have been printed from the function itself. + + conn->stateFlags.myTurn = false; + // The "server" may speak first. + conn->stateFlags.discriminant = false; + + NetConnection_connected(conn); + (void) addr; + (void) addrLen; +} + +// Called when a connection has been established. +static void +NetConnection_connected(NetConnection *conn) { + ConnectStateData *connectStateData; + + conn->stateFlags.connected = true; + NetConnection_setState(conn, NetState_init); + + connectStateData = (ConnectStateData *) NetConnection_getStateData(conn); + ConnectStateData_delete(connectStateData); + NetConnection_setStateData(conn, NULL); + + NetDescriptor_setReadCallback(conn->nd, dataReadyCallback); + NetDescriptor_setCloseCallback(conn->nd, closeCallback); + + (*conn->connectCallback)(conn); +} + +static void +NetConnection_connectedServerErrorCallback(ListenState *listenState, + const ListenError *listenError) { + NetConnection *conn = + (NetConnection *) ListenState_getExtra(listenState); + NetConnectionError error; + + + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = true; + ListenState_close(listenState); + NetConnection_setState(conn, NetState_unconnected); + + { + ConnectStateData *connectStateData; + connectStateData = + (ConnectStateData *) NetConnection_getStateData(conn); + ConnectStateData_free(connectStateData); + NetConnection_setStateData(conn, NULL); + } + + if (conn->errorCallback != NULL) { + error.state = NetState_connecting; + error.err = listenError->err; + error.extra.listenError = listenError; + //NetConnection_incRef(conn); + (*conn->errorCallback)(conn, &error); + //NetConnection_decRef(conn); + } + + NetConnection_close(conn); +} + +static void +NetConnection_connectedClientErrorCallback(ConnectState *connectState, + const ConnectError *connectError) { + NetConnection *conn = + (NetConnection *) ConnectState_getExtra(connectState); + NetConnectionError error; + + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = true; + ConnectState_close(connectState); + NetConnection_setState(conn, NetState_unconnected); + + { + ConnectStateData *connectStateData; + connectStateData = + (ConnectStateData *) NetConnection_getStateData(conn); + ConnectStateData_free(connectStateData); + NetConnection_setStateData(conn, NULL); + } + + if (conn->errorCallback != NULL) { + error.state = NetState_connecting; + error.err = connectError->err; + error.extra.connectError = connectError; + //NetConnection_incRef(conn); + (*conn->errorCallback)(conn, &error); + //NetConnection_decRef(conn); + } + + NetConnection_close(conn); +} + + diff --git a/sc2/src/sc2code/netplay/netconnection.c b/sc2/src/sc2code/netplay/netconnection.c new file mode 100644 index 000000000..f5c65e3e6 --- /dev/null +++ b/sc2/src/sc2code/netplay/netconnection.c @@ -0,0 +1,286 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "netconnection.h" + +#include "netrcv.h" + +#if defined(DEBUG) || defined(NETPLAY_DEBUG) +# include "libs/log.h" +#endif + +#include +#include + + +static void closeCallback(NetDescriptor *nd); +static void NetConnection_doClose(NetConnection *conn); + + +#include "nc_connect.ci" + + +// Used as initial value for Agreement structures, by structure assignment. +const Agreement Agreement_nothingAgreed; + + +// The NetConnection keeps a pointer to the passed NetplayPeerOptions; +// do not free it as long as the NetConnection exists. +NetConnection * +NetConnection_open(int player, const NetplayPeerOptions *options, + NetConnection_ConnectCallback connectCallback, + NetConnection_CloseCallback closeCallback, + NetConnection_ErrorCallback errorCallback, void *extra) { + NetConnection *conn; + + conn = malloc(sizeof (NetConnection)); + + conn->nd = NULL; + conn->player = player; + conn->state = NetState_unconnected; + conn->options = options; + conn->extra = extra; + PacketQueue_init(&conn->queue); + + conn->connectCallback = connectCallback; + conn->closeCallback = closeCallback; + conn->errorCallback = errorCallback; + conn->readyCallback = NULL; + conn->readyCallbackArg = NULL; + + conn->readBuf = malloc(NETPLAY_READBUFSIZE); + conn->readEnd = conn->readBuf; + + conn->stateData = NULL; + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = false; + conn->stateFlags.myTurn = false; + conn->stateFlags.endingTurn = false; + conn->stateFlags.pendingTurnChange = false; + conn->stateFlags.discriminant = false; + conn->stateFlags.handshake.localOk = false; + conn->stateFlags.handshake.remoteOk = false; + conn->stateFlags.handshake.canceling = false; + conn->stateFlags.ready.localReady = false; + conn->stateFlags.ready.remoteReady = false; + conn->stateFlags.agreement = Agreement_nothingAgreed; + conn->stateFlags.inputDelay = 0; +#ifdef NETPLAY_CHECKSUM + conn->stateFlags.checksumInterval = NETPLAY_CHECKSUM_INTERVAL; +#endif + +#ifdef NETPLAY_STATISTICS + { + size_t i; + + conn->statistics.packetsReceived = 0; + conn->statistics.packetsSent = 0; + for (i = 0; i < PACKET_NUM; i++) + { + conn->statistics.packetTypeReceived[i] = 0; + conn->statistics.packetTypeSent[i] = 0; + } + } +#endif + + NetConnection_go(conn); + + return conn; +} + +static void +NetConnection_delete(NetConnection *conn) { + free(conn->readBuf); + PacketQueue_uninit(&conn->queue); + free(conn); +} + +static void +Netplay_doCloseCallback(NetConnection *conn) { + if (conn->closeCallback != NULL) { + //NetConnection_incRef(conn); + conn->closeCallback(conn); + //NetConnection_decRef(conn); + } +} + +// Auxiliary function for closing, used by both closeCallback() and +// NetConnection_close() +static void +NetConnection_doClose(NetConnection *conn) { + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = true; + + // First the callback, so that it can still use the information + // of what is the current state, and the stateData: + Netplay_doCloseCallback(conn); + + NetConnection_setState(conn, NetState_unconnected); + if (conn->stateData != NULL) { + NetConnectionStateData_release(conn->stateData); + conn->stateData = NULL; + } +} + +// Called when the NetDescriptor is shut down. +static void +closeCallback(NetDescriptor *nd) { + NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd); + if (conn == NULL) + return; + conn->nd = NULL; + NetConnection_doClose(conn); +} + +// Close and release a NetDescriptor. +void +NetConnection_close(NetConnection *conn) { + if (conn->nd != NULL) { + NetDescriptor_setCloseCallback(conn->nd, NULL); + // We're not interested in the close callback of the + // NetDescriptor anymore. + NetDescriptor_close(conn->nd); + // This would queue the close callback. + conn->nd = NULL; + } + NetConnection_doClose(conn); + NetConnection_delete(conn); +} + +void +NetConnection_doErrorCallback(NetConnection *nd, int err) { + NetConnectionError error; + + if (nd->errorCallback != NULL) { + error.state = nd->state; + error.err = err; + } + (*nd->errorCallback)(nd, &error); +} + +void +NetConnection_setStateData(NetConnection *conn, + NetConnectionStateData *stateData) { + conn->stateData = stateData; +} + +NetConnectionStateData * +NetConnection_getStateData(const NetConnection *conn) { + return conn->stateData; +} + +void +NetConnection_setExtra(NetConnection *conn, void *extra) { + conn->extra = extra; +} + +void * +NetConnection_getExtra(const NetConnection *conn) { + return conn->extra; +} + +void +NetConnection_setReadyCallback(NetConnection *conn, + NetConnection_ReadyCallback callback, void *arg) { + conn->readyCallback = callback; + conn->readyCallbackArg = arg; +} + +NetConnection_ReadyCallback +NetConnection_getReadyCallback(const NetConnection *conn) { + return conn->readyCallback; +} + +void * +NetConnection_getReadyCallbackArg(const NetConnection *conn) { + return conn->readyCallbackArg; +} + +void +NetConnection_setState(NetConnection *conn, NetState state) { +#ifdef NETPLAY_DEBUG + log_add(log_Debug, "NETPLAY: [%d] +/- Connection state changed to: " + "%s.\n", conn->player, netStateData[state].name); +#endif +#ifdef DEBUG + if (state == conn->state) { + log_add(log_Warning, "NETPLAY: [%d] Connection state set to %s " + "while already in that state.\n", + conn->player, netStateData[state].name); + } +#endif + conn->state = state; +} + +NetState +NetConnection_getState(const NetConnection *conn) { + return conn->state; +} + +bool +NetConnection_getDiscriminant(const NetConnection *conn) { + return conn->stateFlags.discriminant; +} + +const NetplayPeerOptions * +NetConnection_getPeerOptions(const NetConnection *conn) { + return conn->options; +} + +bool +NetConnection_isConnected(const NetConnection *conn) { + return conn->stateFlags.connected; +} + +bool +NetConnection_isMyTurn(const NetConnection *conn) { + return conn->stateFlags.myTurn; +} + +int +NetConnection_getPlayerNr(const NetConnection *conn) { + return conn->player; +} + +size_t +NetConnection_getInputDelay(const NetConnection *conn) { + return conn->stateFlags.inputDelay; +} + +#ifdef NETPLAY_CHECKSUM +ChecksumBuffer * +NetConnection_getChecksumBuffer(NetConnection *conn) { + return &conn->checksumBuffer; +} + +size_t +NetConnection_getChecksumInterval(const NetConnection *conn) { + return conn->stateFlags.checksumInterval; +} +#endif /* NETPLAY_CHECKSUM */ + +#ifdef NETPLAY_STATISTICS +NetStatistics * +NetConnection_getStatistics(NetConnection *conn) { + return &conn->statistics; +} +#endif + + diff --git a/sc2/src/sc2code/netplay/netconnection.h b/sc2/src/sc2code/netplay/netconnection.h new file mode 100644 index 000000000..4393012a7 --- /dev/null +++ b/sc2/src/sc2code/netplay/netconnection.h @@ -0,0 +1,209 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETCONNECTION_H +#define _NETCONNECTION_H + +#include "netplay.h" + // for NETPLAY_STATISTICS + +typedef struct NetConnection NetConnection; +typedef struct NetConnectionError NetConnectionError; +typedef struct ConnectStateData ConnectStateData; +#ifdef NETPLAY_STATISTICS +typedef struct NetStatistics NetStatistics; +#endif + +typedef void (*NetConnection_ConnectCallback)(NetConnection *nd); +typedef void (*NetConnection_CloseCallback)(NetConnection *nd); +typedef void (*NetConnection_ErrorCallback)(NetConnection *nd, + const NetConnectionError *error); + +typedef void (*NetConnection_ReadyCallback)(NetConnection *conn, void *arg); + +#include "netstate.h" +#include "netoptions.h" +#ifdef NETPLAY_CHECKSUM +# include "checkbuf.h" +#endif + +struct NetConnectionError { + NetState state; + int err; + union { + const struct ListenError *listenError; + const struct ConnectError *connectError; + } extra; +}; + +#ifdef NETPLAY_STATISTICS +#include "packet.h" + +struct NetStatistics { + size_t packetsReceived; + size_t packetTypeReceived[PACKET_NUM]; + size_t packetsSent; + size_t packetTypeSent[PACKET_NUM]; +}; +#endif + +#ifdef NETCONNECTION_INTERNAL +#include "libs/net.h" +#include "packetq.h" + +typedef struct { + // For actions that require agreement by both parties. + bool localOk : 1; /* Action confirmed by us */ + bool remoteOk : 1; /* Action confirmed by the remote party */ + bool canceling : 1; /* Awaiting cancel confirmation */ +} HandShakeFlags; + +typedef struct { + // For actions that do not require agreement, but for which it + // only is relevant that both sides are ready. + bool localReady : 1; + bool remoteReady : 1; +} ReadyFlags; + +// Which parameters have we both sides of a connection reached agreement on? +typedef struct { + bool randomSeed : 1; +} Agreement; + +typedef struct { + bool connected; + /* This NetConnection is connected. */ + bool disconnected; + /* This NetConnection has been disconnected. This implies + * !connected. It is only set if the NetConnection was once + * connected, but is no longer. */ + bool myTurn; + /* This party is the only one who may send specific packets + * at the moment. */ + bool endingTurn; + /* Request to change the turn from/to the other party. */ + bool pendingTurnChange; + /* It is our turn and the other party wants our turn, + * but we're not ready to give it up just yet; we will first + * send our pending packets. */ + bool discriminant; + /* If it is true here, it is false on the remote side + * of the same connection. It may be used to break ties. + * Unlike myTurn, this one is guaranteed not to change + * during a connection. Undefined while not connected. */ + HandShakeFlags handshake; + ReadyFlags ready; + Agreement agreement; + size_t inputDelay; + /* Used during negotiation of the actual inputDelay. This + * field does NOT necessarilly contain the actual input delay, + * which is a property of the game, not of any specific + * connection. Use getBattleInputDelay() to get at it. */ +#ifdef NETPLAY_CHECKSUM + size_t checksumInterval; +#endif +} NetStateFlags; + +struct NetConnection { + NetDescriptor *nd; + int player; + // Number of the player for this connection, as it is + // known locally. For the other player, it may be + // differently. + NetState state; + NetStateFlags stateFlags; + + NetConnection_ReadyCallback readyCallback; + // Called when both sides have indicated that they are ready. + // Set by Netplay_localReady(). + void *readyCallbackArg; + // Extra argument for readyCallback(). + // XXX: when is this cleaned up if a connection is broken? + + const NetplayPeerOptions *options; + PacketQueue queue; +#ifdef NETPLAY_STATISTICS + NetStatistics statistics; +#endif +#ifdef NETPLAY_CHECKSUM + ChecksumBuffer checksumBuffer; +#endif + NetConnection_ConnectCallback connectCallback; + NetConnection_CloseCallback closeCallback; + NetConnection_ErrorCallback errorCallback; + uint8 *readBuf; + uint8 *readEnd; + NetConnectionStateData *stateData; + // State dependant information. + void *extra; +}; + +struct ConnectStateData { + NETCONNECTION_STATE_DATA_COMMON + + bool isServer; + union { + struct ConnectState *connectState; + struct ListenState *listenState; + } state; +}; + +#endif /* NETCONNECTION_INTERNAL */ + + +NetConnection *NetConnection_open(int player, + const NetplayPeerOptions *options, + NetConnection_ConnectCallback connectCallback, + NetConnection_CloseCallback closeCallback, + NetConnection_ErrorCallback errorCallback, void *extra); +void NetConnection_close(NetConnection *conn); +bool NetConnection_isConnected(const NetConnection *conn); + +void NetConnection_doErrorCallback(NetConnection *nd, int err); + +bool NetConnection_isMyTurn(const NetConnection *conn); +void NetConnection_setStateData(NetConnection *conn, + NetConnectionStateData *stateData); +NetConnectionStateData *NetConnection_getStateData(const NetConnection *conn); +void NetConnection_setExtra(NetConnection *conn, void *extra); +void *NetConnection_getExtra(const NetConnection *conn); +void NetConnection_setState(NetConnection *conn, NetState state); +NetState NetConnection_getState(const NetConnection *conn); +bool NetConnection_getDiscriminant(const NetConnection *conn); +const NetplayPeerOptions *NetConnection_getPeerOptions( + const NetConnection *conn); +int NetConnection_getPlayerNr(const NetConnection *conn); +size_t NetConnection_getInputDelay(const NetConnection *conn); +#ifdef NETPLAY_CHECKSUM +ChecksumBuffer *NetConnection_getChecksumBuffer(NetConnection *conn); +size_t NetConnection_getChecksumInterval(const NetConnection *conn); +#endif +#ifdef NETPLAY_STATISTICS +NetStatistics *NetConnection_getStatistics(NetConnection *conn); +#endif + +void NetConnection_setReadyCallback(NetConnection *conn, + NetConnection_ReadyCallback callback, void *arg); +NetConnection_ReadyCallback NetConnection_getReadyCallback( + const NetConnection *conn); +void *NetConnection_getReadyCallbackArg(const NetConnection *conn); + + +#endif /* _NETCONNECTION_H */ + + diff --git a/sc2/src/sc2code/netplay/netinput.c b/sc2/src/sc2code/netplay/netinput.c new file mode 100644 index 000000000..2bbc07db5 --- /dev/null +++ b/sc2/src/sc2code/netplay/netinput.c @@ -0,0 +1,160 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#include "netplay.h" +#include "netinput.h" + +#include "../intel.h" + // for NETWORK_CONTROL +#include "../setup.h" + // For PlayerControl +#include "libs/log.h" + +#include +#include + + +static BattleInputBuffer battleInputBuffers[NUM_PLAYERS]; +static size_t BattleInput_inputDelay; + +// Call before initBattleInputBuffers() +void +setBattleInputDelay(size_t delay) { + BattleInput_inputDelay = delay; +} + +size_t +getBattleInputDelay(void) { + return BattleInput_inputDelay; +} + +static void +BattleInputBuffer_init(BattleInputBuffer *bib, size_t bufSize) { + bib->buf = malloc(bufSize * sizeof (BATTLE_INPUT_STATE)); + bib->maxSize = bufSize; + bib->first = 0; + bib->size = 0; +} + +static void +BattleInputBuffer_uninit(BattleInputBuffer *bib) { + if (bib->buf != NULL) { + free(bib->buf); + bib->buf = NULL; + } + bib->maxSize = 0; + bib->first = 0; + bib->size = 0; +} + +void +initBattleInputBuffers(void) { + size_t player; + int bufSize = BattleInput_inputDelay * 2 + 2; + + // The input of frame n will be processed in frame 'n + delay'. + // + // In the worst case, side 1 processes frames 'n' through 'n + delay - 1', + // then blocks in frame 'n + delay'. + // Then side 2 receives all this input, and progresses to 'delay' frames + // after the last frame the received input originated from, and blocks + // in the frame after it. + // So it sent input for frames 'n' through 'n + delay + delay + 1'. + // The input for these '2*delay + 2' frames are still + // unhandled by side 1, so it needs buffer space for this. + // + // Initially the buffer is filled with inputDelay zeroes, + // so that a party can process at least that much frames. + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleInputBuffer *bib = &battleInputBuffers[player]; + BattleInputBuffer_init(bib, bufSize); + + { + // Initially a party must be able to process at least inputDelay + // frames, so we fill the buffer with inputDelay zeros. + size_t i; + for (i = 0; i < BattleInput_inputDelay; i++) + BattleInputBuffer_push(bib, (BATTLE_INPUT_STATE) 0); + } + } +} + +void +uninitBattleInputBuffers(void) +{ + size_t player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleInputBuffer *bib; + + if (!(PlayerControl[player] & NETWORK_CONTROL)) + continue; + + bib = &battleInputBuffers[player]; + BattleInputBuffer_uninit(bib); + } +} + +// On error, returns false and sets errno. +bool +BattleInputBuffer_push(BattleInputBuffer *bib, BATTLE_INPUT_STATE input) +{ + size_t next; + + if (bib->size == bib->maxSize) { + // No more space. + log_add(log_Error, "NETPLAY: battleInputBuffer full.\n"); + errno = ENOBUFS; + return false; + } + + next = (bib->first + bib->size) % bib->maxSize; + bib->buf[next] = input; + bib->size++; + return true; +} + +// On error, returns false and sets errno, and *input remains unchanged. +bool +BattleInputBuffer_pop(BattleInputBuffer *bib, BATTLE_INPUT_STATE *input) +{ + if (bib->size == 0) + { + // Buffer is empty. + errno = EAGAIN; + return false; + } + + *input = bib->buf[bib->first]; + bib->first = (bib->first + 1) % bib->maxSize; + bib->size--; + return true; +} + +BattleInputBuffer * +getBattleInputBuffer(size_t player) { + return &battleInputBuffers[player]; +} + + diff --git a/sc2/src/sc2code/netplay/netinput.h b/sc2/src/sc2code/netplay/netinput.h new file mode 100644 index 000000000..69389f4da --- /dev/null +++ b/sc2/src/sc2code/netplay/netinput.h @@ -0,0 +1,50 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETINPUT_H +#define _NETINPUT_H + +#include "../controls.h" + // for BATTLE_INPUT_STATE +#include "../init.h" + // for NUM_PLAYERS + +typedef struct BattleInputBuffer { + BATTLE_INPUT_STATE *buf; + // Cyclic buffer. if 'size' > 0, then 'first' is an index to + // the first used entry, 'size' is the number of used + // entries in the buffer, and (first + size) % maxSize is the + // index to just past the end of the buffer. + size_t maxSize; + size_t first; + size_t size; +} BattleInputBuffer; + +void setBattleInputDelay(size_t delay); +size_t getBattleInputDelay(void); +void initBattleInputBuffers(void); +void uninitBattleInputBuffers(void); +bool BattleInputBuffer_push(BattleInputBuffer *bib, + BATTLE_INPUT_STATE input); +bool BattleInputBuffer_pop(BattleInputBuffer *bib, + BATTLE_INPUT_STATE *input); + +BattleInputBuffer *getBattleInputBuffer(size_t player); + +#endif /* _NETINPUT_H */ + diff --git a/sc2/src/sc2code/netplay/netmelee.c b/sc2/src/sc2code/netplay/netmelee.c new file mode 100644 index 000000000..12baae743 --- /dev/null +++ b/sc2/src/sc2code/netplay/netmelee.c @@ -0,0 +1,557 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netmelee.h" +#include "libs/alarm.h" +#include "libs/callback.h" +#include "libs/log.h" +#include "libs/net.h" +#include "netplay/netinput.h" +#include "netplay/netmisc.h" +#include "netplay/netsend.h" +#include "netplay/notify.h" +#include "netplay/packetq.h" +#include "netplay/proto/npconfirm.h" +#include "netplay/proto/ready.h" + +#include "build.h" + // for StarShipPlayer() +#include "controls.h" + // for BATTLE_INPUT_STATE +#include "init.h" + // for NUM_PLAYERS +#include "globdata.h" + // for GLOBAL + +#include +#include + + +//////////////////////////////////////////////////////////////////////////// + + +NetConnection *netConnections[NUM_PLAYERS]; +size_t numNetConnections; + +void +addNetConnection(NetConnection *conn, int playerNr) { + netConnections[playerNr] = conn; + numNetConnections++; +} + +void +removeNetConnection(int playerNr) { + netConnections[playerNr] = NULL; + numNetConnections--; +} + +void +closeAllConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + if (netConnections[player] != NULL) + closePlayerNetworkConnection(player); +} + +size_t +getNumNetConnections(void) { + return numNetConnections; +} + + +//////////////////////////////////////////////////////////////////////////// + + +// Check the network connections for input. +void +netInput(void) { + uint32 timeoutMs = 0; + NetManager_process(&timeoutMs); + + Alarm_process(); + Callback_process(); +} + +void +netInputBlocking(uint32 timeoutMs) { + uint32 nextAlarmMs; + + nextAlarmMs = Alarm_timeBeforeNextMs(); + if (nextAlarmMs < timeoutMs) + timeoutMs = nextAlarmMs; + + NetManager_process(&timeoutMs); + + Alarm_process(); + Callback_process(); +} + + +//////////////////////////////////////////////////////////////////////////// + + +// Send along all pending network packets. +void +flushPacketQueues(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn; + int flushStatus; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + flushStatus = flushPacketQueue(conn); + if (flushStatus == -1 && errno != EAGAIN) + closePlayerNetworkConnection(player); + } +} + +void +confirmConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_confirm(conn); + } +} + +void +cancelConfirmations(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_cancelConfirmation(conn); + } +} + +void +connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_localReady(conn, callback, arg, true); + } +} + +void +sendBattleInputConnections(BATTLE_INPUT_STATE input) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_battleInput(conn, input); + } +} + +#ifdef NETPLAY_CHECKSUM +void +sendChecksumConnections(uint32 frameNr, uint32 checksum) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_sendChecksum(conn, frameNr, checksum); + } +} +#endif /* NETPLAY_CHECKSUM */ + +void +initBattleStateDataConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleStateData *battleStateData; + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + battleStateData->endFrameCount = 0; + } +} + +BATTLE_INPUT_STATE +networkBattleInput(COUNT player, STARSHIPPTR StarShipPtr) { + BattleInputBuffer *bib = getBattleInputBuffer(player); + BATTLE_INPUT_STATE result; + + for (;;) { + bool ok = BattleInputBuffer_pop(bib, &result); + // Get the input from the front of the + // buffer. + if (ok) + break; + + { + NetConnection *conn = netConnections[player]; + + // First try whether there is incoming data, without blocking. + // If there isn't any, only then give a warning, and then + // block after all. + netInput(); + if (!NetConnection_isConnected(conn)) + { + // Connection aborted. + GLOBAL(CurrentActivity) |= CHECK_ABORT; + return(BATTLE_INPUT_STATE) 0; + } + +#if 0 + log_add(log_Warning, "NETPLAY: [%d] stalling for " + "network input. Increase the input delay if this " + "happens a lot.\n", player); +#endif +#define MAX_BLOCK_TIME 500 + netInputBlocking(MAX_BLOCK_TIME); + if (!NetConnection_isConnected(conn)) + { + // Connection aborted. + GLOBAL(CurrentActivity) |= CHECK_ABORT; + return(BATTLE_INPUT_STATE) 0; + } + } + } + + (void) StarShipPtr; + return result; +} + +NetConnection * +openPlayerNetworkConnection(COUNT player, void *extra) { + NetConnection *conn; + + assert(netConnections[player] == NULL); + + conn = NetConnection_open(player, + &netplayOptions.peer[player], NetMelee_connectCallback, + NetMelee_closeCallback, NetMelee_errorCallback, extra); + + addNetConnection(conn, player); + return conn; +} + +void +shutdownPlayerNetworkConnection(COUNT player) { + NetConnection_close(netConnections[player]); +} + +void +closePlayerNetworkConnection(COUNT player) { + assert(netConnections[player] != NULL); + + NetConnection_close(netConnections[player]); + removeNetConnection(player); +} + +// If the callback function returns 'false', the function will immediately +// return with 'false'. Otherwise it will return 'true' after calling +// the callback function for each connected player. +bool +forAllConnectedPlayers(ForAllCallback callback, void *arg) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + if (!(*callback)(conn, arg)) + return false; + } + return true; +} + +bool +setupInputDelay(size_t localInputDelay) { + COUNT player; + size_t inputDelay = localInputDelay; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + if (NetConnection_getInputDelay(conn) > inputDelay) + inputDelay = NetConnection_getInputDelay(conn); + } + + setBattleInputDelay(inputDelay); + return true; +} + +static bool +sendInputDelayConnection(NetConnection *conn, const size_t *delay) { + Netplay_sendInputDelay(conn, *delay); + return true; +} + +bool +sendInputDelayConnections(size_t delay) { + return forAllConnectedPlayers( + (ForAllCallback) sendInputDelayConnection, &delay); +} + +static bool +setStateConnection(NetConnection *conn, const NetState *state) { + NetConnection_setState(conn, *state); + return true; +} + +bool +setStateConnections(NetState state) { + return forAllConnectedPlayers( + (bool(*)(NetConnection *, void *)) setStateConnection, &state); +} + +///////////////////////////////////////////////////////////////////////////// + +typedef struct { + NetConnection_ReadyCallback readyCallback; + void *readyCallbackArg; + bool notifyRemote; +} LocalReadyConnectionArg; + +static bool +localReadyConnection(NetConnection *conn, LocalReadyConnectionArg *arg) { + Netplay_localReady(conn, arg->readyCallback, arg->readyCallbackArg, + arg->notifyRemote); + return true; +} + +bool +localReadyConnections(NetConnection_ReadyCallback readyCallback, + void *readyArg, bool notifyRemote) { + LocalReadyConnectionArg arg; + arg.readyCallback = readyCallback; + arg.readyCallbackArg = readyArg; + arg.notifyRemote = notifyRemote; + + return forAllConnectedPlayers( + (bool(*)(NetConnection *, void *)) localReadyConnection, &arg); +} + + +///////////////////////////////////////////////////////////////////////////// + + +typedef struct NegotiateReadyState NegotiateReadyState; +struct NegotiateReadyState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(PVOID pInputState); + COUNT MenuRepeatDelay; + + NetConnection *conn; + NetState nextState; + bool done; +}; + +static BOOLEAN +negotiateReadyInputFunc(NegotiateReadyState *state) { +#define NETWORK_POLL_DELAY (ONE_SECOND / 24) + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need negotiateReadyInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +// Called when both sides are ready +static void +negotiateReadyBothReadyCallback(NetConnection *conn, void *arg) { + NegotiateReadyState *state =(NegotiateReadyState *) arg; + + NetConnection_setState(conn, state->nextState); + // This has to be done immediately, as more packets in the + // receive queue may be handled by the netInput() call that + // triggered this callback. + // This is the reason for the nextState argument to + // negotiateReady(); setting the state after the call to + // negotiateReady() would be too late. + state->done = true; +} + +bool +negotiateReady(NetConnection *conn, bool notifyRemote, NetState nextState) { + NegotiateReadyState state; + state.InputFunc = (BOOLEAN(*)(void *)) negotiateReadyInputFunc; + state.MenuRepeatDelay = 0; + state.conn = conn; + state.nextState = nextState; + state.done = false; + + Netplay_localReady(conn, negotiateReadyBothReadyCallback, + (void *) &state, notifyRemote); + flushPacketQueue(conn); + if (!state.done) + DoInput(&state, FALSE); + + return NetConnection_isConnected(conn); +} + +// Wait for all connections to get ready. +// XXX: Right now all connections are handled one by one. Handling them all +// at once would be faster but would require more work, which is +// not worth it as the time is minimal and this function is not +// time critical. +bool +negotiateReadyConnections(bool notifyRemote, NetState nextState) { + COUNT player; + size_t numDisconnected = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) { + numDisconnected++; + continue; + } + + negotiateReady(conn, notifyRemote, nextState); + } + + return numDisconnected == 0; +} + +typedef struct WaitReadyState WaitReadyState; +struct WaitReadyState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(PVOID pInputState); + COUNT MenuRepeatDelay; + + NetConnection *conn; + NetConnection_ReadyCallback readyCallback; + void *readyCallbackArg; + bool done; +}; + +static void +waitReadyCallback(NetConnection *conn, void *arg) { + WaitReadyState *state =(WaitReadyState *) arg; + state->done = true; + + // Call the original callback. + state->readyCallback(conn, state->readyCallbackArg); +} + +static BOOLEAN +waitReadyInputFunc(WaitReadyState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need negotiateReadyInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +bool +waitReady(NetConnection *conn) { + WaitReadyState state; + state.InputFunc =(BOOLEAN(*)(void *)) waitReadyInputFunc; + state.MenuRepeatDelay = 0; + state.conn = conn; + state.readyCallback = NetConnection_getReadyCallback(conn); + state.readyCallbackArg = NetConnection_getReadyCallbackArg(conn); + state.done = false; + + NetConnection_setReadyCallback(conn, waitReadyCallback, (void *) &state); + + DoInput(&state, FALSE); + + return NetConnection_isConnected(conn); +} + + +//////////////////////////////////////////////////////////////////////////// + + diff --git a/sc2/src/sc2code/netplay/netmelee.h b/sc2/src/sc2code/netplay/netmelee.h new file mode 100644 index 000000000..d6f3202d7 --- /dev/null +++ b/sc2/src/sc2code/netplay/netmelee.h @@ -0,0 +1,72 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(_NETMELEE_H) && defined(NETPLAY) +#define _NETMELEE_H + +#include "netplay.h" +#include "netinput.h" +#include "netconnection.h" + +#include "../controls.h" + // for BATTLE_INPUT_STATE + + +extern struct NetConnection *netConnections[]; + + +void addNetConnection(NetConnection *conn, int playerNr); +void removeNetConnection(int playerNr); +void closeAllConnections(void); +void closeAllConnections(void); +size_t getNumNetConnections(void); + +void netInput(void); +void netInputBlocking(uint32 timeoutMs); +void flushPacketQueues(void); + +void confirmConnections(void); +void cancelConfirmations(void); +void connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg); + +void sendBattleInputConnections(BATTLE_INPUT_STATE input); +void sendChecksumConnections(uint32 frameNr, uint32 checksum); +void initBattleStateDataConnections(void); + +BATTLE_INPUT_STATE networkBattleInput(COUNT player, STARSHIPPTR StarShipPtr); + +NetConnection *openPlayerNetworkConnection(COUNT player, void *extra); +void closePlayerNetworkConnection(COUNT player); + +typedef bool(*ForAllCallback)(NetConnection *conn, void *arg); +bool forAllConnectedPlayers(ForAllCallback callback, void *arg); + +bool setupInputDelay(size_t localInputDelay); +bool sendInputDelayConnections(size_t delay); +bool setStateConnections(NetState state); +bool localReadyConnections(NetConnection_ReadyCallback readyCallback, + void *arg, bool notifyRemote); + +bool negotiateReady(NetConnection *conn, bool notifyRemote, + NetState nextState); +bool negotiateReadyConnections(bool notifyRemote, NetState nextState); +bool waitReady(NetConnection *conn); + +#endif /* _NETMELEE_H */ + + diff --git a/sc2/src/sc2code/netplay/netmisc.c b/sc2/src/sc2code/netplay/netmisc.c new file mode 100644 index 000000000..6827baf59 --- /dev/null +++ b/sc2/src/sc2code/netplay/netmisc.c @@ -0,0 +1,174 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "netmisc.h" + +#include "netmelee.h" +#include "packetsenders.h" +#include "proto/ready.h" + +#include "melee.h" + // For feedback functions. + +#include + + +static BattleStateData *BattleStateData_alloc(void); +static void BattleStateData_free(BattleStateData *battleStateData); +static inline BattleStateData *BattleStateData_new( + struct melee_state *meleeState, + struct getmelee_struct *getMeleeState); +static void BattleStateData_delete(BattleStateData *battleStateData); + + +static BattleStateData * +BattleStateData_alloc(void) { + return (BattleStateData *) malloc(sizeof (BattleStateData)); +} + +static void +BattleStateData_free(BattleStateData *battleStateData) { + free(battleStateData); +} + +static inline BattleStateData * +BattleStateData_new(struct melee_state *meleeState, + struct getmelee_struct *getMeleeState) { + BattleStateData *battleStateData = BattleStateData_alloc(); + battleStateData->releaseFunction = + (NetConnectionStateData_ReleaseFunction) BattleStateData_delete; + battleStateData->meleeState = meleeState; + battleStateData->getMeleeState = getMeleeState; + return battleStateData; +} + +static void +BattleStateData_delete(BattleStateData *battleStateData) { + BattleStateData_free(battleStateData); +} + + +//////////////////////////////////////////////////////////////////////////// + + +static void NetMelee_enterState_inSetup(NetConnection *conn, void *arg); + +// Called when a connection has been established. +void +NetMelee_connectCallback(NetConnection *conn) { + BattleStateData *battleStateData; + struct melee_state *meleeState; + + meleeState = (struct melee_state *) NetConnection_getExtra(conn); + battleStateData = BattleStateData_new(meleeState, NULL); + NetConnection_setStateData(conn, (void *) battleStateData); + NetConnection_setExtra(conn, NULL); + + sendInit(conn); + Netplay_localReady (conn, NetMelee_enterState_inSetup, NULL, false); +} + +// Called when a connection is closed. +void +NetMelee_closeCallback(NetConnection *conn) { + struct melee_state *meleeState; + + if (NetConnection_getState(conn) > NetState_connecting) { + BattleStateData *battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + meleeState = battleStateData->meleeState; + } else { + meleeState = (struct melee_state *) NetConnection_getExtra(conn); + } + + closeFeedback(meleeState, NetConnection_getPlayerNr(conn)); +} + +// Called when a network error occurs during connect. +void +NetMelee_errorCallback(NetConnection *conn, + const NetConnectionError *error) { + void *meleeState; + + meleeState = NetConnection_getExtra(conn); + errorFeedback(meleeState, NetConnection_getPlayerNr(conn)); + + (void) error; +} + +// Callback function for when both sides have finished initialisation after +// initial connect. +static void +NetMelee_enterState_inSetup(NetConnection *conn, void *arg) { + BattleStateData *battleStateData; + struct melee_state *meleeState; + int player; + + NetConnection_setState(conn, NetState_inSetup); + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + meleeState = battleStateData->meleeState; + + player = NetConnection_getPlayerNr(conn); + + connectedFeedback(meleeState, player); + + entireFleetChanged(meleeState, player); + teamStringChanged(meleeState, player); + + flushPacketQueues(); + + (void) arg; +} + +// Callback function for when both sides have confirmed that the battle +// has ended. +void +NetMelee_reenterState_inSetup(NetConnection *conn) { + BattleStateData *battleStateData; + struct melee_state *meleeState; + + NetConnection_setState(conn, NetState_inSetup); + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + meleeState = battleStateData->meleeState; + + // The player who entered the menu first should send his changes over + // to the other side when the other player enters setup too. + // As myTurn is set when a change is made, and will never be unset + // until the other side makes a change, it can be used to determine + // which side was first. In the case there were no changes made, myTurn + // may be the second player, but in this case it doesn't matter + // who sends what to whom. + if (NetConnection_isMyTurn(conn)) + { + size_t side; + + for (side = 0; side < NUM_SIDES; side++) + { + entireFleetChanged(meleeState, side); + teamStringChanged(meleeState, side); + } + flushPacketQueues(); + } +} + + + + diff --git a/sc2/src/sc2code/netplay/netmisc.h b/sc2/src/sc2code/netplay/netmisc.h new file mode 100644 index 000000000..ad7d1da20 --- /dev/null +++ b/sc2/src/sc2code/netplay/netmisc.h @@ -0,0 +1,70 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETMISC_H +#define _NETMISC_H + +typedef struct BattleStateData BattleStateData; + +#include "netconnection.h" +#include "netstate.h" +#include "types.h" + +#include "battle.h" + // for BattleFrameCounter + +struct BattleStateData { + NETCONNECTION_STATE_DATA_COMMON + + struct melee_state *meleeState; + struct getmelee_struct *getMeleeState; + BattleFrameCounter endFrameCount; +}; + + +void NetMelee_connectCallback(NetConnection *conn); +void NetMelee_closeCallback(NetConnection *conn); +void NetMelee_errorCallback(NetConnection *conn, + const NetConnectionError *error); + +void NetMelee_reenterState_inSetup(NetConnection *conn); + + +// Returns true iff the connection is in a state where the confirmation +// handshake is meaningful. Right now this is only when we're in the +// pre-game setup menu. +static inline bool +handshakeMeaningful(NetState state) { + return state == NetState_inSetup; +} + +static inline bool +readyFlagsMeaningful(NetState state) { + return state == NetState_init || + state == NetState_preBattle || + state == NetState_interBattle || + state == NetState_inBattle || + state == NetState_endingBattle || + state == NetState_endingBattle2 || + state == NetState_endMelee; +} + + +#endif /* _NETMISC_H */ + + diff --git a/sc2/src/sc2code/netplay/netoptions.c b/sc2/src/sc2code/netplay/netoptions.c new file mode 100644 index 000000000..cc97dfdbf --- /dev/null +++ b/sc2/src/sc2code/netplay/netoptions.c @@ -0,0 +1,39 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netoptions.h" + +NetplayOptions netplayOptions = { + /* .metaServer = */ "uqm.stack.nl", + /* .metaPort = */ "21836", + /* .peer = */ { + /* [0] Player 1 (bottom) */ { + /* .isServer = */ true, + /* .host = */ "localhost", + /* .port = */ "21837" /* 0x554d - "UM" */, + }, + /* [1] Player 2 (top) */ { + /* .isServer = */ true, + /* .host = */ "localhost", + /* .port = */ "21837" /* 0x554d - "UM" */, + }, + }, + /* .inputDelay = */ 0, +}; + + diff --git a/sc2/src/sc2code/netplay/netoptions.h b/sc2/src/sc2code/netplay/netoptions.h new file mode 100644 index 000000000..cdefe21ad --- /dev/null +++ b/sc2/src/sc2code/netplay/netoptions.h @@ -0,0 +1,51 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETOPTIONS_H +#define _NETOPTIONS_H + +#include "types.h" + +#include + +#define NETPLAY_NUM_PLAYERS 2 + // Not using NUM_PLAYERS because that would mean we'd have + // to include init.h, and all that comes with it. + // XXX: Don't use a hardcoded limit. + +typedef struct { + bool isServer; + const char *host; + const char *port; + // May be given as a service name. +} NetplayPeerOptions; + +typedef struct { + const char *metaServer; + const char *metaPort; + // May be given as a service name. + NetplayPeerOptions peer[NETPLAY_NUM_PLAYERS]; + size_t inputDelay; +} NetplayOptions; +extern NetplayOptions netplayOptions; + + +#endif /* _NETOPTIONS_H */ + + + diff --git a/sc2/src/sc2code/netplay/netplay.h b/sc2/src/sc2code/netplay/netplay.h new file mode 100644 index 000000000..bed644b04 --- /dev/null +++ b/sc2/src/sc2code/netplay/netplay.h @@ -0,0 +1,54 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(_NETPLAY_H) && defined(NETPLAY) +#define _NETPLAY_H + +// NETPLAY can either be unset (in which case we will never get here) +// NETPLAY_FULL, or NETPLAY_IPV4 (disables IPv6) +#define NETPLAY_IPV4 1 +#define NETPLAY_FULL 2 + +#define NETPLAY_PROTOCOL_VERSION_MAJOR 0 +#define NETPLAY_PROTOCOL_VERSION_MINOR 1 + +#define NETPLAY_DEBUG + /* Extra debugging for netplay */ +#define NETPLAY_STATISTICS + /* Keep some statistics */ +#define NETPLAY_CHECKSUM + /* Send/process checksums to verify that both sides of a network + * connection are still in sync. + * If not enabled, incoming checksum packets will be ignored. + * TODO: make compilation of crc.c and checksum.c conditional. */ +#define NETPLAY_CHECKSUM_INTERVAL 1 + /* If NETPLAY_CHECKSUM is defined, this define determines + * every how many frames a checksum packet is sent. */ + +#define NETPLAY_READBUFSIZE 2048 +#define NETPLAY_CONNECTTIMEOUT 2000 + /* Time to wait for a connect() to succeed. In ms. */ +//#define NETPLAY_LISTENTIMEOUT 30000 +// /* Time to wait for a listen() to succeed. In ms. */ +#define NETPLAY_RETRYDELAY 2000 + /* Time to wait after all addresses of a host have been tried + * before starting retrying them all. In ms. */ + + +#endif /* _NETPLAY_H */ + diff --git a/sc2/src/sc2code/netplay/netrcv.c b/sc2/src/sc2code/netplay/netrcv.c new file mode 100644 index 000000000..64f19dfbd --- /dev/null +++ b/sc2/src/sc2code/netplay/netrcv.c @@ -0,0 +1,189 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "port.h" + +#include "netconnection.h" +#include "netrcv.h" +#include "packet.h" + +#include "types.h" +#include "libs/log.h" + +#include +#include + + +// Try to get a single packet from a stream of data. +// Returns 0 if the packet was successfully processed, or +// -1 on an error, in which case the state is unchanged. +static ssize_t +dataReceivedSingle(NetConnection *conn, const uint8 *data, + size_t dataLen) { + uint32 packetLen; + PacketType type; + int result; + + if (dataLen < sizeof (PacketHeader)) { + // Incomplete packet. We'll have to wait for the rest. + return 0; + } + + packetLen = packetLength((const Packet *) data); + type = packetType((const Packet *) data); + + if (!validPacketType(type)) { + log_add(log_Warning, "Packet with invalid type %d received.\n", type); + errno = EBADMSG; + return -1; + } + + if (packetLen < packetTypeData[type].len) { + // Bad len field of packet. + log_add(log_Warning, "Packet with bad length field received (type=" + "%s, lenfield=%d.\n", packetTypeData[type].name, + packetLen); + errno = EBADMSG; + return -1; + } + + if (dataLen < packetLen) { + // Incomplete packet. We'll have to wait for the rest. + return 0; + } + +#ifdef NETPLAY_STATISTICS + NetConnection_getStatistics(conn)->packetsReceived++; + NetConnection_getStatistics(conn)->packetTypeReceived[type]++; +#endif + +#ifdef NETPLAY_DEBUG + if (type != PACKET_BATTLEINPUT && type != PACKET_CHECKSUM) { + // Reporting BattleInput and Checksum would get so spammy that it + // would slow down the battle. + log_add(log_Debug, "NETPLAY: [%d] <== Received packet of type %s.\n", + NetConnection_getPlayerNr(conn), packetTypeData[type].name); + } +#endif + + result = packetTypeData[type].handler(conn, data); + if (result == -1) { + // An error occured. errno is set by the handler. + return -1; + } + + return packetLen; +} + +// Try to get all the packets from a stream of data. +// Returns the number of bytes processed. +static ssize_t +dataReceivedMulti(NetConnection *conn, const uint8 *data, size_t len) { + size_t processed; + + processed = 0; + while (len > 0) { + ssize_t packetLen = dataReceivedSingle(conn, data, len); + if (packetLen == -1) { + // Bad packet. Errno is set. + return -1; + } + + if (packetLen == 0) { + // No packet was processed. This means that no complete + // packet arrived. + break; + } + + processed += packetLen; + data += packetLen; + len -= packetLen; + } + + return processed; +} + +// Returns -1 on error (setting errno), or 0 if everything went +// ok, regardless of whether any packets were actually processed. +void +dataReadyCallback(NetDescriptor *nd) { + NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd); + Socket *socket = NetDescriptor_getSocket(nd); + + for (;;) { + ssize_t numRead; + ssize_t numProcessed; + + numRead = Socket_recv(socket, conn->readEnd, + NETPLAY_READBUFSIZE - (conn->readEnd - conn->readBuf), 0); + if (numRead == 0) { + // Other side closed the connection. + NetDescriptor_close(nd); + return; + } + + if (numRead == -1) { + switch (errno) { + case EAGAIN: // No more data for now. + return; + case EINTR: // System call was interrupted. Retry. + continue; + default: { + int savedErrno = errno; + log_add(log_Error, "recv() failed: %s.\n", + strerror(errno)); + NetConnection_doErrorCallback(conn, savedErrno); + NetDescriptor_close(nd); + return; + } + } + } + + conn->readEnd += numRead; + + numProcessed = dataReceivedMulti(conn, conn->readBuf, + conn->readEnd - conn->readBuf); + if (numProcessed == -1) { + // An error occured during processing. + // errno is set. + NetConnection_doErrorCallback(conn, errno); + NetDescriptor_close(nd); + return; + } + if (numProcessed == 0) { + // No packets could be processed. This means we need to receive + // more data first. + return; + } + + // Some packets have been processed. + // We more any rest to the front of the buffer, to make room + // for more data. + // A cyclic buffer would obviate the need for this move, + // but it would complicate things a lot. + memmove(conn->readBuf, conn->readBuf + numProcessed, + (conn->readEnd - conn->readBuf) - numProcessed); + conn->readEnd -= numProcessed; + } +} + + + diff --git a/sc2/src/sc2code/netplay/netrcv.h b/sc2/src/sc2code/netplay/netrcv.h new file mode 100644 index 000000000..9db577635 --- /dev/null +++ b/sc2/src/sc2code/netplay/netrcv.h @@ -0,0 +1,27 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETRCV_H +#define _NETRCV_H + +#include "libs/net.h" + +void dataReadyCallback(NetDescriptor *nd); + +#endif /* _NETRCV_H */ + diff --git a/sc2/src/sc2code/netplay/netsend.c b/sc2/src/sc2code/netplay/netsend.c new file mode 100644 index 000000000..0d47c8d9f --- /dev/null +++ b/sc2/src/sc2code/netplay/netsend.c @@ -0,0 +1,87 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "port.h" + +#include "netconnection.h" +#include "packet.h" +#include "libs/log.h" +#include "libs/net.h" + +#include +#include +#include + + +int +sendPacket(NetConnection *conn, Packet *packet) { + ssize_t sendResult; + size_t len; + Socket *socket; + + assert(NetConnection_isConnected(conn)); + +#ifdef NETPLAY_DEBUG + //if (packetType(packet) != PACKET_BATTLEINPUT && + // packetType(packet) != PACKET_CHECKSUM) { + // // Reporting BattleInput or Checksum would get so spammy that it + // // would slow down the battle. + // log_add(log_Debug, "NETPLAY: [%d] ==> Sending packet of type %s.\n", + // conn->player, packetTypeData[packetType(packet)].name); + //} +#endif + + socket = NetDescriptor_getSocket(conn->nd); + + len = packetLength(packet); + while (len > 0) { + sendResult = Socket_send(socket, (void *) packet, len, 0); + if (sendResult >= 0) { + len -= sendResult; + continue; + } + + switch (errno) { + case EINTR: // System call interrupted, retry; + continue; + case ECONNRESET: { // Connection reset by peer. + // keep errno + return -1; + } + default: { + // Should not happen. + int savedErrno = errno; + log_add(log_Error, "send() failed: %s.\n", strerror(errno)); + errno = savedErrno; + return -1; + } + } + } + +#ifdef NETPLAY_STATISTICS + NetConnection_getStatistics(conn)->packetsSent++; + NetConnection_getStatistics(conn)->packetTypeSent[packetType(packet)]++; +#endif + + return 0; +} + + diff --git a/sc2/src/sc2code/netplay/netsend.h b/sc2/src/sc2code/netplay/netsend.h new file mode 100644 index 000000000..1a2199379 --- /dev/null +++ b/sc2/src/sc2code/netplay/netsend.h @@ -0,0 +1,28 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETSEND_H +#define _NETSEND_H + +#include "packet.h" + +int sendPacket(NetConnection *conn, Packet *packet); + + +#endif /* _NETSEND_H */ + diff --git a/sc2/src/sc2code/netplay/netstate.c b/sc2/src/sc2code/netplay/netstate.c new file mode 100644 index 000000000..8dbc561e3 --- /dev/null +++ b/sc2/src/sc2code/netplay/netstate.c @@ -0,0 +1,49 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "netstate.h" + +#include +#include + + +#define DEFINE_NETSTATEDATA(stateName) \ + { \ + /* .name = */ #stateName, \ + } +NetStateData netStateData[] = { + DEFINE_NETSTATEDATA(unconnected), + DEFINE_NETSTATEDATA(connecting), + DEFINE_NETSTATEDATA(init), + DEFINE_NETSTATEDATA(inSetup), + DEFINE_NETSTATEDATA(preBattle), + DEFINE_NETSTATEDATA(interBattle), + DEFINE_NETSTATEDATA(selectShip), + DEFINE_NETSTATEDATA(inBattle), + DEFINE_NETSTATEDATA(endingBattle), + DEFINE_NETSTATEDATA(endingBattle2), + DEFINE_NETSTATEDATA(endMelee), +}; + +void +NetConnectionStateData_release(NetConnectionStateData *stateData) { + assert(stateData->releaseFunction != NULL); + stateData->releaseFunction(stateData); +} + diff --git a/sc2/src/sc2code/netplay/netstate.h b/sc2/src/sc2code/netplay/netstate.h new file mode 100644 index 000000000..056d6f96b --- /dev/null +++ b/sc2/src/sc2code/netplay/netstate.h @@ -0,0 +1,69 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NETSTATE_H +#define _NETSTATE_H + +#include "port.h" + + +typedef struct NetConnectionStateData NetConnectionStateData; + +typedef enum { + NetState_unconnected, /* No connection initiated */ + NetState_connecting, /* Connection being setup */ + NetState_init, /* Initialising the connection */ + NetState_inSetup, /* In the network game setup */ + NetState_preBattle, /* Pre-battle initialisations */ + NetState_interBattle, /* Negotiations between battles. */ + NetState_selectShip, /* Selecting a ship in battle */ + NetState_inBattle, /* Battle has started */ + NetState_endingBattle, /* Both sides are prepared to end */ + NetState_endingBattle2, /* Waiting for the final synchronisation */ + NetState_endMelee, /* Melee ended; remote is not yet in setup */ +} NetState; + +#include "types.h" + +typedef struct { + const char *name; +} NetStateData; +extern NetStateData netStateData[]; + +typedef void (*NetConnectionStateData_ReleaseFunction)( + NetConnectionStateData *stateData); + +#define NETCONNECTION_STATE_DATA_COMMON \ + NetConnectionStateData_ReleaseFunction releaseFunction; + +struct +NetConnectionStateData { + NETCONNECTION_STATE_DATA_COMMON +}; + +void NetConnectionStateData_release(NetConnectionStateData *stateData); + +static inline bool +NetState_battleActive(NetState state) { + return state == NetState_inBattle || state == NetState_endingBattle || + state == NetState_endingBattle2; +} + + +#endif /* _NETSTATE_H */ + diff --git a/sc2/src/sc2code/netplay/notify.c b/sc2/src/sc2code/netplay/notify.c new file mode 100644 index 000000000..92c1ed07f --- /dev/null +++ b/sc2/src/sc2code/netplay/notify.c @@ -0,0 +1,117 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This files contains functions that notify the other side of local +// changes. + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "notify.h" + +#include "packetsenders.h" + + +// Convert a local player number to a side indication relative to this +// party. +static inline NetplaySide +netSide(NetConnection *conn, int side) { + if (side == conn->player) + return NetplaySide_remote; + + return NetplaySide_local; +} + +void +Netplay_selectShip(NetConnection *conn, uint16 index) { + assert(NetConnection_getState(conn) == NetState_selectShip); + + sendSelectShip(conn, index); +} + +void +Netplay_battleInput(NetConnection *conn, BATTLE_INPUT_STATE input) { + assert(NetConnection_getState(conn) == NetState_inBattle || + NetConnection_getState(conn) == NetState_endingBattle || + NetConnection_getState(conn) == NetState_endingBattle2); + + sendBattleInput(conn, input); +} + +void +Netplay_teamStringChanged(NetConnection *conn, int player, + const char *name, size_t len) { + assert(NetConnection_getState(conn) == NetState_inSetup); + assert(!conn->stateFlags.handshake.localOk); + + sendTeamString(conn, netSide(conn, player), name, len); +} + +// On initialisation, or load. +void +Netplay_entireFleetChanged(NetConnection *conn, int player, + const BYTE *fleet, size_t fleetSize) { + assert(NetConnection_getState(conn) == NetState_inSetup); + assert(!conn->stateFlags.handshake.localOk); + + sendFleet(conn, netSide(conn, player), fleet, fleetSize); +} + +void +Netplay_fleetShipChanged(NetConnection *conn, int player, + size_t index, BYTE ship) { + assert(NetConnection_getState(conn) == NetState_inSetup); + assert(!conn->stateFlags.handshake.localOk); + + sendFleetShip(conn, netSide(conn, player), index, ship); +} + +void +Netplay_seedRandom(NetConnection *conn, uint32 seed) { + assert(NetConnection_getState(conn) == NetState_preBattle); + + sendSeedRandom(conn, seed); + conn->stateFlags.agreement.randomSeed = true; +} + +void +Netplay_sendInputDelay(NetConnection *conn, uint32 delay) { + assert(NetConnection_getState(conn) == NetState_preBattle); + + sendInputDelay(conn, delay); +} + +void +Netplay_sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount) { + assert(NetConnection_getState(conn) == NetState_endingBattle); + + sendFrameCount(conn, frameCount); +} + +#ifdef NETPLAY_CHECKSUM +void +Netplay_sendChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum) { + assert(NetState_battleActive(NetConnection_getState(conn))); + + sendChecksum(conn, frameNr, checksum); +} +#endif /* NETPLAY_CHECKSUM */ + + + + diff --git a/sc2/src/sc2code/netplay/notify.h b/sc2/src/sc2code/netplay/notify.h new file mode 100644 index 000000000..59dc15823 --- /dev/null +++ b/sc2/src/sc2code/netplay/notify.h @@ -0,0 +1,50 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NOTIFY_H +#define _NOTIFY_H + +#include "netplay.h" + // for NETPLAY_CHECKSUM +#include "netconnection.h" +#include "controls.h" + // for BATTLE_INPUT_STATE +#ifdef NETPLAY_CHECKSUM +# include "checksum.h" +#endif + +void Netplay_selectShip(NetConnection *conn, uint16 index); +void Netplay_battleInput(NetConnection *conn, BATTLE_INPUT_STATE input); +void Netplay_teamStringChanged(NetConnection *conn, int player, + const char *name, size_t len); +void Netplay_entireFleetChanged(NetConnection *conn, int player, + const BYTE *fleet, size_t fleetSize); +void Netplay_fleetShipChanged(NetConnection *conn, int player, + size_t index, BYTE ship); +void Netplay_seedRandom(NetConnection *conn, uint32 seed); +void Netplay_sendInputDelay(NetConnection *conn, uint32 delay); +void Netplay_sendFrameCount(NetConnection *conn, + BattleFrameCounter frameCount); +#ifdef NETPLAY_CHECKSUM +void Netplay_sendChecksum(NetConnection *conn, + BattleFrameCounter frameCount, Checksum checksum); +#endif + + +#endif /* _NOTIFY_H */ + diff --git a/sc2/src/sc2code/netplay/packet.c b/sc2/src/sc2code/netplay/packet.c new file mode 100644 index 000000000..81bb3408d --- /dev/null +++ b/sc2/src/sc2code/netplay/packet.c @@ -0,0 +1,257 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "packet.h" + +#include "uqmversion.h" + +#include "netrcv.h" +#include "packethandlers.h" + +#include +#include +#include + + +#define DEFINE_PACKETDATA(name, inTurn) \ + { \ + /* .len = */ sizeof (Packet_##name), \ + /* .handler = */ (PacketHandler) PacketHandler_##name, \ + /* .name = */ #name, \ + /* .inTurn = */ (inTurn) \ + } +PacketTypeData packetTypeData[PACKET_NUM] = { + DEFINE_PACKETDATA(Init, false), + DEFINE_PACKETDATA(Ping, false), + DEFINE_PACKETDATA(Ack, false), + DEFINE_PACKETDATA(EndTurn, false), + DEFINE_PACKETDATA(Ready, false), + DEFINE_PACKETDATA(Fleet, true), + DEFINE_PACKETDATA(TeamName, true), + DEFINE_PACKETDATA(Handshake0, false), + DEFINE_PACKETDATA(Handshake1, false), + DEFINE_PACKETDATA(HandshakeCancel, false), + DEFINE_PACKETDATA(HandshakeCancelAck, false), + DEFINE_PACKETDATA(SeedRandom, false), + DEFINE_PACKETDATA(InputDelay, false), + DEFINE_PACKETDATA(SelectShip, false), + DEFINE_PACKETDATA(BattleInput, false), + DEFINE_PACKETDATA(FrameCount, false), + DEFINE_PACKETDATA(Checksum, false), +}; + +static inline void * +Packet_alloc(size_t size) { + return malloc(size); +} + +static Packet * +Packet_create(PacketType type, size_t extraSize) { + Packet *result; + size_t len; + + // Alignment requirement. + assert(extraSize % 4 == 0); + + len = packetTypeData[type].len + extraSize; + result = Packet_alloc(len); + result->header.len = hton16((uint16) len); + result->header.type = hton16((uint16) type); + return result; +} + +void +Packet_delete(Packet *packet) { + free(packet); +} + +Packet_Init * +Packet_Init_create(void) { + Packet_Init *packet = (Packet_Init *) Packet_create(PACKET_INIT, 0); + + packet->protoVersion.major = NETPLAY_PROTOCOL_VERSION_MAJOR; + packet->protoVersion.minor = NETPLAY_PROTOCOL_VERSION_MINOR; + packet->padding0 = 0; + packet->uqmVersion.major = UQM_MAJOR_VERSION; + packet->uqmVersion.minor = UQM_MINOR_VERSION; + packet->uqmVersion.patch = UQM_PATCH_VERSION; + packet->padding1 = 0; + return packet; +} + +Packet_Ping * +Packet_Ping_create(uint32 id) { + Packet_Ping *packet = (Packet_Ping *) Packet_create(PACKET_PING, 0); + + packet->id = hton32(id); + return packet; +} + +Packet_Ack * +Packet_Ack_create(uint32 id) { + Packet_Ack *packet = (Packet_Ack *) Packet_create(PACKET_ACK, 0); + + packet->id = hton32(id); + return packet; +} + +Packet_EndTurn * +Packet_EndTurn_create(void) { + Packet_EndTurn *packet = + (Packet_EndTurn *) Packet_create(PACKET_ENDTURN, 0); + + return packet; +} + +Packet_Ready * +Packet_Ready_create(void) { + Packet_Ready *packet = (Packet_Ready *) Packet_create(PACKET_READY, 0); + + return packet; +} + +// The fleet itself still needs to be filled by the caller. +// This function takes care of the necessary padding; it is allocated, +// and filled with zero chars. +Packet_Fleet * +Packet_Fleet_create(NetplaySide side, size_t numShips) { + Packet_Fleet *packet; + size_t fleetSize; + size_t extraSize; + + fleetSize = numShips * sizeof (FleetEntry); + extraSize = (fleetSize + 3) & ~0x03; + packet = (Packet_Fleet *) Packet_create(PACKET_FLEET, extraSize); + packet->side = (uint8) side; + packet->padding = 0; + packet->numShips = hton16((uint16) numShips); + memset((char *) packet + sizeof (Packet_Fleet) + fleetSize, + '\0', extraSize - fleetSize); + + return packet; +} + +// 'size' is the number of bytes (not characters) in 'name', excluding +// a possible terminating '\0'. A '\0' will be included in the packet though. +// This function takes care of the required padding. +Packet_TeamName * +Packet_TeamName_create(NetplaySide side, const char *name, size_t size) { + Packet_TeamName *packet; + size_t extraSize; + + extraSize = ((size + 1) + 3) & ~0x03; + // The +1 is for the '\0'. + packet = (Packet_TeamName *) Packet_create(PACKET_TEAMNAME, extraSize); + packet->side = (uint8) side; + packet->padding = 0; + memcpy(packet->name, name, size); + memset((char *) packet + sizeof (Packet_TeamName) + size, '\0', + extraSize - size); + // This takes care of the terminating '\0', as well as the + // padding. + + return packet; +} + +Packet_Handshake0 * +Packet_Handshake0_create(void) { + Packet_Handshake0 *packet = + (Packet_Handshake0 *) Packet_create(PACKET_HANDSHAKE0, 0); + return packet; +} + +Packet_Handshake1 * +Packet_Handshake1_create(void) { + Packet_Handshake1 *packet = + (Packet_Handshake1 *) Packet_create(PACKET_HANDSHAKE1, 0); + return packet; +} + +Packet_HandshakeCancel * +Packet_HandshakeCancel_create(void) { + Packet_HandshakeCancel *packet = + (Packet_HandshakeCancel *) Packet_create( + PACKET_HANDSHAKECANCEL, 0); + return packet; +} + +Packet_HandshakeCancelAck * +Packet_HandshakeCancelAck_create(void) { + Packet_HandshakeCancelAck *packet = + (Packet_HandshakeCancelAck *) Packet_create( + PACKET_HANDSHAKECANCELACK, 0); + return packet; +} + +Packet_SeedRandom * +Packet_SeedRandom_create(uint32 seed) { + Packet_SeedRandom *packet = + (Packet_SeedRandom *) Packet_create(PACKET_SEEDRANDOM, 0); + + packet->seed = hton32(seed); + return packet; +} + +Packet_InputDelay * +Packet_InputDelay_create(uint32 delay) { + Packet_InputDelay *packet = + (Packet_InputDelay *) Packet_create(PACKET_INPUTDELAY, 0); + + packet->delay = hton32(delay); + return packet; +} + +Packet_SelectShip * +Packet_SelectShip_create(uint16 ship) { + Packet_SelectShip *packet = + (Packet_SelectShip *) Packet_create(PACKET_SELECTSHIP, 0); + packet->ship = hton16(ship); + packet->padding = 0; + return packet; +} + +Packet_BattleInput * +Packet_BattleInput_create(uint8 state) { + Packet_BattleInput *packet = + (Packet_BattleInput *) Packet_create(PACKET_BATTLEINPUT, 0); + packet->state = (uint8) state; + packet->padding0 = 0; + packet->padding1 = 0; + return packet; +} + +Packet_FrameCount * +Packet_FrameCount_create(uint32 frameCount) { + Packet_FrameCount *packet = + (Packet_FrameCount *) Packet_create(PACKET_FRAMECOUNT, 0); + packet->frameCount = hton32(frameCount); + return packet; +} + +Packet_Checksum * +Packet_Checksum_create(uint32 frameNr, uint32 checksum) { + Packet_Checksum *packet = + (Packet_Checksum *) Packet_create(PACKET_CHECKSUM, 0); + packet->frameNr = hton32(frameNr); + packet->checksum = hton32(checksum); + return packet; +} + + + diff --git a/sc2/src/sc2code/netplay/packet.h b/sc2/src/sc2code/netplay/packet.h new file mode 100644 index 000000000..cbdbc78e1 --- /dev/null +++ b/sc2/src/sc2code/netplay/packet.h @@ -0,0 +1,261 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PACKET_H +#define _PACKET_H + +typedef struct Packet Packet; + +typedef enum PacketType { + PACKET_INIT, + PACKET_PING, + PACKET_ACK, + PACKET_ENDTURN, + PACKET_READY, + PACKET_FLEET, + PACKET_TEAMNAME, + PACKET_HANDSHAKE0, + PACKET_HANDSHAKE1, + PACKET_HANDSHAKECANCEL, + PACKET_HANDSHAKECANCELACK, + PACKET_SEEDRANDOM, + PACKET_INPUTDELAY, + PACKET_SELECTSHIP, + PACKET_BATTLEINPUT, + PACKET_FRAMECOUNT, + PACKET_CHECKSUM, + + PACKET_NUM, /* Number of packet types */ +} PacketType; + + +#include "netconnection.h" + +#include "types.h" +#include "libs/network/bytesex.h" + + +/* NB: These handlers are expected not to modify the state if an + * error occurs. + * When a handler is called, it has already been validated that the + * a complete packet has arrived. + */ +typedef int (*PacketHandler)(NetConnection *conn, const void *packet); + +typedef struct { + size_t len; /* Minimal length of a packet of this type */ + PacketHandler handler; + const char *name; + bool inTurn; /* Can only be sent if it's this party's turn. */ +} PacketTypeData; + +extern PacketTypeData packetTypeData[]; + + +// When adding new packets, be sure to have all the fields properly aligned, +// and that the size of a packet is a multiple of 4 bytes in length. +// Fields should be no longer than 4 bytes in themselves, as larger +// fields may require a larger alignment. + +typedef struct { + uint16 len; + uint16 type; /* Actually of type PacketType */ +} PacketHeader; + +// "Base class" for all packets. +struct Packet { + PacketHeader header; +}; + +static inline size_t +packetLength(const Packet *packet) { + return (size_t) ntoh16(packet->header.len); +} + +static inline PacketType +packetType(const Packet *packet) { + return (PacketType) ntoh16(packet->header.type); +} + +static inline bool +validPacketType(PacketType type) { + return type < PACKET_NUM; +} + +typedef struct { + PacketHeader header; + struct { + uint8 major; + uint8 minor; + } protoVersion; /* Protocol version */ + uint16 padding0; /* Set to 0 */ + struct { + uint8 major; + uint8 minor; + uint8 patch; + } uqmVersion; /* Protocol version */ + uint8 padding1; /* Set to 0 */ +} Packet_Init; + +typedef struct { + PacketHeader header; + uint32 id; +} Packet_Ping; + +// Acknowldement of a Ping. +typedef struct { + PacketHeader header; + uint32 id; +} Packet_Ack; + +// Used to ask or confirm a turn change. +typedef struct { + PacketHeader header; + // No contents. +} Packet_EndTurn; + +// Used to signal that a party is ready to continue. +typedef struct { + PacketHeader header; + // No contents. +} Packet_Ready; + +typedef struct { + PacketHeader header; + uint32 seed; +} Packet_SeedRandom; + +typedef struct { + PacketHeader header; + uint32 delay; +} Packet_InputDelay; + +// This enum is used to indicate that a packet containing it relates to +// either the local or the remote player, from the perspective of the +// sender of the message; +typedef enum { + NetplaySide_local, + NetplaySide_remote +} NetplaySide; + +typedef struct { + uint8 index; /* Position in the fleet */ + uint8 ship; /* Ship type index */ +} FleetEntry; +// Structure describing an update to a player's fleet. +// TODO: use strings as ship identifiers, instead of numbers, +// so that adding of new ships doesn't break this. +typedef struct { + PacketHeader header; + uint8 side; + uint8 padding; + uint16 numShips; + FleetEntry ships[]; + // Be sure to add padding to this structure to make it a multiple of + // 4 bytes in length. +} Packet_Fleet; + +typedef struct { + PacketHeader header; + uint8 side; + uint8 padding; + uint8 name[]; + // '\0' terminated. + // Be sure to add padding to this structure to make it a multiple of + // 4 bytes in length. +} Packet_TeamName; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_Handshake0; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_Handshake1; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_HandshakeCancel; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_HandshakeCancelAck; + +typedef struct { + PacketHeader header; + uint16 ship; + // The value '(uint16) ~0' indicates random selection. + uint16 padding; +} Packet_SelectShip; + +typedef enum { + PK_THRUST, + PK_LEFT, + PK_RIGHT, + PK_FIRE, + PK_SPECIAL, + + PK_NUM +} PacketKey; + +typedef struct { + PacketHeader header; + uint8 state; /* Actually BATTLE_INPUT_STATE */ + uint8 padding0; + uint16 padding1; +} Packet_BattleInput; + +typedef struct { + PacketHeader header; + uint32 frameCount; /* Actually BattleFrameCounter */ +} Packet_FrameCount; + +typedef struct { + PacketHeader header; + uint32 frameNr; /* Actually BattleFrameCounter */ + uint32 checksum; /* Actually Checksum */ +} Packet_Checksum; + + +void Packet_delete(Packet *packet); +Packet_Init *Packet_Init_create(void); +Packet_Ping *Packet_Ping_create(uint32 id); +Packet_Ack *Packet_Ack_create(uint32 id); +Packet_EndTurn *Packet_EndTurn_create(void); +Packet_Ready *Packet_Ready_create(void); +Packet_Handshake0 *Packet_Handshake0_create(void); +Packet_Handshake1 *Packet_Handshake1_create(void); +Packet_HandshakeCancel *Packet_HandshakeCancel_create(void); +Packet_HandshakeCancelAck *Packet_HandshakeCancelAck_create(void); +Packet_SeedRandom *Packet_SeedRandom_create(uint32 seed); +Packet_InputDelay *Packet_InputDelay_create(uint32 delay); +Packet_Fleet *Packet_Fleet_create(NetplaySide side, size_t numShips); +Packet_TeamName *Packet_TeamName_create(NetplaySide side, const char *name, + size_t size); +Packet_SelectShip *Packet_SelectShip_create(uint16 ship); +Packet_BattleInput *Packet_BattleInput_create(uint8 state); +Packet_FrameCount *Packet_FrameCount_create(uint32 frameCount); +Packet_Checksum *Packet_Checksum_create(uint32 frameNr, uint32 checksum); + + +#endif /* _PACKET_H */ + diff --git a/sc2/src/sc2code/netplay/packethandlers.c b/sc2/src/sc2code/netplay/packethandlers.c new file mode 100644 index 000000000..0326f81aa --- /dev/null +++ b/sc2/src/sc2code/netplay/packethandlers.c @@ -0,0 +1,520 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "packethandlers.h" + +#include "netinput.h" +#include "netmisc.h" +#include "packetsenders.h" +#include "proto/npconfirm.h" +#include "proto/ready.h" +#include "libs/log.h" + +#include "controls.h" + // for BATTLE_INPUT_STATE +#include "init.h" + // for NUM_PLAYERS +#include "melee.h" + // for various update functions. +#include "pickmele.h" + // for various update functions. +#include "libs/mathlib.h" + // for TFB_SeedRandom + +#include + + +static bool +testNetState(bool condition, PacketType type) { + if (!condition) { + log_add(log_Error, "Packet of type '%s' received from wrong " + "state.\n", packetTypeData[type].name); + errno = EBADMSG; + } + return condition; +} + +int +PacketHandler_Init(NetConnection *conn, const Packet_Init *packet) { + if (!testNetState(conn->state == NetState_init && + !conn->stateFlags.ready.remoteReady, PACKET_INIT)) + return -1; // errno is set + + if (packet->protoVersion.major != NETPLAY_PROTOCOL_VERSION_MAJOR || + packet->protoVersion.minor != NETPLAY_PROTOCOL_VERSION_MINOR) { + log_add(log_Error, "Protocol version %d.%d not supported.\n", + packet->protoVersion.major, packet->protoVersion.minor); + errno = ENOSYS; + return -1; + } + + Netplay_remoteReady(conn); + + return 0; +} + +int +PacketHandler_Ping(NetConnection *conn, const Packet_Ping *packet) { + if (!testNetState(conn->state > NetState_init, PACKET_PING)) + return -1; // errno is set + + sendAck(conn, packet->id); + return 0; +} + +int +PacketHandler_Ack(NetConnection *conn, const Packet_Ack *packet) { + if (!testNetState(conn->state > NetState_init, PACKET_ACK)) + return -1; // errno is set + + (void) conn; + (void) packet; + return 0; +} + +int +PacketHandler_EndTurn(NetConnection *conn, const Packet_EndTurn *packet) { + if (!testNetState(conn->state > NetState_init && + !conn->stateFlags.pendingTurnChange, PACKET_ENDTURN)) + return -1; // errno is set + + if (conn->stateFlags.endingTurn) { + // This was the confirmation we were waiting for. + // NB. A remote request while we had sent a request serves as + // a confirmation. + conn->stateFlags.myTurn = !conn->stateFlags.myTurn; + conn->stateFlags.endingTurn = false; + } else { + // The other party wants to change whose turn it is. + // If it would become the other party's turn, we wait until + // the queue is flushed to actually carry out the turn change, + // and send the confirmation. I we wouldn't do that, and we still + // had some data to send in our own turn, we would end up asking + // for our turn back, and the turn would keep changing without + // any progress being made. + if (conn->stateFlags.myTurn) { + // Schedule the turn change until after the next queue flush. + conn->stateFlags.pendingTurnChange = true; + } else { + conn->stateFlags.myTurn = true; + sendEndTurn(conn); + } + } + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +// Convert the side indication relative to a remote party to +// a local player number. +static inline int +localSide(NetConnection *conn, NetplaySide side) { + if (side == NetplaySide_local) { + // "local" relative to the remote party. + return conn->player; + } + + return 1 - conn->player; +} + +static bool +checkYourTurn(NetConnection *conn, PacketType type) { + if (conn->stateFlags.myTurn) { + log_add(log_Warning, "Packet of type '%s' received in an " + "inappropriate turn.\n", packetTypeData[type].name); + errno = EBADMSG; + return false; + } + return true; +} + +int +PacketHandler_Ready(NetConnection *conn, const Packet_Ready *packet) { + if (!testNetState(readyFlagsMeaningful(conn->state) && + !conn->stateFlags.ready.remoteReady, PACKET_READY)) + return -1; // errno is set + + Netplay_remoteReady(conn); + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_Fleet(NetConnection *conn, const Packet_Fleet *packet) { + uint32 numShips = ntoh16(packet->numShips); + size_t i; + size_t len; + int player; + BattleStateData *battleStateData; + + if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET)) + return -1; // errno is set + + if (!checkYourTurn(conn, PACKET_FLEET)) { + // errno is set + return -1; + } + + player = localSide(conn, (NetplaySide) packet->side); + + len = packetLength((const Packet *) packet); + if (sizeof packet + numShips * sizeof(packet->ships[0]) > len) { + // There is not enough room in the packet to contain all + // the ships it says it contains. + log_add(log_Warning, "Invalid fleet size. Specified size is %d, " + "actual size = %d\n", + numShips, (len - sizeof packet) / sizeof(packet->ships[0])); + errno = EBADMSG; + return -1; + } + + if (conn->stateFlags.handshake.localOk) + Netplay_cancelConfirmation(conn); + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + for (i = 0; i < numShips; i++) { + int ship = packet->ships[i].ship; + int index = packet->ships[i].index; + + bool updateResult = updateFleetShip( + battleStateData->meleeState, player, index, ship); + if (!updateResult) + { + errno = EBADMSG; + return -1; + } + } + + // Padding data may follow; it is ignored. + return 0; +} + +int +PacketHandler_TeamName(NetConnection *conn, const Packet_TeamName *packet) { + size_t nameLen; + int side; + BattleStateData *battleStateData; + + if (!testNetState(conn->state == NetState_inSetup, PACKET_TEAMNAME)) + return -1; // errno is set + + if (!checkYourTurn(conn, PACKET_TEAMNAME)) { + // errno is set + return -1; + } + + if (conn->stateFlags.handshake.localOk) + Netplay_cancelConfirmation(conn); + + side = localSide(conn, (NetplaySide) packet->side); + + nameLen = packetLength((const Packet *) packet) + - sizeof (Packet_TeamName) - 1; + // The -1 is for not counting the terminating '\0'. + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + updateTeamName(battleStateData->meleeState, side, packet->name, nameLen); + + // Padding data may follow; it is ignored. + return 0; +} + +static void +handshakeComplete(NetConnection *conn) { + assert(!conn->stateFlags.handshake.localOk); + assert(!conn->stateFlags.handshake.remoteOk); + assert(!conn->stateFlags.handshake.canceling); + + assert(conn->state == NetState_inSetup); + NetConnection_setState(conn, NetState_preBattle); +} + +int +PacketHandler_Handshake0(NetConnection *conn, + const Packet_Handshake0 *packet) { + if (!testNetState(handshakeMeaningful(conn->state) + && !conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKE0)) + return -1; // errno is set + + conn->stateFlags.handshake.remoteOk = true; + if (conn->stateFlags.handshake.localOk && + !conn->stateFlags.handshake.canceling) + sendHandshake1(conn); + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_Handshake1(NetConnection *conn, + const Packet_Handshake1 *packet) { + if (!testNetState(handshakeMeaningful(conn->state) && + (conn->stateFlags.handshake.localOk || + conn->stateFlags.handshake.canceling), PACKET_HANDSHAKE1)) + return -1; // errno is set + + if (conn->stateFlags.handshake.canceling) { + conn->stateFlags.handshake.remoteOk = true; + } else { + bool remoteWasOk = conn->stateFlags.handshake.remoteOk; + + conn->stateFlags.handshake.localOk = false; + conn->stateFlags.handshake.remoteOk = false; + + if (!remoteWasOk) { + // Received Handshake1 without prior Handshake0. + // A Handshake0 is implied, but we still need to confirm + // it with a Handshake1. + sendHandshake1(conn); + } + + handshakeComplete(conn); + } + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_HandshakeCancel(NetConnection *conn, + const Packet_HandshakeCancel *packet) { + if (!testNetState(handshakeMeaningful(conn->state) + && conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKECANCEL)) + return -1; // errno is set + + conn->stateFlags.handshake.remoteOk = false; + sendHandshakeCancelAck(conn); + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_HandshakeCancelAck(NetConnection *conn, + const Packet_HandshakeCancelAck *packet) { + if (!testNetState(handshakeMeaningful(conn->state) + && conn->stateFlags.handshake.canceling, + PACKET_HANDSHAKECANCELACK)) + return -1; // errno is set + + conn->stateFlags.handshake.canceling = false; + if (conn->stateFlags.handshake.localOk) { + if (conn->stateFlags.handshake.remoteOk) { + sendHandshake1(conn); + } else + sendHandshake0(conn); + } + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_SeedRandom(NetConnection *conn, + const Packet_SeedRandom *packet) { + BattleStateData *battleStateData; + + if (!testNetState(conn->state == NetState_preBattle && + !conn->stateFlags.discriminant, PACKET_SEEDRANDOM)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + updateRandomSeed (battleStateData->meleeState, conn->player, + ntoh32(packet->seed)); + + conn->stateFlags.agreement.randomSeed = true; + return 0; +} + +int +PacketHandler_InputDelay(NetConnection *conn, + const Packet_InputDelay *packet) { + BattleStateData *battleStateData; + uint32 delay; + + if (!testNetState(conn->state == NetState_preBattle, + PACKET_INPUTDELAY)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + delay = ntoh32(packet->delay); + if (delay > 60 * BATTLE_FRAME_RATE) { + log_add(log_Error, "NETPLAY: [%d] Received absurdly large " + "input delay value (%d).\n", conn->player, delay); + return -1; + } + conn->stateFlags.inputDelay = ntoh32(packet->delay); + + return 0; +} + +int +PacketHandler_SelectShip(NetConnection *conn, + const Packet_SelectShip *packet) { + bool updateResult; + BattleStateData *battleStateData; + + if (!testNetState(conn->state == NetState_selectShip, PACKET_SELECTSHIP)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + updateResult = updateMeleeSelection(battleStateData->getMeleeState, + conn->player, ntoh16(packet->ship)); + if (!updateResult) + { + errno = EBADMSG; + return -1; + } + + return 0; +} + +int +PacketHandler_BattleInput(NetConnection *conn, + const Packet_BattleInput *packet) { + BATTLE_INPUT_STATE input; + BattleInputBuffer *bib; + + if (!testNetState(conn->state == NetState_inBattle || + conn->state == NetState_endingBattle || + conn->state == NetState_endingBattle2, PACKET_BATTLEINPUT)) + return -1; // errno is set + + input = (BATTLE_INPUT_STATE) packet->state; + bib = getBattleInputBuffer(conn->player); + if (!BattleInputBuffer_push(bib, input)) { + // errno is set + return -1; + } + + return 0; +} + +int +PacketHandler_FrameCount(NetConnection *conn, + const Packet_FrameCount *packet) { + BattleStateData *battleStateData; + BattleFrameCounter frameCount; + + if (!testNetState(conn->state == NetState_endingBattle, + PACKET_FRAMECOUNT)) + return -1; // errno is set + + frameCount = (BattleFrameCounter) ntoh32(packet->frameCount); +#ifdef NETPLAY_DEBUG + log_add(log_Debug, "NETPLAY: [%d] <== Received battleFrameCount %u.\n", + conn->player, (unsigned int) frameCount); +#endif + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + if (frameCount > battleStateData->endFrameCount) + battleStateData->endFrameCount = frameCount; + Netplay_remoteReady(conn); + + return 0; +} + +int +PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) { +#ifdef NETPLAY_CHECKSUM + uint32 frameNr; + uint32 checksum; + size_t delay; + size_t interval; +#endif + + if (!testNetState(NetState_battleActive(conn->state), PACKET_CHECKSUM)) + return -1; // errno is set + +#ifdef NETPLAY_CHECKSUM + frameNr = ntoh32(packet->frameNr); + checksum = ntoh32(packet->checksum); + interval = NetConnection_getChecksumInterval(conn); + delay = getBattleInputDelay(); + + if (frameNr % interval != 0) { + log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " + "for frame %u, while we only expect checksums on frames " + "divisable by %u -- discarding.\n", conn->player, + (unsigned int) frameNr, interval); + return 0; + // No need to close the connection; checksums are not + // essential. + } + + // The checksum is sent at the beginning of a frame. + // If we're in frame n and have sent our input already, + // the remote side has got enough input to progress delay + 1 frames from + // frame n. The next frame is then n + delay + 1, for which we can + // receive a checksum. + if (frameNr > battleFrameCount + delay + 1) { + log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " + "for a frame too far in the future (frame %u, current " + "is %u, input delay is %u) -- discarding.\n", conn->player, + (unsigned int) frameNr, battleFrameCount, delay); + return 0; + // No need to close the connection; checksums are not + // essential. + } + + // We can progress delay more frames after the last frame for which we + // received input. If we call that frame n, we can complete frames + // n through n + delay - 1. While we are waiting for the next input, + // in frame n + delay, we will first receive the checksum that the + // remote side sent at the start of frame n + 1. + // In this situation frameNr is n + 1, and battleFrameCount is + // n + delay. + if (frameNr + delay < battleFrameCount) { + log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " + "for a frame too far in the past (frame %u, current " + "is %u, input delay is %u) -- discarding.\n", conn->player, + (unsigned int) frameNr, battleFrameCount, delay); + return 0; + // No need to close the connection; checksums are not + // essential. + } + + addRemoteChecksum(conn, frameNr, checksum); +#endif + +#ifndef NETPLAY_CHECKSUM + (void) packet; +#endif + return 0; +} + + + diff --git a/sc2/src/sc2code/netplay/packethandlers.h b/sc2/src/sc2code/netplay/packethandlers.h new file mode 100644 index 000000000..b02b15967 --- /dev/null +++ b/sc2/src/sc2code/netplay/packethandlers.h @@ -0,0 +1,47 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PACKETHANDLERS_H +#define _PACKETHANDLERS_H + +#include "packet.h" + +#define DECLARE_PACKETHANDLER(type) \ + int PacketHandler_##type(NetConnection *conn, \ + const Packet_##type *packet); + +DECLARE_PACKETHANDLER(Init); +DECLARE_PACKETHANDLER(Ping); +DECLARE_PACKETHANDLER(Ack); +DECLARE_PACKETHANDLER(EndTurn); +DECLARE_PACKETHANDLER(Ready); +DECLARE_PACKETHANDLER(Fleet); +DECLARE_PACKETHANDLER(TeamName); +DECLARE_PACKETHANDLER(Handshake0); +DECLARE_PACKETHANDLER(Handshake1); +DECLARE_PACKETHANDLER(HandshakeCancel); +DECLARE_PACKETHANDLER(HandshakeCancelAck); +DECLARE_PACKETHANDLER(SeedRandom); +DECLARE_PACKETHANDLER(InputDelay); +DECLARE_PACKETHANDLER(SelectShip); +DECLARE_PACKETHANDLER(BattleInput); +DECLARE_PACKETHANDLER(FrameCount); +DECLARE_PACKETHANDLER(Checksum); + + +#endif /* _PACKETHANDLERS_H */ diff --git a/sc2/src/sc2code/netplay/packetq.c b/sc2/src/sc2code/netplay/packetq.c new file mode 100644 index 000000000..9997f7ee5 --- /dev/null +++ b/sc2/src/sc2code/netplay/packetq.c @@ -0,0 +1,200 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "netconnection.h" +#include "packetq.h" +#include "netsend.h" +#include "packetsenders.h" +#ifdef NETPLAY_DEBUG +# include "libs/log.h" +#endif + +#include +#include +#include + +static inline PacketQueueLink * +PacketQueueLink_alloc(void) { + // XXX: perhaps keep a pool of links? + return malloc(sizeof (PacketQueueLink)); +} + +static inline void +PacketQueueLink_delete(PacketQueueLink *link) { + free(link); +} + +// 'maxSize' should at least be 1 +void +PacketQueue_init(PacketQueue *queue) { + queue->size = 0; + queue->first = NULL; + queue->end = &queue->first; + + queue->firstUrgent = NULL; + queue->endUrgent = &queue->first; +} + +static void +PacketQueue_deleteLinks(PacketQueueLink *link) { + while (link != NULL) { + PacketQueueLink *next = link->next; + Packet_delete(link->packet); + PacketQueueLink_delete(link); + link = next; + } +} + +void +PacketQueue_uninit(PacketQueue *queue) { + PacketQueue_deleteLinks(queue->firstUrgent); + PacketQueue_deleteLinks(queue->first); +} + +void +queuePacket(NetConnection *conn, Packet *packet, bool urgent) { + PacketQueue *queue; + PacketQueueLink *link; + + assert(NetConnection_isConnected(conn)); + assert(!urgent || !packetTypeData[packetType(packet)].inTurn); + // Urgent packets should never stall the connection. + + queue = &conn->queue; + + link = PacketQueueLink_alloc(); + link->packet = packet; + link->next = NULL; + if (urgent) { + *queue->endUrgent = link; + queue->endUrgent = &link->next; + } else { + *queue->end = link; + queue->end = &link->next; + } + + queue->size++; + // XXX: perhaps check that this queue isn't getting too large? + +#ifdef NETPLAY_DEBUG + if (packetType(packet) != PACKET_BATTLEINPUT && + packetType(packet) != PACKET_CHECKSUM) { + // Reporting BattleInput or Checksum would get so spammy that it + // would slow down the battle. + log_add(log_Debug, "NETPLAY: [%d] ==> Queueing packet of type %s.\n", + NetConnection_getPlayerNr(conn), + packetTypeData[packetType(packet)].name); + } +#endif +} + +// If an error occurs during sending, we leave the unsent packets in +// the queue, and let the caller decide what to do with them. +// This function may return -1 with errno EAGAIN if we're waiting for +// the other party to act first. +static int +flushPacketQueueLinks(NetConnection *conn, PacketQueueLink **first) { + PacketQueueLink *link; + PacketQueueLink *next; + PacketQueue *queue = &conn->queue; + + for (link = *first; link != NULL; link = next) { + if (packetTypeData[packetType(link->packet)].inTurn && + (!conn->stateFlags.myTurn || conn->stateFlags.endingTurn)) { + // This packet requires it to be 'our turn', and it isn't, + // or we've already told the other party we wanted to end our + // turn. + // This should never happen in the urgent queue. + assert(first != &queue->firstUrgent); + + if (!conn->stateFlags.myTurn && !conn->stateFlags.endingTurn) { + conn->stateFlags.endingTurn = true; + if (sendEndTurnDirect(conn) == -1) { + // errno is set + *first = link; + return -1; + } + } + + *first = link; + errno = EAGAIN; + // We need to wait for the reply to the turn change. + return -1; + } + + if (sendPacket(conn, link->packet) == -1) { + // Errno is set. + *first = link; + return -1; + } + + next = link->next; + Packet_delete(link->packet); + PacketQueueLink_delete(link); + queue->size--; + } + + *first = link; + return 0; +} + +int +flushPacketQueue(NetConnection *conn) { + int flushResult; + PacketQueue *queue = &conn->queue; + + assert(NetConnection_isConnected(conn)); + + flushResult = flushPacketQueueLinks(conn, &queue->firstUrgent); + if (queue->firstUrgent == NULL) + queue->endUrgent = &queue->firstUrgent; + if (flushResult == -1) { + // errno is set + return -1; + } + + flushResult = flushPacketQueueLinks(conn, &queue->first); + if (queue->first == NULL) + queue->end = &queue->first; + if (flushResult == -1) { + // errno is set + return -1; + } + + // If a turn change had been requested by the other side while it was + // our turn, we first sent everything we still had to send in our turn. + // Now that is done, it is the time to actually give up the turn. + if (conn->stateFlags.pendingTurnChange) { + assert(conn->stateFlags.myTurn); + conn->stateFlags.myTurn = false; + conn->stateFlags.pendingTurnChange = false; + + // Send the confirmation to the other side: + if (sendEndTurnDirect(conn) == -1) { + // errno is set + return -1; + } + } + + return 0; +} + + + diff --git a/sc2/src/sc2code/netplay/packetq.h b/sc2/src/sc2code/netplay/packetq.h new file mode 100644 index 000000000..498092015 --- /dev/null +++ b/sc2/src/sc2code/netplay/packetq.h @@ -0,0 +1,68 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PACKETQ_H +#define _PACKETQ_H + +typedef struct PacketQueue PacketQueue; + +#include "packet.h" +#include "types.h" + +#include + +typedef struct PacketQueueLink PacketQueueLink; +struct PacketQueueLink { + PacketQueueLink *next; + Packet *packet; +}; + +struct PacketQueue { + size_t size; + PacketQueueLink *first; + PacketQueueLink **end; + + PacketQueueLink *firstUrgent; + PacketQueueLink **endUrgent; + + // first points to the first entry in the queue + // end points to the location where the next non-urgent message should + // be inserted. + // 'firstUrgent' and 'endUrgent' are analogous to 'first' and 'end'. + + // Urgent packets should only be used for packets that may not + // be delayed while we wait for some remote confirmation. + // As such, these messages themselves should not require a state change + // which needs to be remotely confirmed. + // For example, the endTurn message can be sent when we want to change + // which party is allowed to transmit specific packets. That message + // should not be delayed by messages which require that *we* are that + // party, as without the endTurn message, it will never become our turn. + // Also, ping and ack packets should give some indication of the round + // trip time, which they can only do if they aren't delayed by other + // packets. +}; + +void PacketQueue_init(PacketQueue *queue); +void PacketQueue_uninit(PacketQueue *queue); +void queuePacket(NetConnection *conn, Packet *packet, bool urgent); +int flushPacketQueue(NetConnection *conn); + + +#endif + diff --git a/sc2/src/sc2code/netplay/packetsenders.c b/sc2/src/sc2code/netplay/packetsenders.c new file mode 100644 index 000000000..151e4a143 --- /dev/null +++ b/sc2/src/sc2code/netplay/packetsenders.c @@ -0,0 +1,199 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "packetsenders.h" + +#include "packet.h" +#include "packetq.h" +#include "netsend.h" + + +void +sendInit(NetConnection *conn) { + Packet_Init *packet; + + packet = Packet_Init_create(); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendPing(NetConnection *conn, uint32 id) { + Packet_Ping *packet; + + packet = Packet_Ping_create(id); + queuePacket(conn, (Packet *) packet, true); +} + +void +sendAck(NetConnection *conn, uint32 id) { + Packet_Ack *packet; + + packet = Packet_Ack_create(id); + queuePacket(conn, (Packet *) packet, true); +} + +void +sendEndTurn(NetConnection *conn) { + Packet_EndTurn *packet; + + packet = Packet_EndTurn_create(); + queuePacket(conn, (Packet *) packet, true); +} + +void +sendReady(NetConnection *conn) { + Packet_Ready *packet; + + packet = Packet_Ready_create(); + queuePacket(conn, (Packet *) packet, false); +} + +// Bypass the packet queue. +// Should only be called from the packet queue functions themselves. +int +sendEndTurnDirect(NetConnection *conn) { + Packet_EndTurn *packet; + + packet = Packet_EndTurn_create(); + return sendPacket(conn, (Packet *) packet); +} + +void +sendHandshake0(NetConnection *conn) { + Packet_Handshake0 *packet; + + packet = Packet_Handshake0_create(); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendHandshake1(NetConnection *conn) { + Packet_Handshake1 *packet; + + packet = Packet_Handshake1_create(); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendHandshakeCancel(NetConnection *conn) { + Packet_HandshakeCancel *packet; + + packet = Packet_HandshakeCancel_create(); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendHandshakeCancelAck(NetConnection *conn) { + Packet_HandshakeCancelAck *packet; + + packet = Packet_HandshakeCancelAck_create(); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendTeamString(NetConnection *conn, NetplaySide side, const char *name, + size_t len) { + Packet_TeamName *packet; + + packet = Packet_TeamName_create(side, name, len); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendFleet(NetConnection *conn, NetplaySide side, const BYTE *ships, + size_t numShips) { + size_t i; + Packet_Fleet *packet; + + packet = Packet_Fleet_create(side, numShips); + + for (i = 0; i < numShips; i++) { + packet->ships[i].index = (uint32) i; + packet->ships[i].ship = ships[i]; + } + + queuePacket(conn, (Packet *) packet, false); +} + +void +sendFleetShip(NetConnection *conn, NetplaySide side, size_t shipIndex, + BYTE ship) { + Packet_Fleet *packet; + + packet = Packet_Fleet_create(side, 1); + + packet->ships[0].index = shipIndex; + packet->ships[0].ship = ship; + + queuePacket(conn, (Packet *) packet, false); +} + +void +sendSeedRandom(NetConnection *conn, uint32 seed) { + Packet_SeedRandom *packet; + + packet = Packet_SeedRandom_create(seed); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendInputDelay(NetConnection *conn, uint32 delay) { + Packet_InputDelay *packet; + + packet = Packet_InputDelay_create(delay); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendSelectShip(NetConnection *conn, COUNT ship) { + Packet_SelectShip *packet; + + packet = Packet_SelectShip_create(ship); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input) { + Packet_BattleInput *packet; + + packet = Packet_BattleInput_create((uint8) input); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount) { + Packet_FrameCount *packet; + + packet = Packet_FrameCount_create((uint32) frameCount); + queuePacket(conn, (Packet *) packet, false); +} + +#ifdef NETPLAY_CHECKSUM +void +sendChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum) { + Packet_Checksum *packet; + + packet = Packet_Checksum_create((uint32) frameNr, (uint32) checksum); + queuePacket(conn, (Packet *) packet, false); +} +#endif + + + diff --git a/sc2/src/sc2code/netplay/packetsenders.h b/sc2/src/sc2code/netplay/packetsenders.h new file mode 100644 index 000000000..2fee1f49d --- /dev/null +++ b/sc2/src/sc2code/netplay/packetsenders.h @@ -0,0 +1,56 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PACKETSENDERS_H +#define _PACKETSENDERS_H + +#include "types.h" + +#include "netconnection.h" +#include "packet.h" + +#include "controls.h" + // for BATTLE_INPUT_STATE + +void sendInit(NetConnection *conn); +void sendPing(NetConnection *conn, uint32 id); +void sendAck(NetConnection *conn, uint32 id); +void sendEndTurn(NetConnection *conn); +void sendReady(NetConnection *conn); +int sendEndTurnDirect(NetConnection *conn); +void sendHandshake0(NetConnection *conn); +void sendHandshake1(NetConnection *conn); +void sendHandshakeCancel(NetConnection *conn); +void sendHandshakeCancelAck(NetConnection *conn); +void sendTeamString(NetConnection *conn, NetplaySide side, + const char *name, size_t len); +void sendFleet(NetConnection *conn, NetplaySide side, const BYTE *ships, + size_t numShips); +void sendFleetShip(NetConnection *conn, NetplaySide player, + size_t shipIndex, BYTE ship); +void sendSeedRandom(NetConnection *conn, uint32 seed); +void sendInputDelay(NetConnection *conn, uint32 delay); +void sendSelectShip(NetConnection *conn, COUNT ship); +void sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input); +void sendFrameCount(NetConnection *conn, uint32 frameCount); +void sendChecksum(NetConnection *conn, uint32 frameNr, uint32 checksum); + + +#endif /* _PACKETSENDERS_H */ + + diff --git a/sc2/src/sc2code/netplay/proto/Makeinfo b/sc2/src/sc2code/netplay/proto/Makeinfo new file mode 100644 index 000000000..3e0225bae --- /dev/null +++ b/sc2/src/sc2code/netplay/proto/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="npconfirm.c ready.c" + diff --git a/sc2/src/sc2code/netplay/proto/npconfirm.c b/sc2/src/sc2code/netplay/proto/npconfirm.c new file mode 100644 index 000000000..69292191b --- /dev/null +++ b/sc2/src/sc2code/netplay/proto/npconfirm.c @@ -0,0 +1,81 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "../netplay.h" +#include "npconfirm.h" + +#include "types.h" +#include "../netmisc.h" +#include "../packetsenders.h" + +#include +#include + +int +Netplay_confirm(NetConnection *conn) { + assert(handshakeMeaningful(NetConnection_getState(conn))); + + if (conn->stateFlags.handshake.localOk) { + // Already confirmed + errno = EINVAL; + return -1; + } + + conn->stateFlags.handshake.localOk = true; + + if (conn->stateFlags.handshake.canceling) { + // If a previous confirmation was cancelled, but the cancel + // is not acknowledged yet, we don't have to send anything yet. + // The handshake0 packet will be sent when the acknowledgement + // arrives. + } else if (conn->stateFlags.handshake.remoteOk) { + // A Handshake0 is implied by the following Handshake1. + sendHandshake1(conn); + } else { + sendHandshake0(conn); + } + + return 0; +} + +int +Netplay_cancelConfirmation(NetConnection *conn) { + assert(handshakeMeaningful(NetConnection_getState(conn))); + + if (!conn->stateFlags.handshake.localOk) { + // Not confirmed, or already canceling. + errno = EINVAL; + return -1; + } + + conn->stateFlags.handshake.localOk = false; + if (conn->stateFlags.handshake.canceling) { + // If previous cancellation is still waiting to be acknowledged, + // the confirmation we are cancelling here, has not actually been + // sent yet. By setting the localOk flag to false, it is + // cancelled, without the need for any packets to be sent. + } else { + conn->stateFlags.handshake.canceling = true; + sendHandshakeCancel(conn); + } + + return 0; +} + + diff --git a/sc2/src/sc2code/netplay/proto/npconfirm.h b/sc2/src/sc2code/netplay/proto/npconfirm.h new file mode 100644 index 000000000..432e7d5b1 --- /dev/null +++ b/sc2/src/sc2code/netplay/proto/npconfirm.h @@ -0,0 +1,30 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NPCONFIRM_H +#define _NPCONFIRM_H + +#include "../netplay.h" +#include "../netconnection.h" + +int Netplay_confirm(NetConnection *conn); +int Netplay_cancelConfirmation(NetConnection *conn); + +#endif /* _NPCONFIRM_H */ + + diff --git a/sc2/src/sc2code/netplay/proto/ready.c b/sc2/src/sc2code/netplay/proto/ready.c new file mode 100644 index 000000000..06bb1359c --- /dev/null +++ b/sc2/src/sc2code/netplay/proto/ready.c @@ -0,0 +1,106 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "../netplay.h" +#include "ready.h" + +#include "types.h" +#include "../netmisc.h" +#include "../packetsenders.h" + +#include + +static void +Netplay_bothReady(NetConnection *conn) { + NetConnection_ReadyCallback callback; + void *readyArg; + + assert(conn->readyCallback != NULL); + + callback = conn->readyCallback; + readyArg = conn->readyCallbackArg; + + conn->readyCallback = NULL; + // Clear the readyCallback field before performing the callback, + // so that it can be set again from inside the callback + // function. + conn->readyCallbackArg = NULL; + callback(conn, readyArg); +} + +// If notifyRemote is set, a 'Ready' message will be sent to the other side. +// returns true iff both sides are ready. +// Inside the callback function, ready flags may be set for a possible +// next Ready communication. +bool +Netplay_localReady(NetConnection *conn, NetConnection_ReadyCallback callback, + void *readyArg, bool notifyRemote) { + assert(readyFlagsMeaningful(NetConnection_getState(conn))); + assert(!conn->stateFlags.ready.localReady); + assert(callback != NULL); + + NetConnection_setReadyCallback(conn, callback, readyArg); + + if (notifyRemote) + sendReady(conn); + if (!conn->stateFlags.ready.remoteReady) { + conn->stateFlags.ready.localReady = true; + return false; + } + + // Reset ready flags: + conn->stateFlags.ready.remoteReady = false; + + // Trigger the callback. + Netplay_bothReady(conn); + return true; +} + +// returns true iff both sides are ready. +bool +Netplay_remoteReady(NetConnection *conn) { + assert(readyFlagsMeaningful(NetConnection_getState(conn))); + // This is supposed to be already verified by the calling + // function. + assert(!conn->stateFlags.ready.remoteReady); + + if (!conn->stateFlags.ready.localReady) { + conn->stateFlags.ready.remoteReady = true; + return false; + } + + // Reset ready flags: + conn->stateFlags.ready.localReady = false; + + // Trigger the callback. + Netplay_bothReady(conn); + return true; +} + +bool +Netplay_isLocalReady(NetConnection *conn) { + return conn->stateFlags.ready.localReady; +} + +bool +Netplay_isRemoteReady(NetConnection *conn) { + return conn->stateFlags.ready.remoteReady; +} + + diff --git a/sc2/src/sc2code/netplay/proto/ready.h b/sc2/src/sc2code/netplay/proto/ready.h new file mode 100644 index 000000000..384237656 --- /dev/null +++ b/sc2/src/sc2code/netplay/proto/ready.h @@ -0,0 +1,31 @@ +/* + * Copyright 2006 Serge van den Boom + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _READY_H +#define _READY_H + +#include "../netconnection.h" + +bool Netplay_localReady(NetConnection *conn, + NetConnection_ReadyCallback callback, void *arg, bool notifyRemote); +bool Netplay_remoteReady(NetConnection *conn); +bool Netplay_isLocalReady(NetConnection *conn); +bool Netplay_isRemoteReady(NetConnection *conn); + +#endif /* _READY_H */ + diff --git a/sc2/src/sc2code/pickmele.c b/sc2/src/sc2code/pickmele.c index e62258f6c..0aa776ed9 100644 --- a/sc2/src/sc2code/pickmele.c +++ b/sc2/src/sc2code/pickmele.c @@ -22,6 +22,11 @@ #include "controls.h" #include "intel.h" #include "melee.h" +#ifdef NETPLAY +# include "netplay/netmelee.h" +# include "netplay/netmisc.h" +# include "netplay/notify.h" +#endif #include "races.h" #include "settings.h" #include "setup.h" @@ -35,23 +40,107 @@ #define PICK_Y_OFFS 24 #define PICK_SIDE_OFFS 100 -typedef struct getmelee_struct { +struct getmelee_struct { BOOLEAN (*InputFunc) (struct getmelee_struct *pInputState); COUNT MenuRepeatDelay; BOOLEAN Initialized; HSTARSHIP hBattleShip; COUNT row, col, ships_left, which_player; + COUNT randomIndex; +#ifdef NETPLAY + BOOLEAN remoteSelected; +#endif RECT flash_rect; -} GETMELEE_STATE; +}; +#ifdef NETPLAY +static void reportShipSelected (GETMELEE_STATE *gms, COUNT index); +#endif +// Returns the th ship in the queue, or 0 if it is not available. +// For all the ships in the queue, the ShipFacing field contains the +// index in the queue. +static HSTARSHIP +MeleeShipByQueueIndex (const QUEUE *queue, COUNT index) +{ + HSTARSHIP hShip; + HSTARSHIP hNextShip; + + for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip) + { + STARSHIPPTR StarShipPtr = LockStarShip (queue, hShip); + if (StarShipPtr->ShipFacing == index) + { + hNextShip = hShip; + if (StarShipPtr->RaceResIndex == 0) + hShip = 0; + UnlockStarShip (queue, hNextShip); + break; + } + hNextShip = _GetSuccLink (StarShipPtr); + UnlockStarShip (queue, ship); + } + + return hShip; +} + +// Returns the th available ship in the queue. +static HSTARSHIP +MeleeShipByUsedIndex (const QUEUE *queue, COUNT index) +{ + HSTARSHIP hShip; + HSTARSHIP hNextShip; + + for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip) + { + STARSHIPPTR StarShipPtr = LockStarShip (queue, hShip); + if (StarShipPtr->RaceResIndex && index-- == 0) + { + UnlockStarShip (queue, hShip); + break; + } + hNextShip = _GetSuccLink (StarShipPtr); + UnlockStarShip (queue, hShip); + } + + return hShip; +} + +#if 0 +static COUNT +queueIndexFromShip (HSTARSHIP hShip) +{ + COUNT result; + STARSHIPPTR StarShipPtr = LockStarShip (queue, hShip); + result = StarShipPtr->ShipFacing; + UnlockStarShip (queue, hShip); +} +#endif + +static void +PickMelee_ChangedSelection (GETMELEE_STATE *gms) +{ + LockMutex (GraphicsLock); + gms->flash_rect.corner.x = PICK_X_OFFS + ((ICON_WIDTH + 2) * gms->col); + gms->flash_rect.corner.y = PICK_Y_OFFS + ((ICON_HEIGHT + 2) * gms->row) + + ((1 - gms->which_player) * PICK_SIDE_OFFS); + SetFlashRect (&gms->flash_rect, (FRAME)0); + UnlockMutex (GraphicsLock); +} + +// Select a new ship from the fleet for battle. +// Returns 'TRUE' if no choice has been made yet; this function is to be +// called again later. +// Returns 'FALSE' if a choice has been made. gms->hStarShip is set +// to the chosen (or randomly selected) ship, or to 0 if 'exit' has +// been chosen. /* TODO: Include player timeouts */ static BOOLEAN DoGetMelee (GETMELEE_STATE *gms) { BOOLEAN left, right, up, down, select; - HSTARSHIP hNextShip; COUNT which_player = gms->which_player; + BOOLEAN done = FALSE; SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); @@ -60,11 +149,23 @@ DoGetMelee (GETMELEE_STATE *gms) gms->Initialized = TRUE; gms->row = 0; gms->col = NUM_MELEE_COLS_ORIG; +#ifdef NETPLAY + gms->remoteSelected = FALSE; +#endif - goto ChangeSelection; + // We determine upfront which ship would be chosen if the player + // wants a random ship, to keep it simple to keep network parties + // synchronised. + gms->randomIndex = (COUNT)TFB_Random () % gms->ships_left; + + PickMelee_ChangedSelection (gms); + return TRUE; } SleepThread (ONE_SECOND / 120); +#ifdef NETPLAY + netInput (); +#endif if (PlayerInput[which_player] == ComputerInput) { @@ -74,51 +175,85 @@ DoGetMelee (GETMELEE_STATE *gms) left = right = up = down = FALSE; select = TRUE; } - else if (which_player == 0) +#ifdef NETPLAY + else if (PlayerInput[which_player] == NetworkInput) { - left = PulsedInputState.key[PlayerOne][KEY_LEFT] || - PulsedInputState.menu[KEY_MENU_LEFT]; - right = PulsedInputState.key[PlayerOne][KEY_RIGHT] || - PulsedInputState.menu[KEY_MENU_RIGHT]; - up = PulsedInputState.key[PlayerOne][KEY_UP] || - PulsedInputState.menu[KEY_MENU_UP]; - down = PulsedInputState.key[PlayerOne][KEY_DOWN] || - PulsedInputState.menu[KEY_MENU_DOWN]; - select = PulsedInputState.key[PlayerOne][KEY_WEAPON] || - PulsedInputState.menu[KEY_MENU_SELECT]; + flushPacketQueues (); + if (gms->remoteSelected) + return FALSE; + return TRUE; } +#endif else { - left = PulsedInputState.key[PlayerTwo][KEY_LEFT] || - PulsedInputState.menu[KEY_MENU_LEFT]; - right = PulsedInputState.key[PlayerTwo][KEY_RIGHT] || - PulsedInputState.menu[KEY_MENU_RIGHT]; - up = PulsedInputState.key[PlayerTwo][KEY_UP] || - PulsedInputState.menu[KEY_MENU_UP]; - down = PulsedInputState.key[PlayerTwo][KEY_DOWN] || - PulsedInputState.menu[KEY_MENU_DOWN]; - select = PulsedInputState.key[PlayerTwo][KEY_WEAPON] || - PulsedInputState.menu[KEY_MENU_SELECT]; + CONTROL_TEMPLATE template; + + if (which_player == 0) + template = PlayerOne; + else + template = PlayerTwo; + + left = PulsedInputState.key[template][KEY_LEFT]; + right = PulsedInputState.key[template][KEY_RIGHT]; + up = PulsedInputState.key[template][KEY_UP]; + down = PulsedInputState.key[template][KEY_DOWN]; + select = PulsedInputState.key[template][KEY_WEAPON]; } if (GLOBAL (CurrentActivity) & CHECK_ABORT) { gms->hBattleShip = 0; GLOBAL (CurrentActivity) &= ~CHECK_ABORT; - return FALSE; +#ifdef NETPLAY + // TODO: send abort packet +#endif + done = true; } - - if (select) + else if (select) { - if (gms->hBattleShip || - (gms->col == NUM_MELEE_COLS_ORIG && ConfirmExit ())) + if (gms->col == NUM_MELEE_COLS_ORIG) { - GLOBAL (CurrentActivity) &= ~CHECK_ABORT; - return FALSE; + if (gms->row == 0) + { + // Random ship + gms->hBattleShip = MeleeShipByUsedIndex ( + &race_q[which_player], gms->randomIndex); +#ifdef NETPLAY + reportShipSelected (gms, (COUNT)~0); +#endif + done = TRUE; + } + else + { + // Exit + if (ConfirmExit ()) + { + gms->hBattleShip = 0; + GLOBAL (CurrentActivity) &= ~CHECK_ABORT; + done = TRUE; + } + } + } + else + { + COUNT ship_index; + + ship_index = (gms->row * NUM_MELEE_COLS_ORIG) + gms->col; + gms->hBattleShip = MeleeShipByQueueIndex ( + &race_q[which_player], ship_index); + if (gms->hBattleShip != 0) + { + // Selection contains a ship. +#ifdef NETPLAY + reportShipSelected (gms, ship_index); +#endif + done = TRUE; + } } } else { + // Process motion commands. COUNT new_row, new_col; new_row = gms->row; @@ -146,72 +281,30 @@ DoGetMelee (GETMELEE_STATE *gms) if (new_row != gms->row || new_col != gms->col) { - COUNT ship_index; - gms->row = new_row; gms->col = new_col; PlayMenuSound (MENU_SOUND_MOVE); -ChangeSelection: - LockMutex (GraphicsLock); - gms->flash_rect.corner.x = PICK_X_OFFS - + ((ICON_WIDTH + 2) * gms->col); - gms->flash_rect.corner.y = PICK_Y_OFFS - + ((ICON_HEIGHT + 2) * gms->row) - + ((1 - which_player) * PICK_SIDE_OFFS); - SetFlashRect (&gms->flash_rect, (FRAME)0); - - gms->hBattleShip = GetHeadLink (&race_q[which_player]); - if (gms->col == NUM_MELEE_COLS_ORIG) - { - if (gms->row) - gms->hBattleShip = 0; - else - { - ship_index = (COUNT)TFB_Random () % gms->ships_left; - for (gms->hBattleShip = GetHeadLink (&race_q[which_player]); - gms->hBattleShip != 0; gms->hBattleShip = hNextShip) - { - STARSHIPPTR StarShipPtr = LockStarShip ( - &race_q[which_player], gms->hBattleShip); - if (StarShipPtr->RaceResIndex && ship_index-- == 0) - { - UnlockStarShip (&race_q[which_player], - gms->hBattleShip); - break; - } - hNextShip = _GetSuccLink (StarShipPtr); - UnlockStarShip (&race_q[which_player], - gms->hBattleShip); - } - } - } - else - { - ship_index = (gms->row * NUM_MELEE_COLS_ORIG) + gms->col; - for (gms->hBattleShip = GetHeadLink (&race_q[which_player]); - gms->hBattleShip != 0; gms->hBattleShip = hNextShip) - { - STARSHIPPTR StarShipPtr = LockStarShip ( - &race_q[which_player], gms->hBattleShip); - if (StarShipPtr->ShipFacing == ship_index) - { - hNextShip = gms->hBattleShip; - if (StarShipPtr->RaceResIndex == 0) - gms->hBattleShip = 0; - UnlockStarShip (&race_q[which_player], hNextShip); - break; - } - hNextShip = _GetSuccLink (StarShipPtr); - UnlockStarShip (&race_q[which_player], gms->hBattleShip); - } - } - UnlockMutex (GraphicsLock); + PickMelee_ChangedSelection (gms); } } - return TRUE; + +#ifdef NETPLAY + flushPacketQueues (); +#endif + + return !done; } +#ifdef NETPLAY +static void +endMeleeCallback (NetConnection *conn, void *arg) +{ + NetMelee_reenterState_inSetup (conn); + (void) arg; +} +#endif + HSTARSHIP GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) { @@ -222,17 +315,30 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) CONTEXT OldContext; GETMELEE_STATE gmstate; STARSHIPPTR StarShipPtr; + BOOLEAN firstTime; if (!(GLOBAL (CurrentActivity) & IN_BATTLE)) return (0); +#ifdef NETPLAY + { + NetConnection *conn = netConnections[which_player]; + if (conn != NULL) { + BattleStateData *battleStateData; + battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + battleStateData->getMeleeState = &gmstate; + } + } +#endif + s.frame = SetAbsFrameIndex (PickMeleeFrame, which_player); OldContext = SetContext (OffScreenContext); SetContextFGFrame (s.frame); if (LastStarShipPtr == 0 || LastStarShipPtr->special_counter == 0) { - COUNT cur_bucks; + COUNT cur_bucks; HSTARSHIP hBattleShip, hNextShip; cur_bucks = 0; @@ -264,7 +370,8 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) GetFrameRect (s.frame, &gmstate.flash_rect); gmstate.flash_rect.extent.width -= 4; t.baseline.x = gmstate.flash_rect.extent.width; - gmstate.flash_rect.corner.x = gmstate.flash_rect.extent.width - (6 * 3); + gmstate.flash_rect.corner.x = + gmstate.flash_rect.extent.width - (6 * 3); gmstate.flash_rect.corner.y = 2; gmstate.flash_rect.extent.width = (6 * 3); gmstate.flash_rect.extent.height = 7 - 2; @@ -291,6 +398,7 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) if (LOBYTE (battle_counter) == 0 || HIBYTE (battle_counter) == 0) { + // One side is out of ships. Game over. DWORD TimeOut; BOOLEAN PressState, ButtonState; @@ -321,12 +429,33 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) && (!(PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL) || GetTimeCounter () < TimeOut))); +#ifdef NETPLAY + setStateConnections (NetState_endMelee); + localReadyConnections (endMeleeCallback, NULL, true); +#endif + LockMutex (GraphicsLock); return (0); } - if (LastStarShipPtr == 0 && which_player) + // Fade in if we're not already faded in. +#ifdef NETPLAY + // Hack. If one side is network controlled, the top player does not have + // to be the first player to get a chance to select his ship, + // As what is top locally doesn't have to be the same as what it is + // remotely. + // This check is not so nice as it replicates the code used to decide + // which party is first in selectAllShips. + firstTime = (LastStarShipPtr == 0) && ((which_player == 0) == + (((PlayerControl[0] & NETWORK_CONTROL) && + !NetConnection_getDiscriminant(netConnections[0])) || + ((PlayerControl[1] & NETWORK_CONTROL) && + NetConnection_getDiscriminant(netConnections[1])))); +#else + firstTime = LastStarShipPtr == 0 && which_player == 1; +#endif + if (firstTime) { BYTE fade_buf[] = {FadeAllToColor}; @@ -342,6 +471,16 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) gmstate.flash_rect.extent.width = (ICON_WIDTH + 2); gmstate.flash_rect.extent.height = (ICON_HEIGHT + 2); +#ifdef NETPLAY + { + bool allOk = negotiateReadyConnections (true, NetState_selectShip); + if (!allOk) + { + // Some network connection has been reset. + gmstate.hBattleShip = 0; + } + } +#endif gmstate.InputFunc = DoGetMelee; SetDefaultMenuRepeatDelay (); gmstate.Initialized = FALSE; @@ -359,7 +498,8 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) GLOBAL (CurrentActivity) &= ~IN_BATTLE; else { - StarShipPtr = LockStarShip (&race_q[which_player], gmstate.hBattleShip); + StarShipPtr = + LockStarShip (&race_q[which_player], gmstate.hBattleShip); OwnStarShip (StarShipPtr, 1 << which_player, StarShipPtr->captains_name_index); StarShipPtr->captains_name_index = 0; @@ -370,7 +510,78 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) WaitForSoundEnd (0); } +#ifdef NETPLAY + { + NetConnection *conn = netConnections[which_player]; + if (conn != NULL) + { + BattleStateData *battleStateData; + battleStateData = (BattleStateData *) + NetConnection_getStateData(conn); + battleStateData->getMeleeState = NULL; + if (gmstate.hBattleShip == 0) + NetConnection_setState(conn, NetState_inSetup); + } + } +#endif + return (gmstate.hBattleShip); } +#ifdef NETPLAY +// Called when a ship selection has arrived from a remote player. +bool +updateMeleeSelection (GETMELEE_STATE *gms, COUNT player, COUNT ship) +{ + if (gms == NULL) + { + // This happens when we get an update message from a connection + // for who we are not selecting a ship (yet?). + fprintf (stderr, "Unexpected ship selection packet received.\n"); + return false; + } + + if (ship == (COUNT) ~0) + { + // Random selection. + gms->hBattleShip = + MeleeShipByUsedIndex (&race_q[player], gms->randomIndex); + gms->remoteSelected = TRUE; + NetConnection_setState (netConnections[player], NetState_interBattle); + return true; + } + + gms->hBattleShip = MeleeShipByQueueIndex (&race_q[player], ship); + if (gms->hBattleShip == 0) + { + fprintf (stderr, "Invalid ship selection received from remote " + "party.\n"); + return false; + } + + gms->remoteSelected = TRUE; + NetConnection_setState (netConnections[player], NetState_interBattle); + return true; +} + +static void +reportShipSelected (GETMELEE_STATE *gms, COUNT index) +{ + size_t playerI; + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) + { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + Netplay_selectShip (conn, index); + NetConnection_setState (conn, NetState_interBattle); + } + (void) gms; +} +#endif diff --git a/sc2/src/sc2code/pickmele.h b/sc2/src/sc2code/pickmele.h index 0528ad64c..cc4cfbb47 100644 --- a/sc2/src/sc2code/pickmele.h +++ b/sc2/src/sc2code/pickmele.h @@ -23,6 +23,9 @@ HSTARSHIP GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player); +typedef struct getmelee_struct GETMELEE_STATE; + +bool updateMeleeSelection (GETMELEE_STATE *gms, COUNT player, COUNT ship); #endif /* _PICKMELE_H */ diff --git a/sc2/src/sc2code/planets/lander.c b/sc2/src/sc2code/planets/lander.c index 5c7b4c6dc..0486d0917 100644 --- a/sc2/src/sc2code/planets/lander.c +++ b/sc2/src/sc2code/planets/lander.c @@ -141,26 +141,26 @@ DamageColorCycle (COLOR c, COUNT i) { WHITE_COLOR, BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x00), 0x2A), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0x7, 0x00), 0x7E), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0xE, 0x00), 0x7C), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x07, 0x00), 0x7E), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0E, 0x00), 0x7C), BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A), BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1C, 0x00), 0x78), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0xA), 0x0E), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E), }; if (i) c = damage_tab[i]; else if (c == WHITE_COLOR) c = damage_tab[6]; - else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0xA), 0x0E)) + else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)) c = damage_tab[5]; else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1C, 0x00), 0x78)) c = damage_tab[4]; else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)) c = damage_tab[3]; - else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0xE, 0x00), 0x7C)) + else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0E, 0x00), 0x7C)) c = damage_tab[2]; - else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x7, 0x00), 0x7E)) + else if (c == BUILD_COLOR (MAKE_RGB15 (0x1F, 0x07, 0x00), 0x7E)) c = damage_tab[1]; else c = damage_tab[0]; @@ -1944,16 +1944,20 @@ PlanetSide (PMENU_STATE pMS) PSD.MineralText[1] = PSD.MineralText[0]; PSD.MineralText[2] = PSD.MineralText[1]; - PSD.ColorCycle[0] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x3, 0x00), 0x7F); - PSD.ColorCycle[1] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0xA, 0x00), 0x7D); + PSD.ColorCycle[0] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7F); + PSD.ColorCycle[1] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D); PSD.ColorCycle[2] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B); PSD.ColorCycle[3] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71); for (index = 4; index < (NUM_TEXT_FRAMES >> 1) - 4; ++index) PSD.ColorCycle[index] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F); - PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 4] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71); - PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 3] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B); - PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 2] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0xA, 0x000), 0x7D); - PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 1] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x3, 0x00), 0x7F); + PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 4] = + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71); + PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 3] = + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B); + PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 2] = + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D); + PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 1] = + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7F); pMS->ModuleFrame = (FRAME)&PSD; index = NORMALIZE_FACING ((COUNT)TFB_Random ()); diff --git a/sc2/src/sc2code/setup.c b/sc2/src/sc2code/setup.c index c0c0ecc23..1d282059d 100644 --- a/sc2/src/sc2code/setup.c +++ b/sc2/src/sc2code/setup.c @@ -22,6 +22,9 @@ #include "controls.h" #include "options.h" #include "nameref.h" +#ifdef NETPLAY +# include "netplay/netmelee.h" +#endif #include "init.h" #include "intel.h" #include "resinst.h" @@ -72,7 +75,10 @@ InitPlayerInput (void) { HumanInput[0] = p1_combat_summary; HumanInput[1] = p2_combat_summary; - ComputerInput = computer_intelligence; + ComputerInput = computer_intelligence; +#ifdef NETPLAY + NetworkInput = networkBattleInput; +#endif } void @@ -215,10 +221,11 @@ SetPlayerInput (void) for (which_player = 0; which_player < NUM_PLAYERS; ++which_player) { - if (!(PlayerControl[which_player] & HUMAN_CONTROL)) + if (PlayerControl[which_player] & COMPUTER_CONTROL) PlayerInput[which_player] = ComputerInput; else if (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE) { + // Full game. if (which_player == 0) PlayerInput[which_player] = HumanInput[0]; else @@ -227,6 +234,10 @@ SetPlayerInput (void) PlayerControl[which_player] = COMPUTER_CONTROL | AWESOME_RATING; } } +#ifdef NETPLAY + else if (PlayerControl[which_player] & NETWORK_CONTROL) + PlayerInput[which_player] = NetworkInput; +#endif else PlayerInput[which_player] = HumanInput[which_player]; } diff --git a/sc2/src/sc2code/tactrans.c b/sc2/src/sc2code/tactrans.c index e820fbc39..e0342c603 100644 --- a/sc2/src/sc2code/tactrans.c +++ b/sc2/src/sc2code/tactrans.c @@ -20,6 +20,13 @@ #include "collide.h" #include "globdata.h" #include "init.h" +#ifdef NETPLAY +# include "netplay/netmelee.h" +# include "netplay/netmisc.h" +# include "netplay/notify.h" +# include "netplay/proto/ready.h" +# include "netplay/packetq.h" +#endif #include "races.h" #include "settings.h" #include "sounds.h" @@ -49,6 +56,151 @@ OpponentAlive (STARSHIPPTR TestStarShipPtr) return TRUE; } +#ifdef NETPLAY +static void +readyToEnd2Callback (NetConnection *conn, void *arg) +{ + NetConnection_setState (conn, NetState_endingBattle2); + (void) arg; +} + +static void +readyToEndCallback (NetConnection *conn, void *arg) +{ + // This callback function gets called from inside the function that + // updates the frame counter, but this is not a problem as the + // ending frame count will at least be 1 greater than the current + // frame count. + + BattleStateData *battleStateData; + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + +#ifdef NETPLAY_DEBUG + fprintf (stderr, "Both sides are ready to end the battle; starting " + "end-of-battle synchronisation.\n"); +#endif + NetConnection_setState (conn, NetState_endingBattle); + if (battleFrameCount + 1 > battleStateData->endFrameCount) + battleStateData->endFrameCount = battleFrameCount + 1; + Netplay_sendFrameCount (conn, battleFrameCount + 1); + // The +1 is to ensure that after the remote side receives the + // frame count it will still receive one more frame data packet, + // so it will know in advance when the last frame data packet + // will come so it won't block. It also ensures that the + // local frame counter won't go past the sent number, which + // could happen when the function triggering the call to this + // function is the frame update function which might update + // the frame counter one more time. + flushPacketQueue (conn); +#ifdef NETPLAY_DEBUG + fprintf (stderr, "NETPLAY: [%d] ==> Sent battleFrameCount %d.\n", + NetConnection_getPlayerNr(conn), battleFrameCount + 1); +#endif + Netplay_localReady(conn, readyToEnd2Callback, NULL, false); + (void) arg; +} + +/* + * When one player's ship dies, there's a delay before the next ship + * can be chosen. This time depends on the time the ditty is playing + * and may differ for each side. + * To synchronise the time, the following protocol is followed: + * 1. (NetState_inBattle) The Ready protocol is used to let either + * party know when they're ready to stop the battle. + * 2. (NetState_endingBattle) Each party sends the frame number of when + * it wants to end the battle, and continues until that point, where + * it waits until it has received the frame number of the other party. + * 3. After a player has both sent and received a frame count, the + * simulation continues for each party, until the maximum of both + * frame counts has been achieved. + * 4. The Ready protocol is used to let each side signal that the it has + * reached the target frame count. + * 5. The battle ends. + */ +static bool +readyForBattleEndPlayer (NetConnection *conn, void *arg) +{ + BattleStateData *battleStateData; + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + + if (NetConnection_getState (conn) == NetState_interBattle) + { + // This connection is already ready. The entire synchronisation + // protocol has already been done for this connection. + return true; + } + + if (NetConnection_getState (conn) == NetState_inBattle) + { + if (Netplay_isLocalReady(conn)) + { + // We've already sent notice that we are ready, but we're + // still waiting for the other side to say it's ready too. + return false; + } + + // We haven't yet told the other side we're ready. We do so now. + Netplay_localReady (conn, readyToEndCallback, NULL, true); + // This may set the state to endingBattle. + + if (NetConnection_getState (conn) == NetState_inBattle) + return false; + } + + assert (NetConnection_getState (conn) == NetState_endingBattle || + NetConnection_getState (conn) == NetState_endingBattle2); + + // Keep the simulation going as long as the target frame count + // hasn't been reached yet. Note that if the connection state is + // NetState_endingBattle, that we haven't yet received the + // remote frame count, so the target frame count may still rise. + if (battleFrameCount < battleStateData->endFrameCount) + return false; + + if (NetConnection_getState (conn) == NetState_endingBattle) + { + // We have reached the target frame count, but we don't know + // the remote target frame count yet. So we wait until it has + // come in. + waitReady (conn); + // TODO: check whether all connections are still connected. + assert (NetConnection_getState (conn) == NetState_endingBattle2); + + // Continue the simulation if the battleFrameCount has gone up. + if (battleFrameCount < battleStateData->endFrameCount) + return false; + } + + // We are ready and wait for the other party to become ready too. + negotiateReady (conn, true, NetState_interBattle); + + (void) arg; + return true; +} +#endif + +static inline bool +readyForBattleEnd (void) +{ +#ifndef NETPLAY +#if DEMO_MODE + // In Demo mode, the saved journal should be replayed with frame + // accuracy. PLRPlaying () isn't consistent enough. + return true; +#else /* !DEMO_MODE */ + return PLRPlaying ((MUSIC_REF)~0); +#endif /* !DEMO_MODE */ +#else /* defined (NETPLAY) */ + if (PLRPlaying ((MUSIC_REF)~0)) + return false; + + if (!forAllConnectedPlayers (readyForBattleEndPlayer, NULL)) + return false; + + return true; +#endif /* defined (NETPLAY) */ +} + void new_ship (PELEMENT DeadShipPtr) { @@ -86,6 +238,10 @@ new_ship (PELEMENT DeadShipPtr) MusicStarted = FALSE; DeadShipPtr->turn_wait = (BYTE)( DeadShipPtr->state_flags & (GOOD_GUY | BAD_GUY)); + // DeadShipPtr->turn_wait is abused to store which + // side this element is for, probably because this + // information will be lost from state_flags (why is this + // necessary?). for (hElement = GetHeadElement (); hElement; hElement = hSuccElement) { ELEMENTPTR ElementPtr; @@ -105,9 +261,9 @@ new_ship (PELEMENT DeadShipPtr) ElementPtr->life_span = 0; ElementPtr->state_flags = NONSOLID | DISAPPEARING | FINITE_LIFE; - ElementPtr->preprocess_func = - ElementPtr->postprocess_func = - ElementPtr->death_func = 0; + ElementPtr->preprocess_func = 0; + ElementPtr->postprocess_func = 0; + ElementPtr->death_func = 0; ElementPtr->collision_func = 0; } } @@ -124,23 +280,20 @@ new_ship (PELEMENT DeadShipPtr) UnlockElement (hElement); } - DeadShipPtr->life_span = MusicStarted ? - (ONE_SECOND * 3) / BATTLE_FRAME_RATE : 1; + DeadShipPtr->life_span = + MusicStarted ? (ONE_SECOND * 3) / BATTLE_FRAME_RATE : 1; DeadShipPtr->death_func = new_ship; DeadShipPtr->preprocess_func = new_ship; SetElementStarShip (DeadShipPtr, DeadStarShipPtr); } - if (DeadShipPtr->life_span -#if !DEMO_MODE - || PLRPlaying ((MUSIC_REF)~0) -#endif /* DEMO_MODE */ - ) + if (DeadShipPtr->life_span || !readyForBattleEnd ()) { DeadShipPtr->state_flags &= ~DISAPPEARING; ++DeadShipPtr->life_span; + return; } - else + { BOOLEAN RestartMusic; @@ -155,12 +308,57 @@ new_ship (PELEMENT DeadShipPtr) DeadStarShipPtr->RaceDescPtr); free_ship (DeadStarShipPtr, TRUE); UnbatchGraphics (); + +#ifdef NETPLAY + initBattleStateDataConnections (); + { + bool allOk = + negotiateReadyConnections (true, NetState_interBattle); + // We are already in NetState_interBattle, but all + // sides just need to pass this checkpoint before + // going on. + if (!allOk) + { + // Some network connection has been reset. + GLOBAL (CurrentActivity) &= ~IN_BATTLE; + BatchGraphics (); + return; + } + } +#endif /* NETPLAY */ + if (GetNextStarShip (DeadStarShipPtr, WHICH_SIDE (DeadShipPtr->turn_wait)) && RestartMusic) + { +#ifdef NETPLAY + { + bool allOk = + negotiateReadyConnections (true, NetState_inBattle); + if (!allOk) + { + // Some network connection has been reset. + GLOBAL (CurrentActivity) &= ~IN_BATTLE; + BatchGraphics (); + return; + } + } +#endif BattleSong (TRUE); + } else if (LOBYTE (battle_counter) == 0 || HIBYTE (battle_counter) == 0) GLOBAL (CurrentActivity) &= ~IN_BATTLE; + +#ifdef NETPLAY + // Turn_wait was abused to store the side this element was on. + // We don't want this included in checksums, as it can be different + // for both sides of a connection. + // While preprocess_func == new_ship, turn_wait isn't included in + // the checksum at all all, but now that new_ship is done, + // it would be. As the value is irrelevant at this point, we can + // just set it to the same value on either side. + DeadShipPtr->turn_wait = 0; +#endif BatchGraphics (); } } @@ -225,20 +423,16 @@ explosion_preprocess (PELEMENT ShipPtr) if (HIBYTE (LOWORD (rand_val)) < 256 * 1 / 3) dist += DISPLAY_TO_WORLD (8); ElementPtr->current.location.x = - ShipPtr->current.location.x - + COSINE (angle, dist); + ShipPtr->current.location.x + COSINE (angle, dist); ElementPtr->current.location.y = - ShipPtr->current.location.y - + SINE (angle, dist); + ShipPtr->current.location.y + SINE (angle, dist); ElementPtr->preprocess_func = animation_preprocess; rand_val = TFB_Random (); angle = LOBYTE (LOWORD (rand_val)); dist = WORLD_TO_VELOCITY ( - DISPLAY_TO_WORLD (HIBYTE (LOWORD (rand_val)) % 5) - ); + DISPLAY_TO_WORLD (HIBYTE (LOWORD (rand_val)) % 5)); SetVelocityComponents (&ElementPtr->velocity, - COSINE (angle, dist), - SINE (angle, dist)); + COSINE (angle, dist), SINE (angle, dist)); UnlockElement (hElement); } } while (--i); @@ -380,14 +574,14 @@ spawn_ion_trail (PELEMENT ElementPtr) BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x00), 0x2b), BUILD_COLOR (MAKE_RGB15 (0x17, 0x00, 0x00), 0x2c), BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2d), - BUILD_COLOR (MAKE_RGB15 (0xF, 0x00, 0x00), 0x2e), - BUILD_COLOR (MAKE_RGB15 (0xB, 0x00, 0x00), 0x2f), + BUILD_COLOR (MAKE_RGB15 (0x0F, 0x00, 0x00), 0x2e), + BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2f), BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7a), BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7b), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0xE, 0x00), 0x7c), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0xA, 0x00), 0x7d), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0x7, 0x00), 0x7e), - BUILD_COLOR (MAKE_RGB15 (0x1F, 0x3, 0x00), 0x7f), }; + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0E, 0x00), 0x7c), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7d), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x07, 0x00), 0x7e), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7f), }; #define NUM_TAB_COLORS (sizeof (color_tab) / sizeof (color_tab[0])) COUNT color_index = 0; @@ -480,11 +674,12 @@ ship_transition (PELEMENT ElementPtr) LockElement (hShipImage, &ShipImagePtr); ShipImagePtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID; - ShipImagePtr->life_span = ShipImagePtr->thrust_wait = TRANSITION_LIFE; - SetPrimType (&DisplayArray[ShipImagePtr->PrimIndex], STAMPFILL_PRIM); - SetPrimColor ( - &DisplayArray[ShipImagePtr->PrimIndex], START_ION_COLOR - ); + ShipImagePtr->life_span = ShipImagePtr->thrust_wait = + TRANSITION_LIFE; + SetPrimType (&DisplayArray[ShipImagePtr->PrimIndex], + STAMPFILL_PRIM); + SetPrimColor (&DisplayArray[ShipImagePtr->PrimIndex], + START_ION_COLOR); ShipImagePtr->current.image = ElementPtr->current.image; ShipImagePtr->current.location = ElementPtr->current.location; if (!(ElementPtr->state_flags & PLAYER_SHIP)) @@ -531,17 +726,17 @@ flee_preprocess (PELEMENT ElementPtr) COLOR c; COLOR color_tab[] = { - BUILD_COLOR (MAKE_RGB15 (31, 25, 25), 0x24), - BUILD_COLOR (MAKE_RGB15 (31, 19, 19), 0x25), - BUILD_COLOR (MAKE_RGB15 (31, 15, 15), 0x26), - BUILD_COLOR (MAKE_RGB15 (31, 10, 10), 0x27), - BUILD_COLOR (MAKE_RGB15 (31, 4, 4), 0x28), - BUILD_COLOR (MAKE_RGB15 (31, 0, 0), 0x29), - BUILD_COLOR (MAKE_RGB15 (27, 0, 0), 0x2A), - BUILD_COLOR (MAKE_RGB15 (23, 0, 0), 0x2B), - BUILD_COLOR (MAKE_RGB15 (19, 0, 0), 0x2C), - BUILD_COLOR (MAKE_RGB15 (14, 0, 0), 0x2D), - BUILD_COLOR (MAKE_RGB15 (10, 0, 0), 0x2E), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x19, 0x19), 0x24), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x13, 0x13), 0x25), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0F, 0x0F), 0x26), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x27), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x04, 0x04), 0x28), + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x29), + BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x00), 0x2A), + BUILD_COLOR (MAKE_RGB15 (0x17, 0x00, 0x00), 0x2B), + BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2C), + BUILD_COLOR (MAKE_RGB15 (0x0E, 0x00, 0x00), 0x2D), + BUILD_COLOR (MAKE_RGB15 (0x0A, 0x00, 0x00), 0x2E), }; dir = HINIBBLE (ElementPtr->thrust_wait) - 1; diff --git a/sc2/src/starcon2.c b/sc2/src/starcon2.c index 6a2a3ae6a..65b5ae3b0 100644 --- a/sc2/src/starcon2.c +++ b/sc2/src/starcon2.c @@ -38,11 +38,17 @@ #include "options.h" #include "uqmversion.h" #include "comm.h" +#ifdef NETPLAY +# include "libs/callback.h" +# include "libs/alarm.h" +# include "libs/net.h" +# include "netplay/netoptions.h" +#endif #include "setup.h" #include "starcon.h" -#if defined(GFXMODULE_SDL) +#if defined (GFXMODULE_SDL) # include SDL_INCLUDE(SDL.h) // Including this is actually necessary on OSX. #endif @@ -455,6 +461,13 @@ main (int argc, char *argv[]) InitTimeSystem (); InitTaskSystem (); +#ifdef NETPLAY + Network_init (); + Alarm_init (); + Callback_init (); + NetManager_init (); +#endif + GraphicsLock = CreateMutex ("Graphics", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); @@ -481,6 +494,10 @@ main (int argc, char *argv[]) unInitTempDir (); uninitIO (); + +#ifdef NETPLAY + Network_uninit (); +#endif return EXIT_SUCCESS; } @@ -496,6 +513,13 @@ enum STEREOSFX_OPT, ADDON_OPT, ACCEL_OPT, +#ifdef NETPLAY + NETHOST1_OPT, + NETPORT1_OPT, + NETHOST2_OPT, + NETPORT2_OPT, + NETDELAY_OPT, +#endif }; static const char *optString = "+r:d:foc:b:spC:n:?hM:S:T:m:q:ug:l:i:v"; @@ -532,6 +556,13 @@ static struct option longOptions[] = {"stereosfx", 0, NULL, STEREOSFX_OPT}, {"addon", 1, NULL, ADDON_OPT}, {"accel", 1, NULL, ACCEL_OPT}, +#ifdef NETPLAY + {"nethost1", 1, NULL, NETHOST1_OPT}, + {"netport1", 1, NULL, NETPORT1_OPT}, + {"nethost2", 1, NULL, NETHOST2_OPT}, + {"netport2", 1, NULL, NETPORT2_OPT}, + {"netdelay", 1, NULL, NETDELAY_OPT}, +#endif {0, 0, 0, 0} }; @@ -595,7 +626,7 @@ parseOptions (int argc, char *argv[], struct options_struct *options) { int c; optionIndex = -1; - c = getopt_long(argc, argv, optString, longOptions, &optionIndex); + c = getopt_long (argc, argv, optString, longOptions, &optionIndex); if (c == -1) break; @@ -721,9 +752,9 @@ parseOptions (int argc, char *argv[], struct options_struct *options) } case 'g': { - int err = parseFloatOption (optarg, &options->gamma, + int result = parseFloatOption (optarg, &options->gamma, "gamma correction"); - if (err) + if (result == -1) badArg = TRUE; else options->gammaSet = TRUE; @@ -814,6 +845,39 @@ parseOptions (int argc, char *argv[], struct options_struct *options) badArg = TRUE; } break; +#ifdef NETPLAY + case NETHOST1_OPT: + netplayOptions.peer[0].isServer = false; + netplayOptions.peer[0].host = optarg; + break; + case NETPORT1_OPT: + netplayOptions.peer[0].port = optarg; + break; + case NETHOST2_OPT: + netplayOptions.peer[1].isServer = false; + netplayOptions.peer[1].host = optarg; + break; + case NETPORT2_OPT: + netplayOptions.peer[1].port = optarg; + break; + case NETDELAY_OPT: + { + if (parseIntOption (optarg, &netplayOptions.inputDelay, + "network input delay") == -1) + { + badArg = TRUE; + break; + } + + if (netplayOptions.inputDelay > 60 * BATTLE_FRAME_RATE) + { + log_add (log_Fatal, "Network input delay is absurdly " + "large."); + badArg = TRUE; + } + break; + } +#endif default: log_add (log_Fatal, "Error: Invalid option '%s' not found.", longOptions[optionIndex].name); @@ -849,7 +913,7 @@ parseVolume (const char *str, float *vol, const char *optName) log_add (log_Error, "Error: Invalid value for '%s'.", optName); return -1; } - intVol = (int) strtol(str, &endPtr, 10); + intVol = (int) strtol (str, &endPtr, 10); if (*endPtr != '\0') { log_add (log_Error, "Error: Junk characters in volume specified " @@ -884,7 +948,7 @@ parseIntOption (const char *str, int *result, const char *optName) log_add (log_Error, "Error: Invalid value for '%s'.", optName); return -1; } - temp = (int) strtol(str, &endPtr, 10); + temp = (int) strtol (str, &endPtr, 10); if (*endPtr != '\0') { log_add (log_Error, "Error: Junk characters in argument '%s'.", @@ -907,7 +971,7 @@ parseFloatOption (const char *str, float *f, const char *optName) log_add (log_Error, "Error: Invalid value for '%s'.", optName); return -1; } - temp = (float) strtod(str, &endPtr); + temp = (float) strtod (str, &endPtr); if (*endPtr != '\0') { log_add (log_Error, "Error: Junk characters in argument '%s'.", @@ -954,6 +1018,14 @@ usage (FILE *out, const struct options_struct *defaultOptions) "mixsdl)"); log_add (log_User, " --stereosfx (enables positional sound effects, " "currently only for openal)"); +#ifdef NETPLAY + log_add (log_User, " --nethostN=HOSTNAME (server to connect to for " + "player N (1=bottom, 2=top)"); + log_add (log_User, " --netportN=PORT (port to connect to/listen on for " + "player N (1=bottom, 2=top)"); + log_add (log_User, " --netdelay=FRAMES (number of frames to " + "buffer/delay network input for"); +#endif log_add (log_User, "The following options can take either '3do' or 'pc' " "as an option:"); log_add (log_User, " -m, --music : Music version (default %s)", @@ -973,7 +1045,6 @@ usage (FILE *out, const struct options_struct *defaultOptions) log_add (log_User, " --scroll : ff/frev during comm. pc=per-page, " "3do=smooth (default %s)", PC_3DO_optString (defaultOptions->smoothScroll)); - log_setOutput (old); } diff --git a/sc2/src/uqmversion.h b/sc2/src/uqmversion.h index 63765327a..d718a29df 100644 --- a/sc2/src/uqmversion.h +++ b/sc2/src/uqmversion.h @@ -21,8 +21,8 @@ #define UQM_MAJOR_VERSION_S "0" #define UQM_MINOR_VERSION 5 #define UQM_MINOR_VERSION_S "5" -#define UQM_PATCH_VERSION 3 -#define UQM_PATCH_VERSION_S "3" +#define UQM_PATCH_VERSION 4 +#define UQM_PATCH_VERSION_S "4" #define UQM_EXTRA_VERSION "" /* The final version is interpreted as: * printf ("%d.%d.%d%s", UQM_MAJOR_VERSION, UQM_MINOR_VERSION,